diff --git a/Login_Monitor.ps1 b/Login_Monitor.ps1
index 4aa90ad..9fb5da8 100644
--- a/Login_Monitor.ps1
+++ b/Login_Monitor.ps1
@@ -101,12 +101,39 @@ $LogDir = Split-Path $LogFile -Parent
if (!(Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null }
if (!(Test-Path $LogBackupFolder)) { New-Item -ItemType Directory -Path $LogBackupFolder -Force | Out-Null }
+# UTF-8 с BOM: иначе часть просмотрщиков (FAR и др.) открывает лог как ANSI/OEM и показывает "кракозябры".
+$script:Utf8BomEncoding = New-Object System.Text.UTF8Encoding $true
+
+function Ensure-FileStartsWithUtf8Bom {
+ param([Parameter(Mandatory = $true)][string]$Path)
+ if (-not (Test-Path -LiteralPath $Path)) { return }
+ $bytes = [System.IO.File]::ReadAllBytes($Path)
+ if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { return }
+ $bom = [byte[]](0xEF, 0xBB, 0xBF)
+ $combined = New-Object byte[] ($bom.Length + $bytes.Length)
+ [Buffer]::BlockCopy($bom, 0, $combined, 0, $bom.Length)
+ if ($bytes.Length -gt 0) {
+ [Buffer]::BlockCopy($bytes, 0, $combined, $bom.Length, $bytes.Length)
+ }
+ [System.IO.File]::WriteAllBytes($Path, $combined)
+}
+
+function Write-TextFileUtf8Bom {
+ param(
+ [Parameter(Mandatory = $true)][string]$Path,
+ [Parameter(Mandatory = $true)][string]$Text
+ )
+ [System.IO.File]::WriteAllText($Path, $Text, $script:Utf8BomEncoding)
+}
+
+Ensure-FileStartsWithUtf8Bom -Path $LogFile
+
function Write-Log {
param([string]$Message)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
- $logMessage = "$timestamp - $Message"
- Add-Content -Path $LogFile -Value $logMessage -Force -Encoding UTF8
- Write-Host $logMessage
+ $logMessage = "$timestamp - $Message" + [Environment]::NewLine
+ [System.IO.File]::AppendAllText($LogFile, $logMessage, $script:Utf8BomEncoding)
+ Write-Host ($logMessage.TrimEnd("`r`n"))
}
try {
@@ -350,7 +377,7 @@ function Send-Heartbeat {
Send-TelegramMessage -Message $message | Out-Null
Write-Log "Отправлено уведомление о запуске скрипта"
} else {
- $timestamp | Out-File -FilePath $HeartbeatFile -Force -Encoding UTF8
+ Write-TextFileUtf8Bom -Path $HeartbeatFile -Text $timestamp
}
}
@@ -376,6 +403,8 @@ function Rotate-LogFile {
Copy-Item -Path $LogFile -Destination $backupFilePath -Force
Clear-Content -Path $LogFile -Force
+ # После Clear-Content файл пустой без BOM — восстановим UTF-8 BOM для корректного просмотра в FAR/редакторах
+ Ensure-FileStartsWithUtf8Bom -Path $LogFile
Write-Log "Лог-файл скопирован в бэкап: $backupFilePath"
$oldBackups = Get-ChildItem -Path $LogBackupFolder -Filter "LoginLog_*.bak" |
@@ -414,7 +443,7 @@ function Check-AndRotateLog {
elseif ($currentTime -ge $targetRotationTime) { $shouldRotate = $true }
if ($shouldRotate -and (Rotate-LogFile)) {
- $currentTime.ToString("yyyy-MM-dd HH:mm:ss") | Out-File -FilePath $lastRotationFile -Force -Encoding UTF8
+ Write-TextFileUtf8Bom -Path $lastRotationFile -Text ($currentTime.ToString("yyyy-MM-dd HH:mm:ss"))
}
return $targetRotationTime
}
@@ -687,15 +716,38 @@ function Format-RDGatewayEvent {
function Send-DailyReport {
try {
- $quserOutput = & quser 2>$null
- $count = 0
- if ($quserOutput -and $quserOutput.Count -gt 1) { $count = $quserOutput.Count - 1 }
+ $quserOutput = @(& quser 2>$null)
+ $usernames = [System.Collections.Generic.List[string]]::new()
+ if ($quserOutput -and $quserOutput.Count -gt 1) {
+ $sessionLines = @($quserOutput | Select-Object -Skip 1)
+ foreach ($raw in $sessionLines) {
+ $line = ($raw -replace '\s+', ' ').Trim()
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
+ $parts = $line -split ' ', 2
+ if ($parts.Count -lt 1) { continue }
+ $u = $parts[0].Trim()
+ if (-not [string]::IsNullOrWhiteSpace($u) -and $u -ne 'USERNAME') {
+ $usernames.Add($u) | Out-Null
+ }
+ }
+ }
+ $count = $usernames.Count
+ $uniqueUsers = $usernames | Sort-Object -Unique
$message = "📊 ЕЖЕДНЕВНЫЙ ОТЧЕТ`r`n"
$message += "🖥️ Сервер: $env:COMPUTERNAME`r`n"
$message += "🕐 Время отчета: $(Get-Date -Format 'dd.MM.yyyy HH:mm:ss')`r`n"
- $message += "👥 Активных сессий (quser): $count"
+ $message += "👥 Активных сессий (quser): $count`r`n"
+ if ($uniqueUsers.Count -gt 0) {
+ $message += "`r`nУникальные логины ($($uniqueUsers.Count)):`r`n"
+ foreach ($name in $uniqueUsers) {
+ $safe = [System.Net.WebUtility]::HtmlEncode($name)
+ $message += " • $safe`r`n"
+ }
+ } else {
+ $message += "`r`nСписок пользователей недоступен (quser пуст или недостаточно прав)."
+ }
Send-TelegramMessage -Message $message | Out-Null
- (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") | Out-File -FilePath $LastReportFile -Force -Encoding UTF8
+ Write-TextFileUtf8Bom -Path $LastReportFile -Text ((Get-Date).ToString("yyyy-MM-dd HH:mm:ss"))
Write-Log "Ежедневный отчет отправлен"
return $true
} catch {
diff --git a/README.md b/README.md
index eb507b9..ad150fb 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ PowerShell-набор для мониторинга входов в Windows с
## Что изменилось (важное)
- **Кодировка `.ps1`**: в репозитории добавлены `.editorconfig` и `.gitattributes`, чтобы `*.ps1` по умолчанию сохранялись как **UTF-8 with BOM** и с **CRLF** (это сильно снижает “кракозябры” и ошибки парсинга PowerShell).
+- **Кодировка логов**: `login_monitor.log` / `watchdog.log` пишутся как **UTF-8 с BOM** (и при необходимости BOM добавляется к уже существующему файлу), чтобы в **FAR/старых просмотрщиках** не было ситуации “в консоли нормально, а в файле РЈРІРµ…” из‑за неверной авто-кодировки.
- **Фильтрация шума PMG/LDAP**: добавлено узкое исключение для `4624` с `LogonType=3`, если `IpAddress=192.168.160.57` и `LogonProcessName` содержит `Advapi` (частые периодические сетевые логоны).
- **`auditpol` на русской Windows**: настройка/проверка аудита опирается на категорию **`Вход/выход`** и подкатегории **`Вход в систему` / `Выход из системы`** (ожидается строка **`Успех и сбой`**). Это устраняет ошибки вида `0x00000057` из‑за несуществующего на RU ОС имени `Logon`.
- **Стабильность**: `auditpol` запускается через `cmd.exe` с перехватом `stdout+stderr`, чтобы не ломать выполнение при `$ErrorActionPreference = "Stop"`.
diff --git a/Watchdog_RDP_Monitor.ps1 b/Watchdog_RDP_Monitor.ps1
index 83c50d1..a472120 100644
--- a/Watchdog_RDP_Monitor.ps1
+++ b/Watchdog_RDP_Monitor.ps1
@@ -18,15 +18,36 @@ param(
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
+$script:Utf8BomEncoding = New-Object System.Text.UTF8Encoding $true
+$script:WatchdogLogBomChecked = $false
+
+function Ensure-FileStartsWithUtf8Bom {
+ param([Parameter(Mandatory = $true)][string]$Path)
+ if (-not (Test-Path -LiteralPath $Path)) { return }
+ $bytes = [System.IO.File]::ReadAllBytes($Path)
+ if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { return }
+ $bom = [byte[]](0xEF, 0xBB, 0xBF)
+ $combined = New-Object byte[] ($bom.Length + $bytes.Length)
+ [Buffer]::BlockCopy($bom, 0, $combined, 0, $bom.Length)
+ if ($bytes.Length -gt 0) {
+ [Buffer]::BlockCopy($bytes, 0, $combined, $bom.Length, $bytes.Length)
+ }
+ [System.IO.File]::WriteAllBytes($Path, $combined)
+}
+
function Write-WatchdogLog {
param([string]$Message)
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
- $line = "$ts - $Message"
+ $line = "$ts - $Message" + [Environment]::NewLine
$dir = Split-Path -Parent $WatchdogLog
if ($dir -and -not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
- Add-Content -Path $WatchdogLog -Value $line -Encoding UTF8
+ if (-not $script:WatchdogLogBomChecked) {
+ Ensure-FileStartsWithUtf8Bom -Path $WatchdogLog
+ $script:WatchdogLogBomChecked = $true
+ }
+ [System.IO.File]::AppendAllText($WatchdogLog, $line, $script:Utf8BomEncoding)
}
function Get-MainScriptProcesses {