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


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

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 }
    }
}

# --- INI ---

function Get-IniPath {
    return (Join-Path $PSScriptRoot "settings.ini")
}

function Read-IniBroker {
    $path = Get-IniPath
    if (-not (Test-Path $path)) { return $null }
    try {
        foreach ($line in Get-Content -Path $path -Encoding UTF8) {
            if ($line -match '^\s*Broker\s*=\s*(.+?)\s*$') {
                return $Matches[1]
            }
        }
    } catch {
        Write-Diag "Не удалось прочитать $path : $($_.Exception.Message)" "Warn"
    }
    return $null
}

function Save-IniBroker {
    param([string]$Broker)
    $path = Get-IniPath
    try {
        $content = @(
            "; Настройки RDS User Info",
            "; Автоматически созданный файл. Можно править вручную.",
            "",
            "[Settings]",
            "Broker=$Broker"
        )
        Set-Content -Path $path -Value $content -Encoding UTF8
        Write-Diag "Broker сохранён в $path"
    } catch {
        Write-Diag "Не удалось записать $path : $($_.Exception.Message)" "Err"
    }
}

# --- Диалоги ---

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()
    }
    return $null
}

function Show-BrokerDialog {
    param(
        [string]$CurrentBroker = "",
        [string]$Title = "Настройка брокера",
        [string]$Prompt = "Введите FQDN брокера подключений:"
    )
    $form = New-Object System.Windows.Forms.Form
    $form.Text = $Title
    $form.Size = New-Object System.Drawing.Size(440, 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(400, 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(400, 20)
    $textBox.Text = $CurrentBroker
    $form.Controls.Add($textBox)

    $btnOk = New-Object System.Windows.Forms.Button
    $btnOk.Text = "Сохранить"
    $btnOk.Location = New-Object System.Drawing.Point(110, 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(230, 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) {
        $v = $textBox.Text.Trim()
        if ($v.Length -gt 0) { return $v }
    }
    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
}

# ============================================================================
# 2. СБОР ДАННЫХ
# ============================================================================

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" })
}

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)
}

function Get-PreviousSessions {
    param(
        [string]$SamAccountName,
        [string]$ConnectionBroker,
        [int]$MaxEvents = 500,
        [int]$DaysBack = 30
    )

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

    $RDPAuths = @()
    try {
        $RDPAuths = Get-WinEvent -ComputerName $ConnectionBroker `
            -LogName 'Microsoft-Windows-TerminalServices-SessionBroker/Operational' `
            -FilterXPath '<QueryList><Query Id="0"><Select>*[System[EventID=801]]</Select></Query></QueryList>' `
            -MaxEvents $MaxEvents -ErrorAction Stop
    } catch {
        Write-Diag "Не удалось прочитать журнал SessionBroker на $ConnectionBroker : $($_.Exception.Message)" "Err"
        return $results
    }

    if (-not $RDPAuths -or $RDPAuths.Count -eq 0) {
        Write-Diag "SessionBroker/801: события не найдены" "Warn"
        return $results
    }

    Write-Diag "SessionBroker/801: получено событий с брокера — $($RDPAuths.Count)"

    [xml[]]$xml = $RDPAuths | ForEach-Object { $_.ToXml() }
    $EventData = foreach ($event in $xml.Event) {
        New-Object PSObject -Property @{
            TimeCreated = (Get-Date ($event.System.TimeCreated.SystemTime) -Format 'yyyy-MM-dd HH:mm:ss')
            User        = $event.UserData.EventXML.Param1
            Domain      = $event.UserData.EventXML.Param2
            Client      = $event.UserData.EventXML.Param3
        }
    }

    $filtered = @($EventData | Where-Object { Test-UserMatch -EventUser $_.User -SamAccountName $SamAccountName })
    Write-Diag "SessionBroker/801: событий для '$SamAccountName' — $($filtered.Count)"

    foreach ($item in $filtered) {
        $clientIP = $item.Client
        $hostName = "N/A"
        if ($clientIP -and $clientIP -ne "LOCAL") {
            if ($dnsCache.ContainsKey($clientIP)) {
                $hostName = $dnsCache[$clientIP]
            } else {
                try {
                    $hostName = [System.Net.Dns]::GetHostEntry($clientIP).HostName
                    $dnsCache[$clientIP] = $hostName
                } catch { $hostName = "N/A" }
            }
        }
        $results += [PSCustomObject]@{
            Time = [datetime]::Parse($item.TimeCreated)
            Host = $hostName
            IP   = if ($clientIP) { $clientIP } else { "N/A" }
        }
    }

    return @($results | Sort-Object Time -Descending)
}

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 {
            $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
}

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
}

# ============================================================================
# 3. ФОРМА С РЕЗУЛЬТАТАМИ
# ============================================================================

function Show-ResultsForm {
    param(
        [object]$UserInfo,
        [array]$CurrentSessions,
        [array]$PreviousSessions,
        [array]$ProfileDisks,
        [string]$Broker,
        [string]$DomainNetBIOS
    )

    # Сохраняем данные в скрипт-скоуп, чтобы обработчики кнопок имели к ним доступ
    $script:FormBroker       = $Broker
    $script:FormUserInfo     = $UserInfo
    $script:FormDomain       = $DomainNetBIOS
    $script:FormResult       = "Close"

    $form = New-Object System.Windows.Forms.Form
    $form.Text = "Информация о пользователе: $($UserInfo.DisplayName)  |  Брокер: $Broker"
    $form.Size = New-Object System.Drawing.Size(920, 800)
    $form.StartPosition = "CenterScreen"
    $form.FormBorderStyle = "Sizable"
    $form.MaximizeBox = $true
    $script:FormWindow = $form

    # --- Учётная запись ---
    $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(880, 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(860, 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(880, 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(860, 95)
    $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)
    $script:FormLvCurrent = $listViewCurrent

    $btnLogoff = New-Object System.Windows.Forms.Button
    $btnLogoff.Text = "Разорвать выбранную сессию"
    $btnLogoff.Location = New-Object System.Drawing.Point(10, 122)
    $btnLogoff.Size = New-Object System.Drawing.Size(220, 25)
    $btnLogoff.Add_Click({
        if ($script:FormLvCurrent.SelectedItems.Count -eq 0) {
            [System.Windows.Forms.MessageBox]::Show("Выберите сессию для разрыва.", "Внимание", "OK", "Warning")
            return
        }
        $sel = $script:FormLvCurrent.SelectedItems[0]
        $collection = $sel.Text
        $scriptPath = Join-Path $PSScriptRoot "Logoff-RDSUser.ps1"
        if (Test-Path $scriptPath) {
            $userArg = "$script:FormDomain\$($script:FormUserInfo.SamAccountName)"
            & $scriptPath -BrokerName $script:FormBroker -CollectionName $collection -UserName $userArg
        } 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(880, 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(860, 120)
    $listViewPrevious.View = "Details"
    $listViewPrevious.FullRowSelect = $true
    $listViewPrevious.GridLines = $true
    $listViewPrevious.Columns.Add("Время", 180) | Out-Null
    $listViewPrevious.Columns.Add("Хост", 380) | Out-Null
    $listViewPrevious.Columns.Add("IP-адрес", 280) | 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
        $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(880, 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(860, 95)
    $listViewDisk.View = "Details"
    $listViewDisk.FullRowSelect = $true
    $listViewDisk.GridLines = $true
    $listViewDisk.Columns.Add("Имя файла", 300) | Out-Null
    $listViewDisk.Columns.Add("Коллекция", 200) | Out-Null
    $listViewDisk.Columns.Add("Размер (GB)", 110) | Out-Null
    $listViewDisk.Columns.Add("Дата изменения", 220) | 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)
    $script:FormLvDisk = $listViewDisk

    $btnRemoveDisk = New-Object System.Windows.Forms.Button
    $btnRemoveDisk.Text = "Удалить выбранный диск профиля"
    $btnRemoveDisk.Location = New-Object System.Drawing.Point(10, 122)
    $btnRemoveDisk.Size = New-Object System.Drawing.Size(240, 25)
    $btnRemoveDisk.Add_Click({
        if ($script:FormLvDisk.SelectedItems.Count -eq 0) {
            [System.Windows.Forms.MessageBox]::Show("Выберите диск профиля для удаления.", "Внимание", "OK", "Warning")
            return
        }
        $sel = $script:FormLvDisk.SelectedItems[0]
        $collection = $sel.SubItems[1].Text
        $scriptPath = Join-Path $PSScriptRoot "Remove-UPD.ps1"
        if (Test-Path $scriptPath) {
            $userArg = "$script:FormDomain\$($script:FormUserInfo.SamAccountName)"
            & $scriptPath -ConnectionBroker $script:FormBroker -CollectionName $collection -UserName $userArg
        } else {
            [System.Windows.Forms.MessageBox]::Show("Скрипт Remove-UPD.ps1 не найден в папке скрипта.", "Ошибка", "OK", "Error")
        }
    })
    $groupBoxDisk.Controls.Add($btnRemoveDisk)

    # --- Нижняя панель кнопок ---
    $btnEditBroker = New-Object System.Windows.Forms.Button
    $btnEditBroker.Text = "Изменить брокера"
    $btnEditBroker.Location = New-Object System.Drawing.Point(10, 625)
    $btnEditBroker.Size = New-Object System.Drawing.Size(180, 32)
    $btnEditBroker.Add_Click({
        $script:FormResult = "EditBroker"
        $script:FormWindow.Close()
    })
    $form.Controls.Add($btnEditBroker)

    $btnRefresh = New-Object System.Windows.Forms.Button
    $btnRefresh.Text = "Обновить"
    $btnRefresh.Location = New-Object System.Drawing.Point(200, 625)
    $btnRefresh.Size = New-Object System.Drawing.Size(130, 32)
    $btnRefresh.Add_Click({
        $script:FormResult = "Refresh"
        $script:FormWindow.Close()
    })
    $form.Controls.Add($btnRefresh)

    $btnNewQuery = New-Object System.Windows.Forms.Button
    $btnNewQuery.Text = "Новый запрос"
    $btnNewQuery.Location = New-Object System.Drawing.Point(340, 625)
    $btnNewQuery.Size = New-Object System.Drawing.Size(150, 32)
    $btnNewQuery.Add_Click({
        $script:FormResult = "NewQuery"
        $script:FormWindow.Close()
    })
    $form.Controls.Add($btnNewQuery)

    $btnClose = New-Object System.Windows.Forms.Button
    $btnClose.Text = "Закрыть"
    $btnClose.Location = New-Object System.Drawing.Point(760, 625)
    $btnClose.Size = New-Object System.Drawing.Size(130, 32)
    $btnClose.Add_Click({
        $script:FormResult = "Close"
        $script:FormWindow.Close()
    })
    $form.Controls.Add($btnClose)

    $form.Topmost = $true
    $form.ShowDialog() | Out-Null

    return $script:FormResult
}

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

# Загружаем имя брокера из INI или запрашиваем при первом запуске
$script:CurrentBroker = Read-IniBroker
if (-not $script:CurrentBroker) {
    Write-Diag "Имя брокера не найдено в settings.ini — запрашиваем у пользователя"
    $script:CurrentBroker = Show-BrokerDialog -CurrentBroker "rdcb.contoso.com" `
        -Title "Первичная настройка" -Prompt "Введите FQDN брокера подключений:"
    if (-not $script:CurrentBroker) { exit }
    Save-IniBroker $script:CurrentBroker
} else {
    Write-Diag "Загружен брокер из settings.ini: $script:CurrentBroker"
}

# NetBIOS-имя домена (используется для формирования DOMAIN\user)
$script:DomainNetBIOS = ""
try {
    $script:DomainNetBIOS = (Get-ADDomain).NetBIOSName
} catch {
    Write-Diag "Не удалось получить NetBIOS-имя домена: $($_.Exception.Message)" "Warn"
}

$action = "NewQuery"
$currentUser = $null

while ($true) {
    switch ($action) {
        "NewQuery" {
            $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")
                $action = "NewQuery"
                continue
            }

            if ($users.Count -eq 1) {
                $currentUser = $users[0]
                Write-Diag "Найден один пользователь — выбран автоматически: $($currentUser.SamAccountName)"
            } else {
                $currentUser = Show-UserSelection -Users $users
            }

            if ($null -eq $currentUser) {
                $action = "NewQuery"
                continue
            }
        }
        "EditBroker" {
            $newBroker = Show-BrokerDialog -CurrentBroker $script:CurrentBroker `
                -Title "Изменение брокера" -Prompt "Введите FQDN брокера подключений:"
            if ($newBroker) {
                $script:CurrentBroker = $newBroker
                Save-IniBroker $script:CurrentBroker
            }
            $action = "Refresh"
            continue
        }
    }

    if ($null -eq $currentUser) { exit }

    # Сбор данных по текущему пользователю
    try {
        $userInfo = Get-UserInfo -SamAccountName $currentUser.SamAccountName
    } catch {
        [System.Windows.Forms.MessageBox]::Show("Не удалось получить данные из AD: $_", "Ошибка", "OK", "Error")
        $action = "NewQuery"
        continue
    }

    $currentSessions  = Get-CurrentSessions -SamAccountName $currentUser.SamAccountName -ConnectionBroker $script:CurrentBroker
    $previousSessions = Get-PreviousSessions -SamAccountName $currentUser.SamAccountName -ConnectionBroker $script:CurrentBroker
    $collectionUPDMap = Get-CollectionUPDMapping -ConnectionBroker $script:CurrentBroker
    $profileDisks     = Get-ProfileDiskInfo -SamAccountName $currentUser.SamAccountName -CollectionUPDMapping $collectionUPDMap

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

    $action = Show-ResultsForm -UserInfo $userInfo `
        -CurrentSessions $currentSessions `
        -PreviousSessions $previousSessions `
        -ProfileDisks $profileDisks `
        -Broker $script:CurrentBroker `
        -DomainNetBIOS $script:DomainNetBIOS

    if ($action -eq "Close") { break }
    # для "Refresh" и "EditBroker" — цикл повторится без запроса пользователя
    # для "NewQuery" — цикл спросит нового пользователя
}