Compare commits
15 Commits
1.4.3
..
ab0504c3f9
| Author | SHA1 | Date | |
|---|---|---|---|
| ab0504c3f9 | |||
| d8ab28098e | |||
| 4386a58942 | |||
| d951f54874 | |||
| 4bef73001f | |||
| 4f6385d483 | |||
| 0ce6b8abde | |||
| 6072d70e59 | |||
| 7b9d7dcd40 | |||
| b39d9c0deb | |||
| 0e5196e769 | |||
| 578230f915 | |||
| a337e72a4d | |||
| 50a5f87603 | |||
| 2ae691f83c |
@@ -1,195 +0,0 @@
|
||||
# Развёртывание RDP Login Monitor в домене
|
||||
|
||||
Монитор ставится в **`C:\ProgramData\RDP-login-monitor\`**, задачи планировщика создаёт сам **`Login_Monitor.ps1`** (параметр `-InstallTasks`). Доставку по сети выполняет **`Deploy-LoginMonitor.ps1`**.
|
||||
|
||||
## Файлы на файловой шаре
|
||||
|
||||
Создайте каталог, доступный **конечным компьютерам** на чтение (часто учётная запись компьютера домена), например:
|
||||
|
||||
`\\dc.contoso.local\NETLOGON\RDP-login-monitor\`
|
||||
|
||||
Внутри должны лежать **три файла** (имена фиксированы):
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|------------|
|
||||
| `Login_Monitor.ps1` | Основной скрипт (токен/chat id plain или DPAPI в параметрах). |
|
||||
| `version.txt` | **Одна строка** — номер версии пакета на шаре (см. раздел «Версии» ниже). |
|
||||
| `Deploy-LoginMonitor.ps1` | Установщик: сравнивает версию, копирует монитор, вызывает `-InstallTasks`, при необходимости запускает процесс монитора. |
|
||||
|
||||
При выпуске новой сборки обновляйте на шаре **`Login_Monitor.ps1`** и при необходимости **`version.txt`** (логика описана в разделе «Версии»).
|
||||
|
||||
## Как это работает
|
||||
|
||||
1. **`Deploy-LoginMonitor.ps1`** определяет корень дистрибутива:
|
||||
- параметр **`-SourceShareRoot`** `\\server\share\RDP-login-monitor`, **или**
|
||||
- если скрипт запущен по UNC, берётся **родительская папка** этого файла (удобно вызывать шару без параметров).
|
||||
|
||||
2. Читается **`version.txt`** на шаре и сравнивается с локальной меткой **`C:\ProgramData\RDP-login-monitor\deployed_version.txt`**. Если метки ещё нет — для сравнения подтягивается **`$ScriptVersion`** из уже установленного **`Login_Monitor.ps1`**.
|
||||
|
||||
3. Если версия на шаре **совпадает** с зафиксированной локально — выход без копирования (быстро, можно при каждой загрузке).
|
||||
|
||||
4. Если версия на шаре **новее** — останавливаются процессы монитора с каноническим путём → копируется **`Login_Monitor.ps1`** → выполняется **`Login_Monitor.ps1 -InstallTasks`** → записывается **`deployed_version.txt`** → запускается монитор (если не указан **`-SkipStartMonitorAfterUpdate`**).
|
||||
|
||||
5. Если версия на шаре **старее** локальной — обновление **не выполняется** (защита от отката), пока не указан **`-AllowDowngrade`**.
|
||||
|
||||
Лог установки: **`C:\ProgramData\RDP-login-monitor\Logs\deploy.log`**.
|
||||
|
||||
## Опционально на клиенте: `ignore.lst`
|
||||
|
||||
На каждом компьютере/сервере можно положить файл **`C:\ProgramData\RDP-login-monitor\ignore.lst`** (в том же каталоге, что и **`Login_Monitor.ps1`**). По строкам этого файла монитор **не отправляет в Telegram** отдельные события **Security `4624`/`4625`** (например, шум от известной рабочей станции, тестового пользователя или фиксированного IP).
|
||||
|
||||
- **`Deploy-LoginMonitor.ps1` с шары `ignore.lst` не доставляет** — при необходимости создавайте файл локально или копируйте своим способом.
|
||||
- Примеры синтаксиса — в репозитории в файле **`ignore.lst.example`**.
|
||||
|
||||
## Задачи планировщика после `-InstallTasks`
|
||||
|
||||
Параметр **`Login_Monitor.ps1 -InstallTasks`** создаёт две задачи:
|
||||
|
||||
| Имя | Назначение |
|
||||
|-----|------------|
|
||||
| **`RDP-Login-Monitor`** | Запуск основного монитора при старте ОС (`Register-ScheduledTask`, триггер «При запуске компьютера»). |
|
||||
| **`RDP-Login-Monitor-Watchdog`** | Тот же файл **`Login_Monitor.ps1`** с аргументом **`-Watchdog`**: короткая проверка «жив ли монитор», при необходимости поднимает процесс. |
|
||||
|
||||
Watchdog регистрируется через **`schtasks.exe /Create /SC MINUTE /MO 5`** (а не через CIM-триггеры PowerShell): на части ОС у объектов триггера нет настраиваемых **`RepetitionInterval`** / длительность «раз и навсегда» режется планировщиком — из‑за этого раньше вторая задача могла не создаваться.
|
||||
|
||||
Перед созданием выполняется **`schtasks /Delete … /F`** (если задачи ещё не было, сообщение об ошибке подавляется — это нормально).
|
||||
|
||||
Сразу после регистрации задач вызывается **`schtasks /Run`** и для **основной задачи**, и для **watchdog**: первый запуск не ждёт перезагрузку и ближайшее 5‑минутное окно.
|
||||
|
||||
Если watchdog срабатывает, а основной монитор «не поднимается», смотрите **`Logs\watchdog.log`** (сообщение о PID и предупреждение, если процесс сразу завершился) и **конец `Logs\login_monitor.log`** — частая причина (исправлено в **1.3.5+**): пустой **`$PSCommandPath`** у процесса, запущенного через **`Start-Process`**; скрипт теперь подставляет путь через **`$PSScriptRoot`**.
|
||||
|
||||
Один экземпляр монитора фиксируется **файлом блокировки** в **`C:\ProgramData\RDP-login-monitor\.login_monitor_single_instance.lock`** (не Global mutex): так и **SYSTEM** (задача планировщика), и **интерактивный администратор** могут корректно запускать скрипт вручную без «Отказано в доступе» к mutex (версия **1.3.6+**).
|
||||
|
||||
Проверка:
|
||||
|
||||
```powershell
|
||||
Get-ScheduledTask -TaskName 'RDP-Login-Monitor','RDP-Login-Monitor-Watchdog' -ErrorAction SilentlyContinue
|
||||
schtasks /Query /TN "RDP-Login-Monitor-Watchdog"
|
||||
```
|
||||
|
||||
Логи: **`...\Logs\login_monitor.log`**, **`...\Logs\watchdog.log`**.
|
||||
|
||||
## Запуск Deploy и политики с файловой шары
|
||||
|
||||
Если при запуске **`Deploy-LoginMonitor.ps1`** или **`Login_Monitor.ps1`** с UNC по **FQDN** (`\\dc.domain.local\...`) PowerShell сообщает про **цифровую подпись** / политику выполнения, чаще всего помогает путь по **короткому имени** контроллера: **`\\DC01\NETLOGON\...`** (клиент относит UNC к интрасети иначе). Параллельно убедитесь, что команда реально с **`powershell.exe -ExecutionPolicy Bypass -File "..."`**.
|
||||
|
||||
## Версии: `version.txt` и `$ScriptVersion`
|
||||
|
||||
| Что | Роль |
|
||||
|-----|------|
|
||||
| **`version.txt` на шаре** | Единственный источник для **`Deploy-LoginMonitor.ps1`**: решение «класть ли новый файл на компьютер». Поднимайте номер **всякий раз**, когда нужно, чтобы доменные машины забрали **новую копию** скрипта с шары — в том числе при мелких правках **без** изменения «видимой» версии в логах. |
|
||||
| **`$ScriptVersion` в `Login_Monitor.ps1`** | Версия для **логов и Telegram** (что видит администратор). Меняйте при **значимых** релизах; для полной ясности можно держать ту же строку, что и в **`version.txt`**. |
|
||||
|
||||
**Типичные сценарии:**
|
||||
|
||||
- Крупный релиз: обновили **`Login_Monitor.ps1`**, подняли **`$ScriptVersion`** (например `1.4.0`) и записали то же в **`version.txt`** на шаре.
|
||||
- Мелкая правка на шаре (опечатка, узкий фикс), не хотите путать отчёты по версии в логах: поднимите только **`version.txt`** (например с `1.3.0` на **`1.3.0.1`** — поддерживаются четырёхкомпонентные номера .NET **Version**). **`$ScriptVersion`** можно не трогать; на клиентах после деплоя в логах по-прежнему будет старая «человеческая» версия, но файл будет актуальным.
|
||||
- Идеально поддерживать синхронность **`version.txt`** и **`$ScriptVersion`**, когда правки крупные и версия в логах должна совпадать с дистрибутивом.
|
||||
|
||||
Итого: **обновляться «по сети» обязан именно `version.txt`**; строка **`$ScriptVersion`** нужна для прозрачности в мониторинге и может совпадать с шарой или отставать по «маркетингу», если вы сознательно крутите только patch в **`version.txt`**.
|
||||
|
||||
## Групповая политика (GPO): проверенная схема
|
||||
|
||||
Ниже шаги, которые были проверены на практике для запуска деплоя при старте компьютера.
|
||||
|
||||
1. Положите три файла на шару (см. выше), например:
|
||||
|
||||
`\\B26\NETLOGON\RDP-login-monitor\`
|
||||
|
||||
2. Создайте/настройте GPO и привяжите её к OU с **компьютерами** (не пользователями).
|
||||
|
||||
3. В GPO откройте:
|
||||
**Конфигурация компьютера** → **Политики** → **Конфигурация Windows** → **Сценарии (запуск/завершение)** → **Автозагрузка**.
|
||||
|
||||
4. На вкладке **«Сценарии PowerShell»** добавьте скрипт:
|
||||
|
||||
`\\B26\NETLOGON\RDP-login-monitor\Deploy-LoginMonitor.ps1`
|
||||
|
||||
Параметры можно оставить пустыми, если рядом на шаре лежат `Login_Monitor.ps1` и `version.txt`.
|
||||
|
||||
5. Security Filtering:
|
||||
- можно убрать `Authenticated Users`,
|
||||
- добавить группу компьютеров (например `B26\RDP-Login`),
|
||||
- убедиться, что у неё есть права **Read** + **Apply group policy**.
|
||||
|
||||
6. Проверьте, что в группе действительно состоит объект компьютера, например `FVG-PC$`.
|
||||
|
||||
7. Проверьте доступ на шару/NTFS для контекста компьютера (обычно через `Domain Computers` или целевую группу), потому что startup-скрипт выполняется от имени **SYSTEM**.
|
||||
|
||||
### Важные нюансы
|
||||
|
||||
- Кнопка **«Показать файлы»** в свойствах Startup показывает папку GPO в `SYSVOL` (`...\Policies\{GUID}\Machine\Scripts\Startup`).
|
||||
- Если вы указали в настройке сценария **UNC путь в NETLOGON**, ваш `.ps1` в этой папке `SYSVOL` не появится — это нормально.
|
||||
- После изменения membership компьютера в security-группе часто требуется **перезагрузка** (не только `gpupdate /force`), чтобы обновился токен компьютера.
|
||||
|
||||
### Как запускать без ожидания перезагрузки
|
||||
|
||||
- Startup-сценарий GPO штатно отрабатывает при загрузке ОС, но сам `Deploy-LoginMonitor.ps1` после `-InstallTasks` теперь инициирует немедленный `schtasks /Run`:
|
||||
- `RDP-Login-Monitor`
|
||||
- `RDP-Login-Monitor-Watchdog`
|
||||
- Поэтому после фактического запуска deploy монитор и watchdog стартуют сразу.
|
||||
|
||||
### Серверы без перезагрузок: периодический Deploy
|
||||
|
||||
Если сервер перезагружается редко, одного Startup-сценария недостаточно для своевременного обновления. В этом случае добавьте отдельную задачу, которая периодически запускает `Deploy-LoginMonitor.ps1` с шары.
|
||||
|
||||
Если серверов немного, можно обойтись без отдельной задачи: при выпуске новой версии выполнять `Deploy-LoginMonitor.ps1` вручную на нужных серверах.
|
||||
|
||||
Готовый скрипт в репозитории:
|
||||
|
||||
- `Install-DeployScheduledTask.ps1`
|
||||
|
||||
Этот вариант удобен для большого парка серверов, когда обновления нужно подтягивать автоматически и без ручного обхода.
|
||||
|
||||
Пример (каждые 60 минут и сразу запустить):
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File ".\Install-DeployScheduledTask.ps1" `
|
||||
-TaskName "RDP-Login-Monitor-Deploy" `
|
||||
-DeployScriptPath "\\B26\NETLOGON\RDP-login-monitor\Deploy-LoginMonitor.ps1" `
|
||||
-RepeatMinutes 60 `
|
||||
-RunNow
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```powershell
|
||||
schtasks /Query /TN "RDP-Login-Monitor-Deploy" /V /FO LIST
|
||||
```
|
||||
|
||||
Важно: эта задача отвечает только за доставку обновлений (через `version.txt`). За «живость» процесса по-прежнему отвечает `RDP-Login-Monitor-Watchdog`.
|
||||
|
||||
Пример ручного запуска (для небольшого количества серверов):
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "\\B26\NETLOGON\RDP-login-monitor\Deploy-LoginMonitor.ps1"
|
||||
```
|
||||
|
||||
### Диагностика GPO/Startup
|
||||
|
||||
- События применения политики и startup-сценариев:
|
||||
- `Event Viewer -> Applications and Services Logs -> Microsoft -> Windows -> GroupPolicy -> Operational`
|
||||
- Логи на клиенте:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\deploy.log`
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\login_monitor.log`
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\watchdog.log`
|
||||
- Быстрая ручная проверка deploy:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "\\B26\NETLOGON\RDP-login-monitor\Deploy-LoginMonitor.ps1"
|
||||
```
|
||||
|
||||
## Ручная проверка
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "\\...\Deploy-LoginMonitor.ps1"
|
||||
```
|
||||
|
||||
- **`-WhatIf`** — только сообщение в лог, без копирования.
|
||||
- **`-SkipStartMonitorAfterUpdate`** — после обновления не запускать процесс монитора (остаются задачи планировщика и следующая загрузка / watchdog).
|
||||
|
||||
## Безопасность и замечания
|
||||
|
||||
- ACL на шару: чтение только нужным **компьютерам** / группам; файл **`Login_Monitor.ps1`** может содержать токен — ограничивайте доступ.
|
||||
- DPAPI-секреты привязаны к машине: шифровать на каждой цели или использовать plain на закрытой шаре (см. комментарии в **`Login_Monitor.ps1`**).
|
||||
- Deploy при ошибках пишет в **`deploy.log`** и завершается с кодом **0**, чтобы не блокировать загрузку ОС; проблемы смотрите по логу на ПК.
|
||||
@@ -1,280 +0,0 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Доставка Login_Monitor.ps1 с файловой шары по версии (домен: ПК и серверы).
|
||||
.DESCRIPTION
|
||||
Читает version.txt на шаре, сравнивает с локальной меткой (или с версией в установленном скрипте).
|
||||
При необходимости копирует Login_Monitor.ps1 в C:\ProgramData\RDP-login-monitor\, регистрирует задачи (-InstallTasks),
|
||||
перезапускает процесс монитора.
|
||||
Предназначен для GPO «Сценарий запуска компьютера» (SYSTEM); можно запускать вручную от администратора.
|
||||
|
||||
СТРУКТУРА НА ШАРЕ (пример):
|
||||
\\dc\share\RDP-login-monitor\Login_Monitor.ps1
|
||||
\\dc\share\RDP-login-monitor\version.txt — одна строка, например: 1.3.0
|
||||
\\dc\share\RDP-login-monitor\Deploy-LoginMonitor.ps1
|
||||
|
||||
Если Deploy-LoginMonitor.ps1 запускают с этой шары, параметр -SourceShareRoot можно не указывать —
|
||||
корень шары берётся из расположения этого файла.
|
||||
|
||||
.NOTES
|
||||
Лог: C:\ProgramData\RDP-login-monitor\Logs\deploy.log
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# UNC-каталог, где лежат Login_Monitor.ps1 и version.txt. Пусто = родительский каталог этого скрипта.
|
||||
[string]$SourceShareRoot = "",
|
||||
[switch]$WhatIf,
|
||||
# После обновления не стартовать монитор (только файлы и задачи).
|
||||
[switch]$SkipStartMonitorAfterUpdate,
|
||||
# Разрешить установку более старой версии с шары (по умолчанию откаты блокируются).
|
||||
[switch]$AllowDowngrade
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Test-DeployRunningElevated {
|
||||
try {
|
||||
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
# LocalSystem (GPO startup / задачи SYSTEM): не всегда даёт true на BuiltInRole::Administrator.
|
||||
if ($null -ne $id.User -and $id.User.Value -eq 'S-1-5-18') { return $true }
|
||||
$p = New-Object Security.Principal.WindowsPrincipal($id)
|
||||
return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
$InstallRoot = [System.IO.Path]::GetFullPath("$env:ProgramData\RDP-login-monitor")
|
||||
$LocalScript = Join-Path $InstallRoot "Login_Monitor.ps1"
|
||||
$VersionStampPath = Join-Path $InstallRoot "deployed_version.txt"
|
||||
$DeployUpdateMarkerPath = Join-Path $InstallRoot "deploy_last_update.txt"
|
||||
$DeployLogPath = Join-Path $InstallRoot "Logs\deploy.log"
|
||||
$ScriptName = "Login_Monitor.ps1"
|
||||
$VersionFileName = "version.txt"
|
||||
$PsExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
|
||||
$Utf8Bom = New-Object System.Text.UTF8Encoding $true
|
||||
|
||||
function Write-DeployLog {
|
||||
param([string]$Message)
|
||||
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $Message" + [Environment]::NewLine
|
||||
try {
|
||||
$dir = Split-Path $DeployLogPath -Parent
|
||||
if (-not (Test-Path -LiteralPath $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
}
|
||||
[System.IO.File]::AppendAllText($DeployLogPath, $line, $Utf8Bom)
|
||||
} catch { }
|
||||
if ($Host.Name -eq 'ConsoleHost' -or $Host.Name -eq 'Windows PowerShell ISE Host') {
|
||||
Write-Host $line.TrimEnd("`r`n")
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-SourceShareRoot {
|
||||
if (-not [string]::IsNullOrWhiteSpace($SourceShareRoot)) {
|
||||
return [System.IO.Path]::GetFullPath($SourceShareRoot.TrimEnd('\'))
|
||||
}
|
||||
$here = $PSCommandPath
|
||||
if ([string]::IsNullOrWhiteSpace($here)) { $here = $MyInvocation.MyCommand.Path }
|
||||
if ([string]::IsNullOrWhiteSpace($here)) {
|
||||
throw "Укажите -SourceShareRoot или запускайте Deploy-LoginMonitor.ps1 с путём к файлу (например с UNC-шары)."
|
||||
}
|
||||
return [System.IO.Path]::GetFullPath((Split-Path -Parent $here))
|
||||
}
|
||||
|
||||
function Read-VersionLineFromFile {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) { return $null }
|
||||
$raw = (Get-Content -LiteralPath $Path -TotalCount 3 -ErrorAction Stop | Where-Object { $_ -match '\S' }) | Select-Object -First 1
|
||||
if ($null -eq $raw) { return $null }
|
||||
return ([string]$raw).Trim() -replace '^v', ''
|
||||
}
|
||||
|
||||
function Get-LocalDeployedVersion {
|
||||
$fromStamp = Read-VersionLineFromFile -Path $VersionStampPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($fromStamp)) { return $fromStamp }
|
||||
|
||||
if (-not (Test-Path -LiteralPath $LocalScript)) { return $null }
|
||||
|
||||
try {
|
||||
$head = Get-Content -LiteralPath $LocalScript -TotalCount 120 -ErrorAction Stop
|
||||
foreach ($ln in $head) {
|
||||
if ($ln -match '^\s*\$ScriptVersion\s*=\s*["'']([0-9]+(?:\.[0-9]+){0,3})["'']') {
|
||||
return $Matches[1]
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Normalize-VersionOrNull {
|
||||
param([string]$Text)
|
||||
if ([string]::IsNullOrWhiteSpace($Text)) { return $null }
|
||||
$t = $Text.Trim() -replace '^v', ''
|
||||
try {
|
||||
return [version]$t
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Compare-VersionStrings {
|
||||
param([string]$Left, [string]$Right)
|
||||
$a = Normalize-VersionOrNull -Text $Left
|
||||
$b = Normalize-VersionOrNull -Text $Right
|
||||
if ($null -eq $a -or $null -eq $b) { return $null }
|
||||
return $a.CompareTo($b)
|
||||
}
|
||||
|
||||
function Get-ScriptPathFromCommandLine {
|
||||
param([string]$CommandLine)
|
||||
if ([string]::IsNullOrWhiteSpace($CommandLine)) { return $null }
|
||||
$m = [regex]::Match($CommandLine, '(?i)-File\s+"([^"]+)"')
|
||||
if ($m.Success) {
|
||||
try { return [System.IO.Path]::GetFullPath($m.Groups[1].Value) } catch { return $null }
|
||||
}
|
||||
$m2 = [regex]::Match($CommandLine, '(?i)-File\s+(\S+)')
|
||||
if ($m2.Success) {
|
||||
try { return [System.IO.Path]::GetFullPath($m2.Groups[1].Value) } catch { return $null }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Test-CommandLineIsWatchdog {
|
||||
param([string]$CommandLine)
|
||||
return ($CommandLine -match '(?i)(^|\s)-Watchdog(\s|$)')
|
||||
}
|
||||
|
||||
function Stop-RdpLoginMonitorMainProcesses {
|
||||
$canonical = [System.IO.Path]::GetFullPath($LocalScript)
|
||||
try {
|
||||
$procs = Get-CimInstance Win32_Process -Filter "Name = 'powershell.exe' OR Name = 'pwsh.exe'" -ErrorAction Stop
|
||||
foreach ($proc in $procs) {
|
||||
$cl = [string]$proc.CommandLine
|
||||
if ($cl -notmatch 'Login_Monitor\.ps1') { continue }
|
||||
if (Test-CommandLineIsWatchdog -CommandLine $cl) { continue }
|
||||
$sp = Get-ScriptPathFromCommandLine -CommandLine $cl
|
||||
if ($null -eq $sp) { continue }
|
||||
if ([System.IO.Path]::GetFullPath($sp) -ne $canonical) { continue }
|
||||
if ([int]$proc.ProcessId -eq $PID) { continue }
|
||||
Write-DeployLog "Останавливаю процесс монитора PID $($proc.ProcessId) перед обновлением файла."
|
||||
Stop-Process -Id $proc.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch {
|
||||
Write-DeployLog "Предупреждение при остановке монитора: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
# --- main ---
|
||||
try {
|
||||
$shareRoot = Resolve-SourceShareRoot
|
||||
$sourceScript = Join-Path $shareRoot $ScriptName
|
||||
$sourceVersionFile = Join-Path $shareRoot $VersionFileName
|
||||
|
||||
Write-DeployLog "Deploy: корень дистрибутива: $shareRoot"
|
||||
|
||||
if (-not (Test-Path -LiteralPath $sourceScript)) {
|
||||
Write-DeployLog "ОШИБКА: на шаре нет файла: $sourceScript"
|
||||
exit 0
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $sourceVersionFile)) {
|
||||
Write-DeployLog "ОШИБКА: на шаре нет version.txt: $sourceVersionFile (добавьте одну строку с версией, как в Login_Monitor.ps1)."
|
||||
exit 0
|
||||
}
|
||||
|
||||
$shareVerRaw = Read-VersionLineFromFile -Path $sourceVersionFile
|
||||
if ([string]::IsNullOrWhiteSpace($shareVerRaw)) {
|
||||
Write-DeployLog "ОШИБКА: version.txt пустой или не читается: $sourceVersionFile"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$localVerRaw = Get-LocalDeployedVersion
|
||||
Write-DeployLog "Версия на шаре: $shareVerRaw; локально установлено: $(if ($localVerRaw) { $localVerRaw } else { '(нет)' })."
|
||||
|
||||
$cmp = Compare-VersionStrings -Left $shareVerRaw -Right $localVerRaw
|
||||
if ($null -ne $cmp) {
|
||||
if ($cmp -eq 0) {
|
||||
Write-DeployLog "Актуально, копирование не требуется."
|
||||
exit 0
|
||||
}
|
||||
if ($cmp -lt 0 -and -not $AllowDowngrade) {
|
||||
Write-DeployLog "На шаре версия старше локальной — пропуск (используйте -AllowDowngrade для отката)."
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $InstallRoot)) {
|
||||
if ($WhatIf) {
|
||||
Write-DeployLog "[WhatIf] Создать каталог $InstallRoot"
|
||||
} else {
|
||||
New-Item -ItemType Directory -Path $InstallRoot -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
if ($WhatIf) {
|
||||
Write-DeployLog "[WhatIf] Скопировать $sourceScript -> $LocalScript; InstallTasks; версия $shareVerRaw"
|
||||
exit 0
|
||||
}
|
||||
|
||||
if (-not (Test-DeployRunningElevated)) {
|
||||
Write-DeployLog "ОШИБКА: запустите Deploy из повышенной консоли PowerShell («Запуск от имени администратора»). Без этого дочерний Login_Monitor.ps1 -InstallTasks завершится с кодом 1 (нет прав на регистрацию задач)."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Stop-RdpLoginMonitorMainProcesses
|
||||
|
||||
Copy-Item -LiteralPath $sourceScript -Destination $LocalScript -Force
|
||||
Write-DeployLog "Файл скопирован: $LocalScript"
|
||||
|
||||
$installArgs = @(
|
||||
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $LocalScript, '-InstallTasks'
|
||||
)
|
||||
$instOut = Join-Path $InstallRoot "Logs\deploy_installtasks_stdout.log"
|
||||
$instErr = Join-Path $InstallRoot "Logs\deploy_installtasks_stderr.log"
|
||||
$p = Start-Process -FilePath $PsExe -ArgumentList $installArgs -Wait -PassThru -WindowStyle Hidden `
|
||||
-RedirectStandardOutput $instOut -RedirectStandardError $instErr
|
||||
if ($p.ExitCode -ne 0) {
|
||||
Write-DeployLog "Предупреждение: InstallTasks завершился с кодом $($p.ExitCode)."
|
||||
foreach ($pair in @(@($instOut, 'stdout'), @($instErr, 'stderr'))) {
|
||||
$lp = $pair[0]
|
||||
$lbl = $pair[1]
|
||||
if (Test-Path -LiteralPath $lp) {
|
||||
$tail = Get-Content -LiteralPath $lp -Tail 40 -ErrorAction SilentlyContinue
|
||||
if ($tail) {
|
||||
Write-DeployLog "InstallTasks $lbl (хвост): $($tail -join ' | ')"
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-DeployLog "Подсказка: полный вывод в Logs\deploy_installtasks_*.log; также см. login_monitor.log за это время."
|
||||
} else {
|
||||
Write-DeployLog "InstallTasks выполнен (код 0)."
|
||||
}
|
||||
|
||||
[System.IO.File]::WriteAllText($VersionStampPath, "$shareVerRaw`r`n", $Utf8Bom)
|
||||
Write-DeployLog "Записана метка версии: $VersionStampPath"
|
||||
|
||||
$updStamp = Get-Date -Format "dd.MM.yyyy HH:mm:ss"
|
||||
$updMarker = @(
|
||||
"Version=$shareVerRaw"
|
||||
"UpdatedAt=$updStamp"
|
||||
"PendingStartupNotice=1"
|
||||
) -join "`r`n"
|
||||
[System.IO.File]::WriteAllText($DeployUpdateMarkerPath, "$updMarker`r`n", $Utf8Bom)
|
||||
Write-DeployLog "Записана метка обновления: $DeployUpdateMarkerPath (Version=$shareVerRaw; UpdatedAt=$updStamp)."
|
||||
|
||||
if (-not $SkipStartMonitorAfterUpdate) {
|
||||
Start-Process -FilePath $PsExe -ArgumentList @(
|
||||
'-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $LocalScript
|
||||
) -WindowStyle Hidden
|
||||
Write-DeployLog "Запущен процесс монитора (новый файл)."
|
||||
} else {
|
||||
Write-DeployLog "Запуск монитора пропущен (-SkipStartMonitorAfterUpdate); поднимется при следующей загрузке или watchdog."
|
||||
}
|
||||
|
||||
exit 0
|
||||
} catch {
|
||||
Write-DeployLog "КРИТИЧЕСКАЯ ОШИБКА: $($_.Exception.Message)"
|
||||
exit 0
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
# Запуск: от администратора на ТОМ ЖЕ компьютере, где будет Login_Monitor.ps1.
|
||||
# Результат (Base64) вставьте в $TelegramBotTokenProtectedB64 / $TelegramChatIDProtectedB64.
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$PlainText
|
||||
)
|
||||
Add-Type -AssemblyName System.Security
|
||||
$bytes = [Text.Encoding]::UTF8.GetBytes($PlainText)
|
||||
$protected = [System.Security.Cryptography.ProtectedData]::Protect(
|
||||
$bytes, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine
|
||||
)
|
||||
[Convert]::ToBase64String($protected)
|
||||
@@ -1,45 +0,0 @@
|
||||
#Requires -RunAsAdministrator
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$TaskName = "RDP-Login-Monitor-Deploy",
|
||||
[string]$DeployScriptPath = "\\B26\NETLOGON\RDP-login-monitor\Deploy-LoginMonitor.ps1",
|
||||
[ValidateRange(5, 1440)][int]$RepeatMinutes = 60,
|
||||
[switch]$RunNow
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($DeployScriptPath)) {
|
||||
throw "DeployScriptPath не задан."
|
||||
}
|
||||
|
||||
$psExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
if (-not (Test-Path -LiteralPath $psExe)) {
|
||||
throw "Не найден powershell.exe по пути: $psExe"
|
||||
}
|
||||
|
||||
$taskCmd = "`"$psExe`" -NoProfile -ExecutionPolicy Bypass -File `"$DeployScriptPath`""
|
||||
$schtasksExe = Join-Path $env:SystemRoot "System32\schtasks.exe"
|
||||
|
||||
Write-Host "Создаю/обновляю задачу: $TaskName" -ForegroundColor Cyan
|
||||
Write-Host "Команда: $taskCmd" -ForegroundColor DarkGray
|
||||
Write-Host "Периодичность: каждые $RepeatMinutes мин." -ForegroundColor DarkGray
|
||||
|
||||
& $schtasksExe /Create /F /TN $TaskName /RU SYSTEM /RL HIGHEST /SC MINUTE /MO $RepeatMinutes /TR $taskCmd | Out-Host
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "schtasks /Create вернул код $LASTEXITCODE"
|
||||
}
|
||||
|
||||
Write-Host "Задача создана/обновлена: $TaskName" -ForegroundColor Green
|
||||
|
||||
if ($RunNow) {
|
||||
Write-Host "Запускаю задачу немедленно..." -ForegroundColor Cyan
|
||||
& $schtasksExe /Run /TN $TaskName | Out-Host
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "schtasks /Run вернул код $LASTEXITCODE"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Готово." -ForegroundColor Green
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Andrey Lutsenko (PapaTramp)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+101
-972
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,10 @@
|
||||
# RDP Login Monitor
|
||||
# RDP Login Monitor
|
||||
|
||||
PowerShell-набор для мониторинга входов в Windows с отправкой уведомлений в Telegram.
|
||||
PowerShell-набор для мониторинга входов в Windows с отправкой уведомлений в Telegram:
|
||||
|
||||
## Актуальная схема (рекомендуется)
|
||||
|
||||
- Базовый путь установки: **`C:\ProgramData\RDP-login-monitor\`**.
|
||||
- Основной скрипт: **`Login_Monitor.ps1`** — журнал Security **`4624`/`4625`** (логика зависит от типа ОС: рабочая станция или сервер/КД), при наличии журнала — **Remote Connection Manager `1149`** (часто актуально для РС с RDP), при роли **RD Gateway** — **`302`/`303`**, **ежедневный отчёт** в Telegram (активные сессии через `quser`), **heartbeat**, **ротация логов**, уведомления в Telegram.
|
||||
- Установка задач: запуск **`Login_Monitor.ps1 -InstallTasks`** создаёт:
|
||||
- `RDP-Login-Monitor` (основной монитор),
|
||||
- `RDP-Login-Monitor-Watchdog` (контроль процесса каждые 5 минут).
|
||||
- Доменная доставка и обновления: **`Deploy-LoginMonitor.ps1`** + **`version.txt`** с шары `NETLOGON`. После успешного деплоя в приветственном сообщении Telegram может появиться отметка об обновлении (файл **`deploy_last_update.txt`** рядом с логами).
|
||||
- Для полной инструкции по деплою/GPO используйте **[DEPLOY.md](DEPLOY.md)**.
|
||||
- **`Encrypt-DpapiForRdpMonitor.ps1`** — опционально для подготовки DPAPI-строк токена/chat id.
|
||||
- `Login_Monitor.ps1` — основной монитор (Security `4624/4625`, RD Gateway `302/303`, ротация логов, heartbeat, ежедневный отчет).
|
||||
- `Watchdog_RDP_Monitor.ps1` — watchdog: проверяет, жив ли основной скрипт, и перезапускает его при падении/зависании heartbeat.
|
||||
- `Install-ScheduledTasks.ps1` — создание в Планировщике заданий с теми же именами и параметрами, что в разделе 3 (нужна повышенная PowerShell).
|
||||
|
||||
## Что изменилось (важное)
|
||||
|
||||
@@ -22,109 +15,86 @@ PowerShell-набор для мониторинга входов в Windows с
|
||||
|
||||
## 1) Подготовка
|
||||
|
||||
1. Подготовьте папку установки:
|
||||
- `C:\ProgramData\RDP-login-monitor\`
|
||||
2. Скопируйте в неё как минимум:
|
||||
- `Login_Monitor.ps1`
|
||||
- (для доменного развёртывания отдельно на шаре) `Deploy-LoginMonitor.ps1` и `version.txt`.
|
||||
3. Откройте `Login_Monitor.ps1` и задайте токен/чат:
|
||||
- `$TelegramBotToken` или `...ProtectedB64`
|
||||
- `$TelegramChatID` или `...ProtectedB64`
|
||||
4. Запускайте с правами администратора (чтение `Security` журнала и регистрация задач).
|
||||
5. Логи и служебные файлы будут в:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\`
|
||||
6. (Опционально) Подавление части алертов по списку — см. раздел **«7) ignore.lst»** ниже.
|
||||
1. Скопируйте в `D:\Soft\` как минимум `Login_Monitor.ps1` и `Watchdog_RDP_Monitor.ps1` (для установки из скрипта — ещё `Install-ScheduledTasks.ps1`), либо измените пути в параметрах установщика / в watchdog.
|
||||
2. Откройте `Login_Monitor.ps1` и задайте:
|
||||
- `$TelegramBotToken`
|
||||
- `$TelegramChatID`
|
||||
3. Убедитесь, что существует `D:\Soft\Logs\` (скрипт сам создаст при запуске).
|
||||
4. Запускайте от имени администратора (нужен доступ к журналу `Security`).
|
||||
|
||||
## 2) Ручной запуск
|
||||
|
||||
Используйте этот вариант для быстрой проверки старта/логики без установки задач планировщика.
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\RDP-login-monitor\Login_Monitor.ps1"
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "D:\Soft\Login_Monitor.ps1"
|
||||
```
|
||||
|
||||
Примечание: при ручном запуске монитор работает в текущей сессии до остановки (например, `Ctrl+C`).
|
||||
|
||||
## 3) Запуск через Планировщик заданий (Task Scheduler)
|
||||
|
||||
Текущая схема: вручную задачи в GUI создавать не нужно.
|
||||
Задания можно завести **вручную в GUI** (ниже) или **одной командой из PowerShell** — см. `Install-ScheduledTasks.ps1` (в начале файла стоит `#Requires -RunAsAdministrator`). Скрипт регистрирует **`RDP Login Monitor`** (старт ОС, запуск `Login_Monitor.ps1`) и **`RDP Login Monitor Watchdog`** (старт ОС + повтор каждые 5 минут, запуск `Watchdog_RDP_Monitor.ps1` из того же каталога). Пути по умолчанию: `D:\Soft\`. Параметры: `-InstallRoot`, `-MainTaskName`, `-WatchdogTaskName`, `-WatchdogRepeatMinutes`, задержки случайного старта и т.д.
|
||||
|
||||
Достаточно запустить:
|
||||
Пример после копирования файлов в `D:\Soft\`:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\RDP-login-monitor\Login_Monitor.ps1" -InstallTasks
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "D:\Soft\Install-ScheduledTasks.ps1"
|
||||
```
|
||||
|
||||
Скрипт сам зарегистрирует `RDP-Login-Monitor` и `RDP-Login-Monitor-Watchdog`, а также запросит немедленный первый запуск задач.
|
||||
Это не заменяет настройку токена Telegram в `Login_Monitor.ps1`, а только регистрирует задания.
|
||||
|
||||
Для доменной установки/обновления с шары вручную ничего в планировщике на клиенте настраивать не требуется: используйте `Deploy-LoginMonitor.ps1` (подробно в `DEPLOY.md`).
|
||||
### Задание №1: основной монитор (вручную)
|
||||
|
||||
Создайте задачу `RDP Login Monitor`:
|
||||
|
||||
- **General**
|
||||
- `Run whether user is logged on or not`
|
||||
- `Run with highest privileges`
|
||||
- `Configure for`: ваша версия Windows Server/Windows
|
||||
- **Triggers**
|
||||
- `At startup` (при запуске системы)
|
||||
- **Actions**
|
||||
- Program/script: `powershell.exe`
|
||||
- Add arguments:
|
||||
```text
|
||||
-NoProfile -ExecutionPolicy Bypass -File "D:\Soft\Login_Monitor.ps1"
|
||||
```
|
||||
- **Settings**
|
||||
- `If the task is already running`: `Do not start a new instance`
|
||||
|
||||
### Задание №2: watchdog (вручную)
|
||||
|
||||
Создайте задачу `RDP Login Monitor Watchdog`:
|
||||
|
||||
- **General**
|
||||
- `Run whether user is logged on or not`
|
||||
- `Run with highest privileges`
|
||||
- **Triggers**
|
||||
- `At startup`
|
||||
- Дополнительно: `Repeat task every 5 minutes` (или отдельный триггер по расписанию каждые 5 минут)
|
||||
- **Actions**
|
||||
- Program/script: `powershell.exe`
|
||||
- Add arguments:
|
||||
```text
|
||||
-NoProfile -ExecutionPolicy Bypass -File "D:\Soft\Watchdog_RDP_Monitor.ps1"
|
||||
```
|
||||
- **Settings**
|
||||
- `If the task is already running`: `Do not start a new instance`
|
||||
|
||||
> Если watchdog лежит не в `D:\Soft\`, поправьте путь в аргументах или параметр `-MainScriptPath`.
|
||||
|
||||
## 4) Что проверять после запуска
|
||||
|
||||
- Логи:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\login_monitor.log`
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\watchdog.log`
|
||||
- `D:\Soft\Logs\login_monitor.log`
|
||||
- `D:\Soft\Logs\watchdog.log`
|
||||
- Heartbeat:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\last_heartbeat.txt` обновляется по интервалу **`$HeartbeatInterval`** (по умолчанию раз в час).
|
||||
- Ежедневный отчёт: после первого прохождения дневного слота (по умолчанию **09:00**, задаётся **`$DailyReportHour`** / **`$DailyReportMinute`** в `Login_Monitor.ps1`) в Telegram уходит сводка по **`quser`**; метка последнего отчёта — `Logs\last_daily_report.txt`.
|
||||
- `D:\Soft\Logs\last_heartbeat.txt` обновляется примерно раз в час (по `$HeartbeatInterval`).
|
||||
- Telegram при старте: при установленном **RD Session Host** (или аналогичных компонентах RDS, не только шлюз) — строка про входы по RDP/RDS на этом сервере; при доступном журнале **RD Gateway** — отдельная строка про подключения к **внутренним целевым ПК** через шлюз (302/303). Узел только с ролью RD Gateway не дублирует формулировку «хост сессий».
|
||||
|
||||
## 5) Автоматический перезапуск при падении
|
||||
|
||||
Режим `-Watchdog` внутри `Login_Monitor.ps1` делает:
|
||||
`Watchdog_RDP_Monitor.ps1` делает:
|
||||
|
||||
- проверяет, есть ли процесс `powershell.exe/pwsh.exe` с `Login_Monitor.ps1` в командной строке;
|
||||
- если процесса нет — запускает монитор;
|
||||
- если монитор уже есть — не дублирует экземпляр.
|
||||
|
||||
## 6) Дополнительные параметры и прочие файлы
|
||||
|
||||
- **`-SkipScheduledTaskMaintenance`**: при обычном запуске монитора не выполнять проверку/пересоздание задач планировщика (если регистрацию задач ведёте только через **`-InstallTasks`** или вручную).
|
||||
- **`Install-DeployScheduledTask.ps1`** — helper для периодического запуска **`Deploy-LoginMonitor.ps1`** с шары (см. **[DEPLOY.md](DEPLOY.md)**).
|
||||
- **`Watchdog_RDP_Monitor.ps1`** и **`Install-ScheduledTasks.ps1`** — **альтернативная** схема с отдельным watchdog-файлом и путями по умолчанию **`D:\Soft`**. Для новых установок рекомендуется встроенный режим **`-Watchdog`** в **`Login_Monitor.ps1`** и задачи **`RDP-Login-Monitor`** / **`RDP-Login-Monitor-Watchdog`**.
|
||||
- **`ignore.lst.example`** в репозитории — образец файла **`ignore.lst`** для подавления отдельных уведомлений Security (см. раздел 7).
|
||||
|
||||
## 7) Подавление уведомлений Security: `ignore.lst`
|
||||
|
||||
В каталоге установки можно положить файл **`C:\ProgramData\RDP-login-monitor\ignore.lst`** (рядом с **`Login_Monitor.ps1`**). Правила из списка проверяются **только** для Telegram-уведомлений по событиям **`4624`/`4625`** журнала Security (успех/неудача входа). Жёстко заданные в скрипте исключения (`ExcludedUsers`, локальный IP, сервисные учётные записи и т.д.) по-прежнему действуют для всех типов событий; **`ignore.lst`** добавляет к ним **дополнительные** совпадения именно для **4624/4625**.
|
||||
|
||||
События **RD Gateway (`302`/`303`)**, **RCM `1149`**, ежедневный отчёт и heartbeat **этим файлом не настраиваются** (для `1149` список не используется, даже если формально вызывается общая функция фильтрации).
|
||||
|
||||
### Как читается файл
|
||||
|
||||
- Чтение выполняется по мере обработки событий; содержимое **кэшируется в памяти**. Если **`LastWriteTimeUtc`** файла изменился (редактирование и сохранение), список **перечитывается автоматически** — перезапуск монитора не обязателен.
|
||||
- Кодировка: **UTF-8** (`Get-Content -Encoding UTF8`). Строка может начинаться с BOM — он отбрасывается при разборе.
|
||||
- Пустые строки пропускаются. Строки, начинающиеся с **`#`** или **`;`**, считаются комментариями.
|
||||
- Строка с **`:`**: берётся **первая** двоеточие — всё слева (после обрезки пробелов) определяет тип правила, всё справа — значение. Если справа пусто, строка игнорируется.
|
||||
- Строка **без** **`:`**: целиком трактуется как правило типа «любое совпадение» (см. ниже).
|
||||
|
||||
### Типы правил (левая часть до первого `:`)
|
||||
|
||||
| Левая часть (фрагменты совпадают как regex, без учёта регистра) | Поле события |
|
||||
| --- | --- |
|
||||
| `рабоч`, `workstation`, `wks` | имя рабочей станции (**WorkstationName** и аналоги в XML события) |
|
||||
| `польз`, `username`, `subject`, `account`, `target user`, целое слово `user` | имя пользователя (**TargetUserName** и др.) |
|
||||
| `ip`, `ip адрес`, `ipaddress`, `адрес ip` | IP источника (**IpAddress** и др.), только если в событии есть непустой IP |
|
||||
|
||||
Если левая часть **не** подошла ни к одному типу, но двоеточие есть — используется режим как в разборе строк Telegram: тип **«любое»**, значение — **только правая часть** (метка слева отбрасывается).
|
||||
|
||||
### Совпадение для типа «любое» (строка без `:` или «неизвестная» метка слева от `:`)
|
||||
|
||||
Проверка по очереди:
|
||||
|
||||
1. Если значение похоже на **IPv4** — сравнивается с IP источника в событии (точное совпадение, без учёта регистра для текста не применимо).
|
||||
2. Если значение содержит **`\`** — сравнивается с **учётной записью**: полное совпадение с `DOMAIN\user` **или** совпадение с **SAM** после последнего `\` (как `DOMAIN\IVANOV` при правиле `IVANOV`).
|
||||
3. Иначе сначала полное совпадение с **именем рабочей станции**, затем с **учётной записью** по тем же правилам, что в п.2.
|
||||
|
||||
Для явных типов **User** / **Workstation** / **Ip** используется только соответствующее поле (для пользователя — те же правила полного имени и SAM, что в п.2).
|
||||
|
||||
### Примеры и поставка
|
||||
|
||||
- Расширенные примеры строк — в **`ignore.lst.example`** в корне репозитория (скопируйте на сервер как **`ignore.lst`** и отредактируйте).
|
||||
- **`Deploy-LoginMonitor.ps1`** этот файл **не копирует**: правила обычно разные на каждой машине; при необходимости создайте `ignore.lst` вручную или через вашу систему конфигурации.
|
||||
|
||||
## Ключевые слова (для поиска репозитория)
|
||||
|
||||
`rdp`, `rd-gateway`, `rdp-gateway`, `rds`, `remote-desktop`, `windows-security-log`, `eventlog`, `event-id-4624`, `event-id-4625`, `event-id-302`, `event-id-303`, `powershell`, `telegram-bot`, `watchdog`, `gpo`, `netlogon`, `domain-deployment`, `windows-server`, `monitoring`
|
||||
- если heartbeat старше `$HeartbeatStaleMinutes` (по умолчанию 90 минут) — перезапускает монитор.
|
||||
|
||||
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
# RDP Login Monitor
|
||||
|
||||
PowerShell toolkit for monitoring Windows logons with Telegram notifications.
|
||||
|
||||
## Recommended layout
|
||||
|
||||
- Installation root: **`C:\ProgramData\RDP-login-monitor\`**.
|
||||
- Main script: **`Login_Monitor.ps1`** — Security log **`4624`/`4625`** (behavior depends on OS type: workstation vs server/domain controller), optional **Remote Connection Manager `1149`** when the log is available (often useful for RDP-enabled workstations), **RD Gateway** events **`302`/`303`** when the gateway role/log is present, **daily report** to Telegram (active sessions via `quser`), **heartbeat**, **log rotation**, Telegram alerts.
|
||||
- Scheduled tasks: run **`Login_Monitor.ps1 -InstallTasks`** to register:
|
||||
- `RDP-Login-Monitor` (main monitor),
|
||||
- `RDP-Login-Monitor-Watchdog` (process health check every 5 minutes).
|
||||
- Domain delivery and upgrades: **`Deploy-LoginMonitor.ps1`** + **`version.txt`** on a share such as `NETLOGON`. After a successful deploy, the startup Telegram message may include an update note (file **`deploy_last_update.txt`** next to logs).
|
||||
- Full deploy/GPO guidance: **[DEPLOY.md](DEPLOY.md)**.
|
||||
- **`Encrypt-DpapiForRdpMonitor.ps1`** — optional helper to prepare DPAPI-protected Base64 for the bot token / chat id.
|
||||
|
||||
## Notable behavior
|
||||
|
||||
- **`.ps1` encoding**: `.editorconfig` and `.gitattributes` encourage **`*.ps1`** as **UTF-8 with BOM** and **CRLF**, reducing mojibake and PowerShell parse issues.
|
||||
- **Log encoding**: `login_monitor.log` / `watchdog.log` are written as **UTF-8 with BOM** (BOM is applied to existing files if missing) so viewers like **FAR Manager** do not mis-detect encoding.
|
||||
- **`auditpol` on Russian Windows**: auditing checks use the **`Вход/выход`** category and **`Вход в систему` / `Выход из системы`** subcategories (expect **`Успех и сбой`**), avoiding errors such as `0x00000057` when English names like `Logon` are absent on a localized OS.
|
||||
- **Stability**: `auditpol` is invoked via `cmd.exe` with merged stdout/stderr so `$ErrorActionPreference = 'Stop'` does not abort on stderr-only output.
|
||||
|
||||
## 1) Preparation
|
||||
|
||||
1. Create the install folder:
|
||||
- `C:\ProgramData\RDP-login-monitor\`
|
||||
2. Copy at least:
|
||||
- `Login_Monitor.ps1`
|
||||
- (for domain rollout on a share) `Deploy-LoginMonitor.ps1` and `version.txt`.
|
||||
3. Edit `Login_Monitor.ps1` and set the bot token / chat:
|
||||
- `$TelegramBotToken` or `$TelegramBotTokenProtectedB64`
|
||||
- `$TelegramChatID` or `$TelegramChatIDProtectedB64`
|
||||
4. Run elevated (Security log access and task registration).
|
||||
5. Logs and auxiliary files:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\`
|
||||
6. (Optional) Suppress some Security alerts via **`ignore.lst`** — see **section 7** below.
|
||||
|
||||
## 2) Manual run
|
||||
|
||||
Use this to validate startup/logic without registering scheduled tasks.
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\RDP-login-monitor\Login_Monitor.ps1"
|
||||
```
|
||||
|
||||
The monitor keeps running in the session until you stop it (for example `Ctrl+C`).
|
||||
|
||||
## 3) Task Scheduler
|
||||
|
||||
You do not need to create tasks manually in the GUI.
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\ProgramData\RDP-login-monitor\Login_Monitor.ps1" -InstallTasks
|
||||
```
|
||||
|
||||
The script registers `RDP-Login-Monitor` and `RDP-Login-Monitor-Watchdog`, then triggers an immediate first run.
|
||||
|
||||
For domain deployment from a share you do not configure the scheduler on clients by hand — use `Deploy-LoginMonitor.ps1` (see `DEPLOY.md`).
|
||||
|
||||
## 4) Post-install checks
|
||||
|
||||
- Logs:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\login_monitor.log`
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\watchdog.log`
|
||||
- Heartbeat:
|
||||
- `C:\ProgramData\RDP-login-monitor\Logs\last_heartbeat.txt` updates on **`$HeartbeatInterval`** (hourly by default).
|
||||
- Daily report: after the first daily window (default **09:00**, controlled by **`$DailyReportHour`** / **`$DailyReportMinute`** in `Login_Monitor.ps1`), Telegram receives a `quser` summary; last run marker: `Logs\last_daily_report.txt`.
|
||||
- Startup Telegram message: with **RD Session Host** (or broader RDS session components, not gateway-only) you get the RDS/RDP session-host line; when the **RD Gateway** log is available you get a separate line about connections to **internal targets** through the gateway (302/303). A gateway-only node does not duplicate the “session host” wording.
|
||||
|
||||
## 5) Automatic restart on failure
|
||||
|
||||
`-Watchdog` inside `Login_Monitor.ps1`:
|
||||
|
||||
- looks for `powershell.exe` / `pwsh.exe` whose command line references `Login_Monitor.ps1`;
|
||||
- if the main monitor is missing, starts it;
|
||||
- if the monitor is already running, does not spawn a second instance.
|
||||
|
||||
## 6) Extra parameters and other repo files
|
||||
|
||||
- **`-SkipScheduledTaskMaintenance`**: during normal monitor startup, skip verification/recreation of scheduled tasks (if you manage tasks only via **`-InstallTasks`** or manually).
|
||||
- **`Install-DeployScheduledTask.ps1`** — helper to run **`Deploy-LoginMonitor.ps1`** from a share on a schedule (see **[DEPLOY.md](DEPLOY.md)**).
|
||||
- **`Watchdog_RDP_Monitor.ps1`** and **`Install-ScheduledTasks.ps1`** — **alternate** layout with a separate watchdog script and default paths under **`D:\Soft`**. For new installs, prefer the built-in **`-Watchdog`** in **`Login_Monitor.ps1`** and tasks **`RDP-Login-Monitor`** / **`RDP-Login-Monitor-Watchdog`**.
|
||||
- **`ignore.lst.example`** in the repo is a template for **`ignore.lst`** to suppress selected Security notifications (see section 7).
|
||||
|
||||
## 7) Suppressing Security alerts: `ignore.lst`
|
||||
|
||||
Place a file **`C:\ProgramData\RDP-login-monitor\ignore.lst`** next to **`Login_Monitor.ps1`**. Rules in this list are evaluated **only** for Telegram notifications from Security events **`4624`/`4625`** (successful/failed logons). Built-in exclusions in the script (`ExcludedUsers`, loopback IPs, service-style accounts, etc.) still apply to all event paths; **`ignore.lst`** adds **extra** matches **for 4624/4625 only**.
|
||||
|
||||
**RD Gateway (`302`/`303`)**, **RCM `1149`**, the daily report, and heartbeat **are not controlled** by this file (for `1149` the list is not applied, even though a shared filter function runs).
|
||||
|
||||
### How the file is loaded
|
||||
|
||||
- The file is read during event processing; entries are **cached in memory**. If **`LastWriteTimeUtc`** changes (save after edit), the list is **reloaded automatically** — no monitor restart required.
|
||||
- Encoding: **UTF-8** (`Get-Content -Encoding UTF8`). A UTF-8 BOM at the start of a line is stripped when parsing.
|
||||
- Blank lines are skipped. Lines starting with **`#`** or **`;`** are comments.
|
||||
- If the line contains **`:`**, the **first** colon splits the line: the left part (trimmed) selects the rule kind, the right part is the value. If the right part is empty, the line is ignored.
|
||||
- If there is **no** colon, the whole trimmed line is one “match-any” value (see below).
|
||||
|
||||
### Rule kinds (left part before the first `:`)
|
||||
|
||||
| Left part (case-insensitive regex fragments) | Event field |
|
||||
| --- | --- |
|
||||
| `рабоч`, `workstation`, `wks` | workstation name (**WorkstationName** and similar XML fields) |
|
||||
| `польз`, `username`, `subject`, `account`, `target user`, whole word `user` | user name (**TargetUserName**, etc.) |
|
||||
| `ip`, `ip адрес`, `ipaddress`, `адрес ip` | source IP (**IpAddress**, etc.) when present |
|
||||
|
||||
If the left part matches none of the above but a colon exists, the parser behaves like the Telegram line splitter: kind is **Any**, value is **only the right part** (the label on the left is discarded).
|
||||
|
||||
### “Any” matching (no colon, or unknown label before `:`)
|
||||
|
||||
Evaluated in order:
|
||||
|
||||
1. If the value looks like **IPv4**, it is compared to the event source IP (exact match).
|
||||
2. If the value contains **`\`**, it is matched against the **account**: full `DOMAIN\user` equality **or** SAM after the last `\` (e.g. rule `IVANOV` matches `DOMAIN\IVANOV`).
|
||||
3. Otherwise: exact match against **workstation name** first, then the same account rules as in step 2.
|
||||
|
||||
Explicit **User** / **Workstation** / **Ip** kinds only compare their respective field (user rules use the same full-name and SAM behavior as step 2).
|
||||
|
||||
### Examples and deployment
|
||||
|
||||
- See **`ignore.lst.example`** in the repo; copy it to the server as **`ignore.lst`** and edit.
|
||||
- **`Deploy-LoginMonitor.ps1`** does **not** copy this file — rules are usually host-specific; create **`ignore.lst`** manually or via your configuration tooling.
|
||||
|
||||
## Keywords (for discovery)
|
||||
|
||||
`rdp`, `rd-gateway`, `rdp-gateway`, `rds`, `remote-desktop`, `windows-security-log`, `eventlog`, `event-id-4624`, `event-id-4625`, `event-id-302`, `event-id-303`, `powershell`, `telegram-bot`, `watchdog`, `gpo`, `netlogon`, `domain-deployment`, `windows-server`, `monitoring`
|
||||
@@ -1,24 +0,0 @@
|
||||
# Файл ignore.lst (UTF-8): положите рядом с Login_Monitor.ps1 в каталоге установки:
|
||||
# C:\ProgramData\RDP-login-monitor\ignore.lst
|
||||
#
|
||||
# Каждая непустая строка — одно правило. Строки с # или ; в начале — комментарии.
|
||||
# Подавляет только уведомления Security 4624/4625 (не RD Gateway, не RCM 1149).
|
||||
#
|
||||
# Форматы:
|
||||
# user:domain\user
|
||||
# user:user
|
||||
# workstation:IVANOV
|
||||
# ip:111.222.333.444
|
||||
# Можно вставить «как в Telegram» (берётся значение после первого «:»):
|
||||
# 👤 Пользователь: user
|
||||
# 🖥️ Рабочая станция (клиент из события): IVANOV
|
||||
# 🌐 IP адрес: 111.222.333.444
|
||||
#
|
||||
# Строка без префикса:
|
||||
# IVANOV — совпадение с именем рабочей станции ИЛИ с пользователем (sam) ИЛИ с IP (если строка — IPv4)
|
||||
# 111.222.333.444 — только IP (в реальной конфигурации укажите действительный IPv4 клиента)
|
||||
# domain\user — пользователь целиком
|
||||
|
||||
# user:domain\user
|
||||
# workstation:IVANOV
|
||||
# ip:111.222.333.444
|
||||
@@ -1 +0,0 @@
|
||||
1.4.3
|
||||
Reference in New Issue
Block a user