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:
|
||||||
|
|
||||||
## Актуальная схема (рекомендуется)
|
- `Login_Monitor.ps1` — основной монитор (Security `4624/4625`, RD Gateway `302/303`, ротация логов, heartbeat, ежедневный отчет).
|
||||||
|
- `Watchdog_RDP_Monitor.ps1` — watchdog: проверяет, жив ли основной скрипт, и перезапускает его при падении/зависании heartbeat.
|
||||||
- Базовый путь установки: **`C:\ProgramData\RDP-login-monitor\`**.
|
- `Install-ScheduledTasks.ps1` — создание в Планировщике заданий с теми же именами и параметрами, что в разделе 3 (нужна повышенная PowerShell).
|
||||||
- Основной скрипт: **`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.
|
|
||||||
|
|
||||||
## Что изменилось (важное)
|
## Что изменилось (важное)
|
||||||
|
|
||||||
@@ -22,109 +15,86 @@ PowerShell-набор для мониторинга входов в Windows с
|
|||||||
|
|
||||||
## 1) Подготовка
|
## 1) Подготовка
|
||||||
|
|
||||||
1. Подготовьте папку установки:
|
1. Скопируйте в `D:\Soft\` как минимум `Login_Monitor.ps1` и `Watchdog_RDP_Monitor.ps1` (для установки из скрипта — ещё `Install-ScheduledTasks.ps1`), либо измените пути в параметрах установщика / в watchdog.
|
||||||
- `C:\ProgramData\RDP-login-monitor\`
|
2. Откройте `Login_Monitor.ps1` и задайте:
|
||||||
2. Скопируйте в неё как минимум:
|
- `$TelegramBotToken`
|
||||||
- `Login_Monitor.ps1`
|
- `$TelegramChatID`
|
||||||
- (для доменного развёртывания отдельно на шаре) `Deploy-LoginMonitor.ps1` и `version.txt`.
|
3. Убедитесь, что существует `D:\Soft\Logs\` (скрипт сам создаст при запуске).
|
||||||
3. Откройте `Login_Monitor.ps1` и задайте токен/чат:
|
4. Запускайте от имени администратора (нужен доступ к журналу `Security`).
|
||||||
- `$TelegramBotToken` или `...ProtectedB64`
|
|
||||||
- `$TelegramChatID` или `...ProtectedB64`
|
|
||||||
4. Запускайте с правами администратора (чтение `Security` журнала и регистрация задач).
|
|
||||||
5. Логи и служебные файлы будут в:
|
|
||||||
- `C:\ProgramData\RDP-login-monitor\Logs\`
|
|
||||||
6. (Опционально) Подавление части алертов по списку — см. раздел **«7) ignore.lst»** ниже.
|
|
||||||
|
|
||||||
## 2) Ручной запуск
|
## 2) Ручной запуск
|
||||||
|
|
||||||
Используйте этот вариант для быстрой проверки старта/логики без установки задач планировщика.
|
|
||||||
|
|
||||||
```powershell
|
```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)
|
## 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
|
||||||
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) Что проверять после запуска
|
## 4) Что проверять после запуска
|
||||||
|
|
||||||
- Логи:
|
- Логи:
|
||||||
- `C:\ProgramData\RDP-login-monitor\Logs\login_monitor.log`
|
- `D:\Soft\Logs\login_monitor.log`
|
||||||
- `C:\ProgramData\RDP-login-monitor\Logs\watchdog.log`
|
- `D:\Soft\Logs\watchdog.log`
|
||||||
- Heartbeat:
|
- Heartbeat:
|
||||||
- `C:\ProgramData\RDP-login-monitor\Logs\last_heartbeat.txt` обновляется по интервалу **`$HeartbeatInterval`** (по умолчанию раз в час).
|
- `D:\Soft\Logs\last_heartbeat.txt` обновляется примерно раз в час (по `$HeartbeatInterval`).
|
||||||
- Ежедневный отчёт: после первого прохождения дневного слота (по умолчанию **09:00**, задаётся **`$DailyReportHour`** / **`$DailyReportMinute`** в `Login_Monitor.ps1`) в Telegram уходит сводка по **`quser`**; метка последнего отчёта — `Logs\last_daily_report.txt`.
|
|
||||||
- Telegram при старте: при установленном **RD Session Host** (или аналогичных компонентах RDS, не только шлюз) — строка про входы по RDP/RDS на этом сервере; при доступном журнале **RD Gateway** — отдельная строка про подключения к **внутренним целевым ПК** через шлюз (302/303). Узел только с ролью RD Gateway не дублирует формулировку «хост сессий».
|
- Telegram при старте: при установленном **RD Session Host** (или аналогичных компонентах RDS, не только шлюз) — строка про входы по RDP/RDS на этом сервере; при доступном журнале **RD Gateway** — отдельная строка про подключения к **внутренним целевым ПК** через шлюз (302/303). Узел только с ролью RD Gateway не дублирует формулировку «хост сессий».
|
||||||
|
|
||||||
## 5) Автоматический перезапуск при падении
|
## 5) Автоматический перезапуск при падении
|
||||||
|
|
||||||
Режим `-Watchdog` внутри `Login_Monitor.ps1` делает:
|
`Watchdog_RDP_Monitor.ps1` делает:
|
||||||
|
|
||||||
- проверяет, есть ли процесс `powershell.exe/pwsh.exe` с `Login_Monitor.ps1` в командной строке;
|
- проверяет, есть ли процесс `powershell.exe/pwsh.exe` с `Login_Monitor.ps1` в командной строке;
|
||||||
- если процесса нет — запускает монитор;
|
- если процесса нет — запускает монитор;
|
||||||
- если монитор уже есть — не дублирует экземпляр.
|
- если heartbeat старше `$HeartbeatStaleMinutes` (по умолчанию 90 минут) — перезапускает монитор.
|
||||||
|
|
||||||
## 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`
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
-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