Загрузка данных


<#
.SYNOPSIS
    GUI-скрипт для сбора информации о пользователе RDS и управления сессиями/дисками профилей.
.DESCRIPTION
    Запрашивает имя пользователя, ищет в AD, собирает сведения об учётной записи,
    текущих и предыдущих сессиях (с брокера), дисках UPD.
    Позволяет разорвать сессию и удалить диск через внешние скрипты.
.NOTES
    Требуется PowerShell 3.0+, модули ActiveDirectory и RemoteDesktop.
    Запускать от имени администратора домена.
    Учётная запись должна иметь права на чтение журнала SessionBroker на брокере.
#>

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

Import-Module RemoteDesktop -ErrorAction SilentlyContinue
Import-Module ActiveDirectory -ErrorAction SilentlyContinue

# ============================================================================
# 1. ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
# ============================================================================

function Write-Diag {
    param([string]$Message, [string]$Level = "Info")
    $ts = Get-Date -Format "HH:mm:ss"
    switch ($Level) {
        "Warn" { Write-Warning "[$ts] $Message" }
        "Err"  { Write-Host "[$ts] [ОШИБКА] $Message" -ForegroundColor Red }
        default { Write-Host "[$ts] $Message" -ForegroundColor Cyan }
    }
}

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)
    return Get-ADUser -Identity $SamAccountName -Properties `
        DisplayName, LockedOut, Enabled, AccountExpirationDate, `
        PasswordExpired, PasswordNeverExpires, CannotChangePassword, `
        pwdLastSet, msDS-UserPasswordExpiryTimeComputed, `
        Surname, GivenName, SamAccountName, DistinguishedName, SID
}

function Get-CurrentSessions {
    param([string]$SamAccountName, [string]$ConnectionBroker)
    return @(Get-RDUserSession -ConnectionBroker $ConnectionBroker -ErrorAction SilentlyContinue |
        Where-Object { $_.UserName -like "*$SamAccountName" })
}

# ---------------------------------------------------------------------------
# Извлечение поля из XML события: сначала по @Name, затем по local-name
# ---------------------------------------------------------------------------
function Get-EventField {
    param(
        [xml]$Xml,
        [string[]]$Names,
        [int[]]$Positions
    )
    # 1) По атрибуту Name у Data (EventData)
    foreach ($name in $Names) {
        try {
            $node = $Xml.SelectSingleNode("//*[local-name()='Data'][@Name='$name']")
            if ($node -and $node.InnerText) { return $node.InnerText }
        } catch { }
    }
    # 2) По local-name (UserData/EventXML и др.)
    foreach ($name in $Names) {
        try {
            $node = $Xml.SelectSingleNode("//*[local-name()='$name']")
            if ($node -and $node.InnerText) { return $node.InnerText }
        } catch { }
    }
    # 3) По позиции среди Data
    if ($Positions) {
        try {
            $nodes = $Xml.SelectNodes("//*[local-name()='Data']")
            foreach ($pos in $Positions) {
                if ($nodes.Count -ge $pos) {
                    $val = $nodes[$pos - 1].InnerText
                    if ($val) { return $val }
                }
            }
        } catch { }
    }
    return $null
}

function Test-UserMatch {
    param([string]$EventUser, [string]$SamAccountName)
    if ([string]::IsNullOrWhiteSpace($EventUser)) { return $false }
    $normalized = $EventUser
    if ($normalized -like "*\*") { $normalized = $normalized.Split('\')[-1] }
    return ($normalized -ieq $SamAccountName)
}

# ---------------------------------------------------------------------------
# Получение всех событий из журнала брокера с несколькими fallback-методами
# ---------------------------------------------------------------------------
function Get-BrokerEvents {
    param(
        [string]$ConnectionBroker,
        [int]$MaxEvents = 1000
    )

    $logName = 'Microsoft-Windows-TerminalServices-SessionBroker/Operational'
    $evts = @()

    # --- Метод 1: -LogName + простой -FilterXPath без QueryList ---
    try {
        Write-Diag "Попытка 1: Get-WinEvent -LogName '$logName' -FilterXPath '*[System[EventID=801]]'"
        $evts = @(Get-WinEvent -ComputerName $ConnectionBroker -LogName $logName `
                    -FilterXPath '*[System[EventID=801]]' `
                    -MaxEvents $MaxEvents -ErrorAction Stop)
        Write-Diag "Успех. Получено событий: $($evts.Count)"
        return $evts
    } catch {
        Write-Diag "Не удалось: $($_.Exception.Message)" "Warn"
        $evts = @()
    }

    # --- Метод 2: FilterHashtable ---
    try {
        Write-Diag "Попытка 2: Get-WinEvent -FilterHashtable @{LogName=...; ID=801}"
        $evts = @(Get-WinEvent -ComputerName $ConnectionBroker `
                    -FilterHashtable @{ LogName = $logName; ID = 801 } `
                    -MaxEvents $MaxEvents -ErrorAction Stop)
        Write-Diag "Успех. Получено событий: $($evts.Count)"
        return $evts
    } catch {
        Write-Diag "Не удалось: $($_.Exception.Message)" "Warn"
        $evts = @()
    }

    # --- Метод 3: получить все события журнала и отфильтровать в PS ---
    try {
        Write-Diag "Попытка 3: получить все события журнала, фильтрация в PowerShell"
        $evts = @(Get-WinEvent -ComputerName $ConnectionBroker -LogName $logName `
                    -MaxEvents $MaxEvents -ErrorAction Stop |
                 Where-Object { $_.Id -eq 801 })
        Write-Diag "Успех. Получено событий: $($evts.Count)"
        return $evts
    } catch {
        Write-Diag "Не удалось: $($_.Exception.Message)" "Err"
    }

    # --- Метод 4: получить ВСЕ события без фильтра (крайний случай) ---
    try {
        Write-Diag "Попытка 4: получить все события без фильтра по ID"
        $all = @(Get-WinEvent -ComputerName $ConnectionBroker -LogName $logName `
                    -MaxEvents $MaxEvents -ErrorAction Stop)
        Write-Diag "Всего событий в журнале: $($all.Count). Уникальные ID: $((($all | Select-Object -ExpandProperty Id -Unique) -join ', '))"
        return $all
    } catch {
        Write-Diag "Все методы получения событий провалились: $($_.Exception.Message)" "Err"
    }
    return @()
}

# ---------------------------------------------------------------------------
# Предыдущие сессии — сбор событий с брокера + фильтрация в PowerShell
# ---------------------------------------------------------------------------
function Get-PreviousSessions {
    param(
        [string]$SamAccountName,
        [string]$ConnectionBroker,
        [int]$MaxEvents = 1000,
        [int]$DaysBack = 30
    )

    $results = @()
    $startTime = (Get-Date).AddDays(-$DaysBack)
    $dnsCache = @{}

    $evts = Get-BrokerEvents -ConnectionBroker $ConnectionBroker -MaxEvents $MaxEvents

    if ($evts.Count -eq 0) {
        Write-Diag "Событий с брокера не получено." "Warn"
        return $results
    }

    # Отбираем события нужного ID и в пределах периода
    $filtered = @($evts | Where-Object {
        ($_.Id -eq 801) -and ($_.TimeCreated -ge $startTime)
    })
    Write-Diag "Событий ID=801 за последние $DaysBack дн.: $($filtered.Count)"

    # Диагностика: покажем имена полей первого события, чтобы понять структуру XML
    if ($filtered.Count -gt 0) {
        try {
            $sampleXml = [xml]$filtered[0].ToXml()
            $fieldNames = @($sampleXml.SelectNodes("//*[local-name()='Data']") | ForEach-Object { $_.Name }) -join ', '
            if (-not $fieldNames) {
                $fieldNames = @($sampleXml.SelectNodes("//*[local-name()='EventXML']/*") | ForEach-Object { $_.LocalName }) -join ', '
            }
            Write-Diag "Поля первого события ID=801: $fieldNames"
        } catch {
            Write-Diag "Не удалось разобрать XML образца: $($_.Exception.Message)" "Warn"
        }
    }

    foreach ($evt in $filtered) {
        try {
            $xml = [xml]$evt.ToXml()

            $user = Get-EventField -Xml $xml `
                -Names @('User','UserName','Param1','AccountName','TargetUser') `
                -Positions @(1)
            $targetName = Get-EventField -Xml $xml `
                -Names @('TargetName','HostName','ServerName','Param2') `
                -Positions @(2)
            $targetIP = Get-EventField -Xml $xml `
                -Names @('TargetIP','Address','IPAddress','IP','Param3') `
                -Positions @(3)
            $targetFQDN = Get-EventField -Xml $xml `
                -Names @('TargetFQDN','FQDN','Param5','TargetNetbios') `
                -Positions @(5)

            if (Test-UserMatch -EventUser $user -SamAccountName $SamAccountName) {
                $hostName = $targetFQDN
                if ([string]::IsNullOrWhiteSpace($hostName) -and $targetIP -and $targetIP -ne "LOCAL") {
                    if ($dnsCache.ContainsKey($targetIP)) {
                        $hostName = $dnsCache[$targetIP]
                    } else {
                        try {
                            $hostName = [System.Net.Dns]::GetHostEntry($targetIP).HostName
                            $dnsCache[$targetIP] = $hostName
                        } catch { $hostName = "N/A" }
                    }
                }
                if ([string]::IsNullOrWhiteSpace($hostName)) { $hostName = $targetName }

                $results += [PSCustomObject]@{
                    Time       = $evt.TimeCreated
                    Host       = if ($hostName) { $hostName } else { "N/A" }
                    IP         = if ($targetIP) { $targetIP } else { "N/A" }
                    Collection = "N/A (из журнала брокера)"
                }
            }
        } catch {
            # Пропускаем повреждённые события, не прерывая цикл
        }
    }

    Write-Diag "Совпадений по пользователю '$SamAccountName': $($results.Count)"
    return @($results | Sort-Object Time -Descending)
}

# ---------------------------------------------------------------------------
# Соответствие "Коллекция -> путь к UPD"
# ---------------------------------------------------------------------------
function Get-CollectionUPDMapping {
    param([string]$ConnectionBroker)

    $mapping = @{}

    $collections = @()
    try {
        $collections = @(Get-RDSessionCollection -ConnectionBroker $ConnectionBroker -ErrorAction Stop)
    } catch {
        Write-Diag "Не удалось получить коллекции с брокера $ConnectionBroker : $($_.Exception.Message)" "Err"
        return $mapping
    }

    Write-Diag "Найдено коллекций: $($collections.Count)"

    foreach ($coll in $collections) {
        $collName = $coll.CollectionName
        try {
            # ВАЖНО: параметр -UserProfileDisk обязателен для получения настроек UPD
            $config = Get-RDSessionCollectionConfiguration -ConnectionBroker $ConnectionBroker `
                -CollectionName $collName -UserProfileDisk -ErrorAction Stop

            $updPath = $null
            foreach ($propName in @('DiskPath','UserProfileDiskPath','UserProfileDisk','ProfileDiskPath')) {
                if ($config.PSObject.Properties[$propName]) {
                    $val = $config.$propName
                    if ($val -and $val -is [string] -and $val.Trim().Length -gt 0) {
                        $updPath = $val.Trim()
                        break
                    }
                }
            }

            if ($updPath) {
                $updPath = $updPath.TrimEnd('\')
                $mapping[$collName] = $updPath
                Write-Diag "Коллекция '$collName' -> UPD: $updPath"
            } else {
                Write-Diag "Коллекция '$collName' — UPD не настроен (DiskPath пуст)" "Warn"
            }
        } catch {
            Write-Diag "Не удалось получить конфигурацию UPD коллекции '$collName': $($_.Exception.Message)" "Warn"
        }
    }
    return $mapping
}

# ---------------------------------------------------------------------------
# Поиск дисков профиля (UPD) пользователя
# ---------------------------------------------------------------------------
function Get-ProfileDiskInfo {
    param(
        [string]$SamAccountName,
        [hashtable]$CollectionUPDMapping
    )

    $user = Get-ADUser -Identity $SamAccountName -Properties SID
    $sid = $user.SID.Value
    Write-Diag "SID пользователя: $sid"

    $disks = @()

    if ($CollectionUPDMapping.Count -eq 0) {
        Write-Diag "Нет коллекций с настроенным UPD — поиск дисков пропущен" "Warn"
        return $disks
    }

    foreach ($collName in $CollectionUPDMapping.Keys) {
        $updPath = $CollectionUPDMapping[$collName]

        if (-not (Test-Path -Path $updPath -ErrorAction SilentlyContinue)) {
            Write-Diag "Путь UPD недоступен: $updPath (коллекция '$collName')" "Warn"
            continue
        }

        $fileName = "UVHD-$sid.vhdx"
        $files = @(Get-ChildItem -Path $updPath -Filter $fileName -ErrorAction SilentlyContinue)

        if ($files.Count -eq 0) {
            $files = @(Get-ChildItem -Path $updPath -Filter "*$sid*.vhdx" -ErrorAction SilentlyContinue)
        }

        foreach ($file in $files) {
            if ($file.Name -like "*template*") { continue }
            $disks += [PSCustomObject]@{
                FileName     = $file.Name
                Collection   = $collName
                SizeGB       = [math]::Round($file.Length / 1GB, 2)
                LastModified = $file.LastWriteTime
                FullPath     = $file.FullName
            }
            Write-Diag "Найден диск: $($file.Name) в коллекции '$collName'"
        }

        if ($files.Count -eq 0) {
            Write-Diag "Файл '$fileName' не найден в $updPath" "Warn"
        }
    }
    return $disks
}

# ============================================================================
# 2. ОСНОВНАЯ ЛОГИКА
# ============================================================================

$ConnectionBroker = "rdcb.contoso.com"   # ЗАМЕНИТЕ на FQDN вашего брокера

$query = Show-InputBox
if ([string]::IsNullOrWhiteSpace($query)) { exit }

$users = @()
try {
    $users = @(Get-ADUser -Filter "SamAccountName -like '$query*'" `
                -Properties DisplayName, Surname, GivenName -ErrorAction SilentlyContinue)
    if ($users.Count -eq 0) {
        $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]
    Write-Diag "Найден один пользователь — выбран автоматически: $($selectedUser.SamAccountName)"
} 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
$collectionUPDMap  = Get-CollectionUPDMapping -ConnectionBroker $ConnectionBroker
$profileDisks      = Get-ProfileDiskInfo -SamAccountName $selectedUser.SamAccountName -CollectionUPDMapping $collectionUPDMap

Write-Diag "Текущих сессий: $($currentSessions.Count); предыдущих: $($previousSessions.Count); дисков: $($profileDisks.Count)"

# ============================================================================
# 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
    $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 = "Предыдущие подключения (журнал брокера SessionBroker/Operational, EventID 801)"
$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("Хост", 220) | 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

foreach ($disk in $profileDisks) {
    $item = New-Object System.Windows.Forms.ListViewItem($disk.FileName)
    $item.SubItems.Add($disk.Collection) | Out-Null
    $item.SubItems.Add($disk.SizeGB) | Out-Null
    $item.SubItems.Add($disk.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