Workstation vs server: ProductType; WKS uses LT10 + RCM 1149; server keeps 2/3/10 (v1.2.0)
This commit is contained in:
+154
-3
@@ -10,6 +10,8 @@
|
|||||||
- На некоторых серверах RDP-логин приходит как LogonType=3, поэтому интерактивные типы: 2/3/10.
|
- На некоторых серверах RDP-логин приходит как LogonType=3, поэтому интерактивные типы: 2/3/10.
|
||||||
- Добавлены исключения шума: DWM-*, UMFD-*, HealthMailbox*, Font Driver Host*, NtLmSsp и др.
|
- Добавлены исключения шума: DWM-*, UMFD-*, HealthMailbox*, Font Driver Host*, NtLmSsp и др.
|
||||||
- Heartbeat без дрейфа: используется nextHeartbeatTime (а не "прошло N секунд").
|
- Heartbeat без дрейфа: используется nextHeartbeatTime (а не "прошло N секунд").
|
||||||
|
- Win32_OperatingSystem.ProductType: 1 = рабочая станция (4624/4625 только LogonType 10 + при наличии журнала событие 1149 RCM);
|
||||||
|
2/3 = сервер/КД — прежняя логика (типы 2, 3, 10).
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
@@ -30,7 +32,7 @@ function Uc { param([int[]]$C) -join ($C | ForEach-Object { [char]$_ }) }
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# Версия (обновляйте при значимых изменениях; пишется в лог и в консоль при интерактивном запуске)
|
# Версия (обновляйте при значимых изменениях; пишется в лог и в консоль при интерактивном запуске)
|
||||||
$ScriptVersion = "1.1.3"
|
$ScriptVersion = "1.2.0"
|
||||||
|
|
||||||
# Логи
|
# Логи
|
||||||
$LogFile = "D:\Soft\Logs\login_monitor.log"
|
$LogFile = "D:\Soft\Logs\login_monitor.log"
|
||||||
@@ -55,6 +57,10 @@ $EnableRDGatewayMonitoring = $true
|
|||||||
$RDGatewayLogName = "Microsoft-Windows-TerminalServices-Gateway/Operational"
|
$RDGatewayLogName = "Microsoft-Windows-TerminalServices-Gateway/Operational"
|
||||||
$RDGatewayEvents = @(302, 303)
|
$RDGatewayEvents = @(302, 303)
|
||||||
|
|
||||||
|
# RDP Remote Connection Manager (workstations): User authentication succeeded — событие 1149
|
||||||
|
$RcmLogName = "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational"
|
||||||
|
$RcmEventId = 1149
|
||||||
|
|
||||||
$ExcludedProcesses = @(
|
$ExcludedProcesses = @(
|
||||||
"HTTP", "HTTP/*", "W3WP.EXE", "MSExchange", "SYSTEM", "LOCAL SERVICE",
|
"HTTP", "HTTP/*", "W3WP.EXE", "MSExchange", "SYSTEM", "LOCAL SERVICE",
|
||||||
"NETWORK SERVICE", "OUTLOOK.EXE", "EXCHANGE", "EDGETRANSPORT", "STORE.EXE",
|
"NETWORK SERVICE", "OUTLOOK.EXE", "EXCHANGE", "EDGETRANSPORT", "STORE.EXE",
|
||||||
@@ -216,6 +222,9 @@ if (-not (Test-Administrator)) {
|
|||||||
}
|
}
|
||||||
Write-Log "Скрипт запущен с правами администратора, версия $ScriptVersion"
|
Write-Log "Скрипт запущен с правами администратора, версия $ScriptVersion"
|
||||||
|
|
||||||
|
$script:IsWorkstation = $false
|
||||||
|
$script:OsInstallKindLabel = ""
|
||||||
|
|
||||||
function Enable-SecurityAudit {
|
function Enable-SecurityAudit {
|
||||||
Write-Log "Checking security audit (auditpol) settings..."
|
Write-Log "Checking security audit (auditpol) settings..."
|
||||||
|
|
||||||
@@ -398,6 +407,97 @@ function Test-RDSDeploymentPresent {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-OsInstallKind {
|
||||||
|
try {
|
||||||
|
$os = Get-CimInstance Win32_OperatingSystem -ErrorAction Stop
|
||||||
|
$pt = [int]$os.ProductType
|
||||||
|
# 1 Workstation, 2 Domain Controller, 3 Server (member)
|
||||||
|
$label = switch ($pt) {
|
||||||
|
1 { 'Workstation' }
|
||||||
|
2 { 'Domain Controller' }
|
||||||
|
3 { 'Server' }
|
||||||
|
default { "ProductType=$pt" }
|
||||||
|
}
|
||||||
|
return [pscustomobject]@{
|
||||||
|
ProductType = $pt
|
||||||
|
IsWorkstation = ($pt -eq 1)
|
||||||
|
Label = $label
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Log "Определение SKU ОС не удалось: $($_.Exception.Message); считаем Server"
|
||||||
|
return [pscustomobject]@{
|
||||||
|
ProductType = 3
|
||||||
|
IsWorkstation = $false
|
||||||
|
Label = 'Unknown (assume Server)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-RcmLogAvailable {
|
||||||
|
try {
|
||||||
|
$logExists = Get-WinEvent -ListLog $RcmLogName -ErrorAction SilentlyContinue
|
||||||
|
return [bool]$logExists
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Rcm1149EventInfo {
|
||||||
|
param($Event)
|
||||||
|
$eventData = @{
|
||||||
|
TimeCreated = $Event.TimeCreated
|
||||||
|
Username = "-"
|
||||||
|
ClientIP = "-"
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$map = Get-EventDataMap -Event $Event
|
||||||
|
$user = Get-FirstNonEmptyMapValue -DataMap $map -Keys @(
|
||||||
|
"TargetUser","User","Domain User","Param1","AccountName","ConnectionUser","SubjectUserName"
|
||||||
|
)
|
||||||
|
$ip = Get-FirstNonEmptyMapValue -DataMap $map -Keys @(
|
||||||
|
"ClientIP","Client Address","IpAddress","Ip","Param3","Address","CallingStationId"
|
||||||
|
)
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($user)) { $eventData.Username = [string]$user }
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($ip)) { $eventData.ClientIP = [string]$ip }
|
||||||
|
|
||||||
|
if ($eventData.Username -eq '-' -and $Event.Properties.Count -gt 0) {
|
||||||
|
$eventData.Username = [string]$Event.Properties[0].Value
|
||||||
|
}
|
||||||
|
if ($eventData.ClientIP -eq '-') {
|
||||||
|
if ($Event.Properties.Count -gt 2) {
|
||||||
|
$eventData.ClientIP = [string]$Event.Properties[2].Value
|
||||||
|
} elseif ($Event.Properties.Count -gt 1) {
|
||||||
|
$eventData.ClientIP = [string]$Event.Properties[1].Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Log "Ошибка разбора события RCM 1149: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
return $eventData
|
||||||
|
}
|
||||||
|
|
||||||
|
function Format-Rcm1149Event {
|
||||||
|
param(
|
||||||
|
[string]$Username,
|
||||||
|
[string]$ClientIP,
|
||||||
|
[datetime]$TimeCreated,
|
||||||
|
[string]$SecurityLogComputerName
|
||||||
|
)
|
||||||
|
$logHost = $SecurityLogComputerName
|
||||||
|
if ([string]::IsNullOrWhiteSpace($logHost)) { $logHost = $env:COMPUTERNAME }
|
||||||
|
$hUser = (ConvertTo-TelegramHtml $Username)
|
||||||
|
$hIp = (ConvertTo-TelegramHtml $ClientIP)
|
||||||
|
$hLog = (ConvertTo-TelegramHtml $logHost)
|
||||||
|
$hTime = (ConvertTo-TelegramHtml ($TimeCreated.ToString('dd.MM.yyyy HH:mm:ss')))
|
||||||
|
$message = "<b>🔑 RDP: успешная аутентификация (1149)</b>`r`n"
|
||||||
|
$message += "👤 Пользователь: $hUser`r`n"
|
||||||
|
$message += "🏢 Узел: $hLog`r`n"
|
||||||
|
$message += "🌐 Клиент (IP): $hIp`r`n"
|
||||||
|
$message += "🕐 Время: $hTime`r`n"
|
||||||
|
$message += "🔢 Event ID: 1149 (RemoteConnectionManager)"
|
||||||
|
return $message
|
||||||
|
}
|
||||||
|
|
||||||
function Send-Heartbeat {
|
function Send-Heartbeat {
|
||||||
param([switch]$IsStartup = $false)
|
param([switch]$IsStartup = $false)
|
||||||
|
|
||||||
@@ -408,6 +508,14 @@ function Send-Heartbeat {
|
|||||||
$message = "<b>✅ Мониторинг логинов ЗАПУЩЕН</b>`r`n"
|
$message = "<b>✅ Мониторинг логинов ЗАПУЩЕН</b>`r`n"
|
||||||
$message += "🖥️ Сервер: $hHost`r`n"
|
$message += "🖥️ Сервер: $hHost`r`n"
|
||||||
$message += "🕐 Время запуска: $timestamp"
|
$message += "🕐 Время запуска: $timestamp"
|
||||||
|
if ($script:OsInstallKindLabel) {
|
||||||
|
$message += "`r`n💻 <b>Тип установки:</b> $(ConvertTo-TelegramHtml $script:OsInstallKindLabel)"
|
||||||
|
}
|
||||||
|
if ($script:IsWorkstation) {
|
||||||
|
$message += "`r`n📌 <b>Режим:</b> рабочая станция — Security 4624/4625 только LogonType 10 (RDP); при наличии журнала — также 1149 (Remote Connection Manager)."
|
||||||
|
} else {
|
||||||
|
$message += "`r`n📌 <b>Режим:</b> сервер — Security 4624/4625, типы входа 2, 3, 10."
|
||||||
|
}
|
||||||
if (Test-RDSDeploymentPresent) {
|
if (Test-RDSDeploymentPresent) {
|
||||||
$message += "`r`n🔐 <b>RDS (хост сессий):</b> обнаружены компоненты RDS помимо чистого шлюза — в мониторинг входят входы по RDP/RDS на этом узле (Security 4624/4625, типы входа по настройке скрипта)."
|
$message += "`r`n🔐 <b>RDS (хост сессий):</b> обнаружены компоненты RDS помимо чистого шлюза — в мониторинг входят входы по RDP/RDS на этом узле (Security 4624/4625, типы входа по настройке скрипта)."
|
||||||
}
|
}
|
||||||
@@ -867,9 +975,18 @@ function Start-LoginMonitor {
|
|||||||
[switch]$MonitorInteractiveOnly = $true
|
[switch]$MonitorInteractiveOnly = $true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
$osKind = Get-OsInstallKind
|
||||||
|
$script:IsWorkstation = $osKind.IsWorkstation
|
||||||
|
$script:OsInstallKindLabel = $osKind.Label
|
||||||
|
Write-Log "Тип ОС (Win32_ProductType=$($osKind.ProductType)): $($osKind.Label)"
|
||||||
|
|
||||||
Write-Log "========================================"
|
Write-Log "========================================"
|
||||||
Write-Log "Запуск мониторинга логинов"
|
Write-Log "Запуск мониторинга логинов"
|
||||||
Write-Log "Интерактивные типы: 2,3,10"
|
if ($osKind.IsWorkstation) {
|
||||||
|
Write-Log "Режим рабочей станции: Security — только LogonType 10 (RDP); при наличии журнала — событие 1149 (Remote Connection Manager)."
|
||||||
|
} else {
|
||||||
|
Write-Log "Режим сервера: Security — LogonType 2, 3, 10"
|
||||||
|
}
|
||||||
Write-Log "========================================"
|
Write-Log "========================================"
|
||||||
|
|
||||||
Cleanup-OldLogs
|
Cleanup-OldLogs
|
||||||
@@ -879,11 +996,17 @@ function Start-LoginMonitor {
|
|||||||
$rdGatewayAvailable = $false
|
$rdGatewayAvailable = $false
|
||||||
if ($EnableRDGatewayMonitoring) { $rdGatewayAvailable = Test-RDGatewayLog }
|
if ($EnableRDGatewayMonitoring) { $rdGatewayAvailable = Test-RDGatewayLog }
|
||||||
|
|
||||||
|
$rcmMonitoringEnabled = ($osKind.IsWorkstation -and (Test-RcmLogAvailable))
|
||||||
|
if ($osKind.IsWorkstation -and -not $rcmMonitoringEnabled) {
|
||||||
|
Write-Log "Рабочая станция: журнал Remote Connection Manager недоступен — уведомления только по Security 4624/4625 (LogonType 10). Проверьте, что включён удалённый рабочий стол."
|
||||||
|
}
|
||||||
|
|
||||||
$nextHeartbeatTime = (Get-Date).AddSeconds($HeartbeatInterval)
|
$nextHeartbeatTime = (Get-Date).AddSeconds($HeartbeatInterval)
|
||||||
$nextRotationCheck = Check-AndRotateLog
|
$nextRotationCheck = Check-AndRotateLog
|
||||||
$nextReportCheck = Check-AndSendDailyReport
|
$nextReportCheck = Check-AndSendDailyReport
|
||||||
$lastCheckTime = (Get-Date).AddSeconds(-10)
|
$lastCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
$lastGatewayCheckTime = (Get-Date).AddSeconds(-10)
|
$lastGatewayCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
|
$lastRcmCheckTime = (Get-Date).AddSeconds(-10)
|
||||||
$monitorEvents = @(4624, 4625, 4648)
|
$monitorEvents = @(4624, 4625, 4648)
|
||||||
|
|
||||||
while ($true) {
|
while ($true) {
|
||||||
@@ -906,7 +1029,11 @@ function Start-LoginMonitor {
|
|||||||
if ($event.Id -eq 4648) {
|
if ($event.Id -eq 4648) {
|
||||||
$shouldIgnore = $true
|
$shouldIgnore = $true
|
||||||
} elseif ($event.Id -in 4624, 4625) {
|
} elseif ($event.Id -in 4624, 4625) {
|
||||||
$interactiveTypes = @(2, 3, 10)
|
if ($osKind.IsWorkstation) {
|
||||||
|
$interactiveTypes = @(10)
|
||||||
|
} else {
|
||||||
|
$interactiveTypes = @(2, 3, 10)
|
||||||
|
}
|
||||||
if ($interactiveTypes -notcontains $eventInfo.LogonType) {
|
if ($interactiveTypes -notcontains $eventInfo.LogonType) {
|
||||||
$shouldIgnore = $true
|
$shouldIgnore = $true
|
||||||
}
|
}
|
||||||
@@ -968,6 +1095,30 @@ function Start-LoginMonitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($rcmMonitoringEnabled) {
|
||||||
|
$rcmEvents = Get-WinEvent -FilterHashtable @{
|
||||||
|
LogName = $RcmLogName
|
||||||
|
ID = $RcmEventId
|
||||||
|
StartTime = $lastRcmCheckTime
|
||||||
|
} -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($rcmEvents) {
|
||||||
|
foreach ($event in $rcmEvents) {
|
||||||
|
if ($event.TimeCreated -le $lastRcmCheckTime) { continue }
|
||||||
|
$rcmInfo = Get-Rcm1149EventInfo -Event $event
|
||||||
|
if ($rcmInfo.Username -like "*$") { continue }
|
||||||
|
if (Should-IgnoreEvent -Username $rcmInfo.Username -ProcessName "-" `
|
||||||
|
-ComputerName "-" -EventID 1149 -LogonType 10 -SourceIP $rcmInfo.ClientIP) { continue }
|
||||||
|
|
||||||
|
$msg = Format-Rcm1149Event -Username $rcmInfo.Username -ClientIP $rcmInfo.ClientIP `
|
||||||
|
-TimeCreated $rcmInfo.TimeCreated -SecurityLogComputerName $event.MachineName
|
||||||
|
Write-Log "Notify RCM 1149: User=$($rcmInfo.Username) IP=$($rcmInfo.ClientIP)"
|
||||||
|
Send-TelegramMessage -Message $msg | Out-Null
|
||||||
|
}
|
||||||
|
$lastRcmCheckTime = ($rcmEvents | Measure-Object -Property TimeCreated -Maximum | Select-Object -ExpandProperty Maximum).AddSeconds(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$now = Get-Date
|
$now = Get-Date
|
||||||
if ($now -ge $nextHeartbeatTime) {
|
if ($now -ge $nextHeartbeatTime) {
|
||||||
Send-Heartbeat
|
Send-Heartbeat
|
||||||
|
|||||||
Reference in New Issue
Block a user