Загрузка данных
<#
.SYNOPSIS
GUI-скрипт для сбора информации о пользователе RDS и управления сессиями/дисками профилей.
.DESCRIPTION
Скрипт запрашивает у администратора имя пользователя (logon name или фамилию),
выполняет поиск в Active Directory. При наличии нескольких совпадений
отображает окно с чекбоксами для выбора конкретного пользователя.
После выбора собирает и отображает следующую информацию:
- ФИО, Logon Name
- Статус блокировки, отключения, срок действия учётной записи
- Необходимость смены пароля
- Текущие подключения к брокеру RDS (имя коллекции, хост, IP)
- Предыдущие подключения (время, хост, IP, коллекция) из журнала событий
- Информация о диске профиля пользователя (имя файла, коллекция, размер, дата изменения)
Предусмотрена возможность выбрать текущую сессию и разорвать её,
а также выбрать диск профиля и удалить его с помощью внешних скриптов,
расположенных в той же папке.
.NOTES
Требуется PowerShell 3.0+, модули ActiveDirectory и RemoteDesktop.
Запускать от имени администратора домена.
#>
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# ============================================================================
# 1. ФУНКЦИИ
# ============================================================================
function Show-InputBox {
param(
[string]$Title = "Поиск пользователя",
[string]$Prompt = "Введите Logon Name или Фамилию:"
)
$form = New-Object System.Windows.Forms.Form
$form.Text = $Title
$form.Size = New-Object System.Drawing.Size(420, 180)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$label = New-Object System.Windows.Forms.Label
$label.Text = $Prompt
$label.Location = New-Object System.Drawing.Point(10, 15)
$label.Size = New-Object System.Drawing.Size(380, 20)
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10, 40)
$textBox.Size = New-Object System.Drawing.Size(380, 20)
$form.Controls.Add($textBox)
$btnOk = New-Object System.Windows.Forms.Button
$btnOk.Text = "Поиск"
$btnOk.Location = New-Object System.Drawing.Point(100, 80)
$btnOk.Size = New-Object System.Drawing.Size(100, 30)
$btnOk.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Controls.Add($btnOk)
$btnCancel = New-Object System.Windows.Forms.Button
$btnCancel.Text = "Отмена"
$btnCancel.Location = New-Object System.Drawing.Point(220, 80)
$btnCancel.Size = New-Object System.Drawing.Size(100, 30)
$btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.Controls.Add($btnCancel)
$form.AcceptButton = $btnOk
$form.CancelButton = $btnCancel
$form.Topmost = $true
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
return $textBox.Text.Trim()
} else {
return $null
}
}
function Show-UserSelection {
param(
[array]$Users
)
$form = New-Object System.Windows.Forms.Form
$form.Text = "Выбор пользователя"
$form.Size = New-Object System.Drawing.Size(600, 400)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "FixedDialog"
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$label = New-Object System.Windows.Forms.Label
$label.Text = "Найдено несколько пользователей. Выберите нужного (можно несколько, но будет выбран первый отмеченный):"
$label.Location = New-Object System.Drawing.Point(10, 10)
$label.Size = New-Object System.Drawing.Size(560, 20)
$form.Controls.Add($label)
$checkedListBox = New-Object System.Windows.Forms.CheckedListBox
$checkedListBox.Location = New-Object System.Drawing.Point(10, 35)
$checkedListBox.Size = New-Object System.Drawing.Size(560, 280)
$checkedListBox.CheckOnClick = $true
$checkedListBox.SelectionMode = 'One'
foreach ($u in $Users) {
$display = "$($u.Name) | $($u.SamAccountName) | $($u.DistinguishedName)"
[void]$checkedListBox.Items.Add($display)
}
$form.Controls.Add($checkedListBox)
$btnOk = New-Object System.Windows.Forms.Button
$btnOk.Text = "Выбрать"
$btnOk.Location = New-Object System.Drawing.Point(200, 325)
$btnOk.Size = New-Object System.Drawing.Size(100, 30)
$btnOk.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Controls.Add($btnOk)
$btnCancel = New-Object System.Windows.Forms.Button
$btnCancel.Text = "Отмена"
$btnCancel.Location = New-Object System.Drawing.Point(320, 325)
$btnCancel.Size = New-Object System.Drawing.Size(100, 30)
$btnCancel.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.Controls.Add($btnCancel)
$form.AcceptButton = $btnOk
$form.CancelButton = $btnCancel
$form.Topmost = $true
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
$selectedIndex = $checkedListBox.CheckedIndices | Select-Object -First 1
if ($selectedIndex -ne $null) {
return $Users[$selectedIndex]
}
}
return $null
}
function Get-UserInfo {
param(
[string]$SamAccountName
)
$user = Get-ADUser -Identity $SamAccountName -Properties `
DisplayName, LockedOut, Enabled, AccountExpirationDate, `
PasswordExpired, PasswordNeverExpires, CannotChangePassword, `
pwdLastSet, msDS-UserPasswordExpiryTimeComputed, `
Surname, GivenName, SamAccountName, DistinguishedName, SID
return $user
}
function Get-CurrentSessions {
param(
[string]$SamAccountName,
[string]$ConnectionBroker
)
$sessions = Get-RDUserSession -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue |
Where-Object { $_.UserName -like "*$SamAccountName" }
return $sessions
}
function Get-PreviousSessions {
param(
[string]$SamAccountName,
[string]$ConnectionBroker,
[int]$MaxEvents = 20
)
# Получаем предыдущие подключения из журнала RemoteConnectionManager
$logName = 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'
$events = Get-WinEvent -FilterHashtable @{
LogName = $logName
ID = 1149
StartTime = (Get-Date).AddDays(-30)
} -MaxEvents $MaxEvents -ErrorAction SilentlyContinue
$results = @()
foreach ($evt in $events) {
$xml = [xml]$evt.ToXml()
$eventData = $xml.Event.EventData.Data
$user = ($eventData | Where-Object { $_.Name -eq 'User' }).'#text'
$address = ($eventData | Where-Object { $_.Name -eq 'Address' }).'#text'
$hostName = ($eventData | Where-Object { $_.Name -eq 'HostName' }).'#text'
if ($user -like "*$SamAccountName") {
$results += [PSCustomObject]@{
Time = $evt.TimeCreated
Host = $hostName
IP = $address
Collection = "N/A (из журнала)"
}
}
}
return $results
}
function Get-ProfileDiskInfo {
param(
[string]$SamAccountName,
[string]$UPDPath
)
# Получаем SID пользователя
$user = Get-ADUser -Identity $SamAccountName -Properties SID
$sid = $user.SID.Value
$pattern = "UVHD-$sid.vhdx"
$file = Get-ChildItem -Path $UPDPath -Filter $pattern -ErrorAction SilentlyContinue
if ($file) {
return [PSCustomObject]@{
FileName = $file.Name
SizeGB = [math]::Round($file.Length / 1GB, 2)
LastModified = $file.LastWriteTime
Collection = "N/A (из файловой системы)"
FullPath = $file.FullName
}
}
return $null
}
# ============================================================================
# 2. ОСНОВНАЯ ЛОГИКА
# ============================================================================
# Параметры подключения к брокеру и пути к дискам профилей
$ConnectionBroker = "rdcb.contoso.com" # ЗАМЕНИТЕ на FQDN вашего брокера
$UPDPath = "\\fileserver\UPDShare" # ЗАМЕНИТЕ на UNC-путь к папке с дисками профилей
# Запрос имени пользователя
$query = Show-InputBox
if ([string]::IsNullOrWhiteSpace($query)) { exit }
# Поиск в AD
$users = @()
try {
# Пытаемся найти по SamAccountName
$users = Get-ADUser -Filter "SamAccountName -like '$query*'" -Properties DisplayName, Surname, GivenName -ErrorAction SilentlyContinue
if (-not $users) {
# Если не найдено, ищем по фамилии (Surname)
$users = Get-ADUser -Filter "Surname -like '$query*'" -Properties DisplayName, Surname, GivenName -ErrorAction SilentlyContinue
}
} catch {
[System.Windows.Forms.MessageBox]::Show("Ошибка поиска в AD: $_", "Ошибка", "OK", "Error")
exit
}
if ($users.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Пользователь не найден.", "Результат", "OK", "Information")
exit
}
$selectedUser = $null
if ($users.Count -eq 1) {
$selectedUser = $users[0]
} else {
$selectedUser = Show-UserSelection -Users $users
}
if ($null -eq $selectedUser) { exit }
# Сбор информации о пользователе
$userInfo = Get-UserInfo -SamAccountName $selectedUser.SamAccountName
# Текущие сессии
$currentSessions = Get-CurrentSessions -SamAccountName $selectedUser.SamAccountName -ConnectionBroker $ConnectionBroker
# Предыдущие сессии
$previousSessions = Get-PreviousSessions -SamAccountName $selectedUser.SamAccountName -ConnectionBroker $ConnectionBroker
# Диск профиля
$profileDisk = Get-ProfileDiskInfo -SamAccountName $selectedUser.SamAccountName -UPDPath $UPDPath
# ============================================================================
# 3. ФОРМА С РЕЗУЛЬТАТАМИ
# ============================================================================
$form = New-Object System.Windows.Forms.Form
$form.Text = "Информация о пользователе: $($userInfo.Name)"
$form.Size = New-Object System.Drawing.Size(900, 750)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "Sizable"
$form.MaximizeBox = $true
# Панель с основной информацией
$groupBoxUser = New-Object System.Windows.Forms.GroupBox
$groupBoxUser.Text = "Учётная запись"
$groupBoxUser.Location = New-Object System.Drawing.Point(10, 10)
$groupBoxUser.Size = New-Object System.Drawing.Size(860, 120)
$form.Controls.Add($groupBoxUser)
$labelUser = New-Object System.Windows.Forms.Label
$labelUser.Location = New-Object System.Drawing.Point(10, 20)
$labelUser.Size = New-Object System.Drawing.Size(840, 90)
$labelUser.Font = New-Object System.Drawing.Font("Consolas", 9)
$locked = if ($userInfo.LockedOut) { "Да" } else { "Нет" }
$disabled = if (-not $userInfo.Enabled) { "Да" } else { "Нет" }
$pwdExpired = if ($userInfo.PasswordExpired) { "Да" } else { "Нет" }
$accExpires = if ($userInfo.AccountExpirationDate) { $userInfo.AccountExpirationDate } else { "Не установлен" }
$labelUser.Text = @"
ФИО: $($userInfo.DisplayName)
Logon Name: $($userInfo.SamAccountName)
Заблокирована: $locked
Отключена: $disabled
Account expires: $accExpires
Требуется смена пароля: $pwdExpired
"@
$groupBoxUser.Controls.Add($labelUser)
# Текущие подключения
$groupBoxCurrent = New-Object System.Windows.Forms.GroupBox
$groupBoxCurrent.Text = "Текущие подключения к брокеру"
$groupBoxCurrent.Location = New-Object System.Drawing.Point(10, 140)
$groupBoxCurrent.Size = New-Object System.Drawing.Size(860, 150)
$form.Controls.Add($groupBoxCurrent)
$listViewCurrent = New-Object System.Windows.Forms.ListView
$listViewCurrent.Location = New-Object System.Drawing.Point(10, 20)
$listViewCurrent.Size = New-Object System.Drawing.Size(840, 100)
$listViewCurrent.View = "Details"
$listViewCurrent.FullRowSelect = $true
$listViewCurrent.GridLines = $true
$listViewCurrent.Columns.Add("Коллекция", 200) | Out-Null
$listViewCurrent.Columns.Add("Хост", 200) | Out-Null
$listViewCurrent.Columns.Add("IP-адрес", 150) | Out-Null
$listViewCurrent.Columns.Add("Session ID", 100) | Out-Null
$listViewCurrent.Columns.Add("Состояние", 100) | Out-Null
foreach ($s in $currentSessions) {
$item = New-Object System.Windows.Forms.ListViewItem($s.CollectionName)
$item.SubItems.Add($s.HostServer) | Out-Null
$item.SubItems.Add($s.ServerIPAddress) | Out-Null
$item.SubItems.Add($s.UnifiedSessionId) | Out-Null
$item.SubItems.Add($s.SessionState) | Out-Null
$listViewCurrent.Items.Add($item) | Out-Null
}
$groupBoxCurrent.Controls.Add($listViewCurrent)
# Кнопка разрыва сессии
$btnLogoff = New-Object System.Windows.Forms.Button
$btnLogoff.Text = "Разорвать выбранную сессию"
$btnLogoff.Location = New-Object System.Drawing.Point(10, 125)
$btnLogoff.Size = New-Object System.Drawing.Size(200, 25)
$btnLogoff.Add_Click({
if ($listViewCurrent.SelectedItems.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Выберите сессию для разрыва.", "Внимание", "OK", "Warning")
return
}
$sel = $listViewCurrent.SelectedItems[0]
$collection = $sel.Text
$hostServer = $sel.SubItems[1].Text
$sessionId = $sel.SubItems[3].Text
$scriptPath = Join-Path $PSScriptRoot "Logoff-RDSUser.ps1"
if (Test-Path $scriptPath) {
& $scriptPath -BrokerName $ConnectionBroker -CollectionName $collection -UserName "$($userInfo.SamAccountName)"
} else {
[System.Windows.Forms.MessageBox]::Show("Скрипт Logoff-RDSUser.ps1 не найден в папке скрипта.", "Ошибка", "OK", "Error")
}
})
$groupBoxCurrent.Controls.Add($btnLogoff)
# Предыдущие подключения
$groupBoxPrevious = New-Object System.Windows.Forms.GroupBox
$groupBoxPrevious.Text = "Предыдущие подключения (из журнала событий)"
$groupBoxPrevious.Location = New-Object System.Drawing.Point(10, 300)
$groupBoxPrevious.Size = New-Object System.Drawing.Size(860, 150)
$form.Controls.Add($groupBoxPrevious)
$listViewPrevious = New-Object System.Windows.Forms.ListView
$listViewPrevious.Location = New-Object System.Drawing.Point(10, 20)
$listViewPrevious.Size = New-Object System.Drawing.Size(840, 120)
$listViewPrevious.View = "Details"
$listViewPrevious.FullRowSelect = $true
$listViewPrevious.GridLines = $true
$listViewPrevious.Columns.Add("Время", 150) | Out-Null
$listViewPrevious.Columns.Add("Хост", 200) | Out-Null
$listViewPrevious.Columns.Add("IP-адрес", 150) | Out-Null
$listViewPrevious.Columns.Add("Коллекция", 200) | Out-Null
foreach ($p in $previousSessions) {
$item = New-Object System.Windows.Forms.ListViewItem($p.Time.ToString("yyyy-MM-dd HH:mm:ss"))
$item.SubItems.Add($p.Host) | Out-Null
$item.SubItems.Add($p.IP) | Out-Null
$item.SubItems.Add($p.Collection) | Out-Null
$listViewPrevious.Items.Add($item) | Out-Null
}
$groupBoxPrevious.Controls.Add($listViewPrevious)
# Диск профиля
$groupBoxDisk = New-Object System.Windows.Forms.GroupBox
$groupBoxDisk.Text = "Диск профиля пользователя (UPD)"
$groupBoxDisk.Location = New-Object System.Drawing.Point(10, 460)
$groupBoxDisk.Size = New-Object System.Drawing.Size(860, 150)
$form.Controls.Add($groupBoxDisk)
$listViewDisk = New-Object System.Windows.Forms.ListView
$listViewDisk.Location = New-Object System.Drawing.Point(10, 20)
$listViewDisk.Size = New-Object System.Drawing.Size(840, 100)
$listViewDisk.View = "Details"
$listViewDisk.FullRowSelect = $true
$listViewDisk.GridLines = $true
$listViewDisk.Columns.Add("Имя файла", 250) | Out-Null
$listViewDisk.Columns.Add("Коллекция", 200) | Out-Null
$listViewDisk.Columns.Add("Размер (GB)", 100) | Out-Null
$listViewDisk.Columns.Add("Дата изменения", 180) | Out-Null
if ($profileDisk) {
$item = New-Object System.Windows.Forms.ListViewItem($profileDisk.FileName)
$item.SubItems.Add($profileDisk.Collection) | Out-Null
$item.SubItems.Add($profileDisk.SizeGB) | Out-Null
$item.SubItems.Add($profileDisk.LastModified.ToString("yyyy-MM-dd HH:mm:ss")) | Out-Null
$listViewDisk.Items.Add($item) | Out-Null
}
$groupBoxDisk.Controls.Add($listViewDisk)
# Кнопка удаления диска
$btnRemoveDisk = New-Object System.Windows.Forms.Button
$btnRemoveDisk.Text = "Удалить выбранный диск профиля"
$btnRemoveDisk.Location = New-Object System.Drawing.Point(10, 125)
$btnRemoveDisk.Size = New-Object System.Drawing.Size(220, 25)
$btnRemoveDisk.Add_Click({
if ($listViewDisk.SelectedItems.Count -eq 0) {
[System.Windows.Forms.MessageBox]::Show("Выберите диск профиля для удаления.", "Внимание", "OK", "Warning")
return
}
$sel = $listViewDisk.SelectedItems[0]
$collection = $sel.SubItems[1].Text
$scriptPath = Join-Path $PSScriptRoot "Remove-UPD.ps1"
if (Test-Path $scriptPath) {
& $scriptPath -ConnectionBroker $ConnectionBroker -CollectionName $collection -UserName "$($userInfo.SamAccountName)"
} else {
[System.Windows.Forms.MessageBox]::Show("Скрипт Remove-UPD.ps1 не найден в папке скрипта.", "Ошибка", "OK", "Error")
}
})
$groupBoxDisk.Controls.Add($btnRemoveDisk)
# Кнопка закрытия
$btnClose = New-Object System.Windows.Forms.Button
$btnClose.Text = "Закрыть"
$btnClose.Location = New-Object System.Drawing.Point(760, 680)
$btnClose.Size = New-Object System.Drawing.Size(110, 30)
$btnClose.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Controls.Add($btnClose)
$form.AcceptButton = $btnClose
$form.Topmost = $true
$form.ShowDialog() | Out-Null