Public/Show-WindowsDeviceLink.ps1

function Show-WindowsDeviceLink {
    <#
    .SYNOPSIS
    Opens the WindowsDeviceLink operator dashboard.
 
    .DESCRIPTION
    Opens a compact Windows 11 Settings-inspired WinForms dashboard on Windows 11 and supported Windows PE environments.
 
    The GUI focuses on inspecting Device Association state, onboarding, and offboarding.
    Lifecycle actions delegate to existing WindowsDeviceLink public cmdlets.
 
    Interactive authentication is the default on full Windows. Windows PE defaults to DeviceCode because Interactive browser authentication is unavailable there. Use -Method and the corresponding authentication parameters to select another supported authentication flow.
 
    Use -Tenants to provide friendly tenant names for the tenant selector:
 
    @{'Tenant Alpha'='11111111-1111-1111-1111-111111111111'; 'Tenant Beta'='22222222-2222-2222-2222-222222222222'}
 
    Use -TenantsUri to load the same friendly-name-to-tenant-ID mapping from a trusted HTTPS JSON endpoint.
    Use -TenantsPath to load the same JSON format from a local file.
    Precedence is: TenantsUri, then TenantsPath, then explicit -Tenants values.
 
    Backend mode retrieves its authoritative tenant catalog from the Function App.
    The dashboard presents device, local-association, and cloud-association state in
    one view. Pre-associate performs the complete guarded assignment workflow; diagnostic,
    export, recovery, and offboarding actions remain available below it.
 
    .EXAMPLE
    Show-WindowsDeviceLink
 
    .EXAMPLE
    Show-WindowsDeviceLink -Method DeviceCode
 
    .EXAMPLE
    Show-WindowsDeviceLink -Tenants @{
        'Tenant Alpha' = '11111111-1111-1111-1111-111111111111'
        'Tenant Beta' = '22222222-2222-2222-2222-222222222222'
    }
 
    .EXAMPLE
    Show-WindowsDeviceLink -TenantsUri 'https://config.example.com/windowsdevicelink/tenants.json'
 
    .EXAMPLE
    Show-WindowsDeviceLink -TenantsPath 'E:\Config\tenants.json'
    #>

    [CmdletBinding()]
    param(
        [ValidateSet(
            'DeviceCode','Interactive','ClientSecret','AccessToken','Certificate',
            'CertificateThumbprint','CertificateSubjectName','EnvironmentVariable','ManagedIdentity'
        )]
        [string]$Method,

        [ValidateNotNullOrEmpty()]
        [string]$TenantId,

        [hashtable]$Tenants,

        [ValidateNotNull()]
        [uri]$TenantsUri,

        [ValidateNotNullOrEmpty()]
        [string]$TenantsPath,

        [ValidateNotNullOrEmpty()]
        [string]$ClientId,

        [securestring]$AccessToken,

        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,

        [ValidateNotNullOrEmpty()]
        [string]$CertificateThumbprint,

        [ValidateNotNullOrEmpty()]
        [string]$CertificateSubjectName,

        [bool]$SendCertificateChain = $false,

        [securestring]$ClientSecret,

        [ValidateNotNullOrEmpty()]
        [string]$Environment = 'Global',

        [ValidateRange(1,600)]
        [double]$ClientTimeout = 100,

        [ValidateNotNullOrEmpty()]
        [string]$WindowsManagementServicePath

        ,[ValidateNotNull()]
        [uri]$BackendUri

        ,[ValidateNotNull()]
        [securestring]$BackendApiKey

        ,[ValidateSet('Simple','Advanced')]
        [string]$ViewMode = 'Simple'

        ,[ValidateRange(5,600)]
        [int]$TimeoutSeconds = 120
    )

    $outerBoundParameters = @{}
    foreach ($key in $PSBoundParameters.Keys) {
        $outerBoundParameters[$key] = $PSBoundParameters[$key]
    }

    $isWinPE = Test-Path -LiteralPath 'HKLM:\SYSTEM\CurrentControlSet\Control\MiniNT'
    $backendMode = $outerBoundParameters.ContainsKey('BackendUri') -or $outerBoundParameters.ContainsKey('BackendApiKey')
    if ($backendMode -and (-not $outerBoundParameters.ContainsKey('BackendUri') -or -not $outerBoundParameters.ContainsKey('BackendApiKey'))) {
        throw '-BackendUri and -BackendApiKey must be supplied together.'
    }
    if ($backendMode -and ($Tenants -or $outerBoundParameters.ContainsKey('TenantsUri') -or $outerBoundParameters.ContainsKey('TenantsPath'))) {
        throw 'Backend mode obtains its tenant catalog from the Function App; do not combine it with -Tenants, -TenantsUri, or -TenantsPath.'
    }
    if (-not $backendMode -and $outerBoundParameters.ContainsKey('TenantId') -and ($Tenants -or $outerBoundParameters.ContainsKey('TenantsUri') -or $outerBoundParameters.ContainsKey('TenantsPath'))) {
        throw 'Direct mode uses either one explicit -TenantId or a tenant catalog; do not combine them.'
    }

    if ($backendMode -and $outerBoundParameters.ContainsKey('Method')) {
        throw 'Backend mode performs Graph operations through the Function App; do not combine it with -Method.'
    }
    if (-not $backendMode -and -not $outerBoundParameters.ContainsKey('Method')) {
        $Method = if ($isWinPE) { 'DeviceCode' } else { 'Interactive' }
    }

    if ($isWinPE -and $Method -eq 'Interactive') {
        throw 'Interactive authentication is not available in Windows PE. Use -Method DeviceCode or a supported app-only authentication method.'
    }
    $usesInteractiveUserAuthentication = -not $backendMode -and $Method -in @('Interactive','DeviceCode')

    Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop
    Add-Type -AssemblyName System.Drawing -ErrorAction Stop
    [System.Windows.Forms.Application]::EnableVisualStyles()

    $loadedModule = Get-Module WindowsDeviceLink | Select-Object -First 1
    $baseVersion = if ($loadedModule) { $loadedModule.Version.ToString() } else { 'unknown' }
    $prerelease = $null
    if ($loadedModule -and $loadedModule.PrivateData -and $loadedModule.PrivateData.PSData) {
        $prerelease = [string]$loadedModule.PrivateData.PSData.Prerelease
    }
    $displayVersion = if ([string]::IsNullOrWhiteSpace($prerelease)) { $baseVersion } else { "$baseVersion-$prerelease" }

    $workingArea = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea
    $targetWidth = [Math]::Max(820,[Math]::Min(1080,$workingArea.Width - 32))
    $targetHeight = [Math]::Max(620,[Math]::Min(760,$workingArea.Height - 40))

    $form = New-Object System.Windows.Forms.Form
    $form.Text = "WindowsDeviceLink $displayVersion (Preview)"
    $form.StartPosition = 'CenterScreen'
    $form.Size = [System.Drawing.Size]::new($targetWidth,$targetHeight)
    $form.MinimumSize = [System.Drawing.Size]::new(820,620)
    $form.AutoScaleMode = [System.Windows.Forms.AutoScaleMode]::Dpi
    $form.BackColor = [System.Drawing.Color]::FromArgb(243,243,243)

    $formIcon = $null
    if ($loadedModule -and -not [string]::IsNullOrWhiteSpace([string]$loadedModule.ModuleBase)) {
        $iconPath = Join-Path ([string]$loadedModule.ModuleBase) 'Assets\WindowsDeviceLink.ico'
        if (Test-Path -LiteralPath $iconPath) {
            try {
                $formIcon = [System.Drawing.Icon]::new($iconPath)
                $form.Icon = $formIcon
            }
            catch {
                # An icon is cosmetic and must never block the operator workflow.
                $formIcon = $null
            }
        }
    }

    $content = New-Object System.Windows.Forms.Panel
    $content.Dock = [System.Windows.Forms.DockStyle]::Fill
    $content.AutoScroll = $false
    $content.BackColor = $form.BackColor
    $form.Controls.Add($content)

    $toolTip = New-Object System.Windows.Forms.ToolTip
    $toolTip.AutoPopDelay = 10000
    $toolTip.InitialDelay = 400
    $toolTip.ReshowDelay = 200
    $toolTip.ShowAlways = $true

    $colorCardAccent = [System.Drawing.Color]::FromArgb(86,94,102)
    $colorCardTint = [System.Drawing.Color]::FromArgb(248,249,250)

    function New-GuiFont {
        param(
            [string]$Family,
            [float]$Size = 9,
            [System.Drawing.FontStyle]$Style = [System.Drawing.FontStyle]::Regular
        )

        $resolvedFamily = if (-not [string]::IsNullOrWhiteSpace($Family)) {
            $Family
        }
        elseif ($Style -eq [System.Drawing.FontStyle]::Regular) {
            'Tahoma'
        }
        else {
            'Segoe UI'
        }

        return [System.Drawing.Font]::new(
            $resolvedFamily,$Size,$Style,[System.Drawing.GraphicsUnit]::Point
        )
    }


    function New-Card {
        param(
            [string]$Title,
            [int]$X,
            [int]$Y,
            [int]$Width,
            [int]$Height
        )

        $panel = New-Object System.Windows.Forms.Panel
        $panel.Location = [System.Drawing.Point]::new($X,$Y)
        $panel.Size = [System.Drawing.Size]::new($Width,$Height)
        $panel.BackColor = [System.Drawing.Color]::White
        $panel.BorderStyle = [System.Windows.Forms.BorderStyle]::None
        $content.Controls.Add($panel)

        if ($Title) {
            $label = New-Object System.Windows.Forms.Label
            $label.Text = $Title
            $label.Font = New-GuiFont -Size 10 -Style Bold
            $label.Location = [System.Drawing.Point]::new(14,9)
            $label.AutoSize = $true
            $panel.Controls.Add($label)
        }

        $panel
    }

    function New-ValuePair {
        param(
            [System.Windows.Forms.Control]$Parent,
            [string]$Caption,
            [int]$Y,
            [int]$CaptionWidth = 112,
            [int]$X = 14,
            [int]$ValueWidth = 350
        )

        $captionLabel = New-Object System.Windows.Forms.Label
        $captionLabel.Text = $Caption
        $captionLabel.Font = New-GuiFont -Size 8.5 -Style Regular
        $captionLabel.ForeColor = [System.Drawing.Color]::FromArgb(102,102,102)
        $captionLabel.Location = [System.Drawing.Point]::new($X,$Y)
        $captionLabel.Size = [System.Drawing.Size]::new($CaptionWidth,20)
        $captionLabel.Tag = "Caption:$Caption"
        $Parent.Controls.Add($captionLabel)

        $valueLabel = New-Object System.Windows.Forms.Label
        $valueLabel.Text = '-'
        $valueLabel.Font = New-GuiFont -Size 9 -Style Bold
        $valueLabel.Location = [System.Drawing.Point]::new(($X + $CaptionWidth + 6),($Y - 1))
        $valueLabel.Size = [System.Drawing.Size]::new($ValueWidth,22)
        $valueLabel.AutoEllipsis = $true
        $valueLabel.Tag = "Value:$Caption"
        $Parent.Controls.Add($valueLabel)

        $valueLabel
    }

    function New-ActionRow {
        param(
            [System.Windows.Forms.Control]$Parent,
            [string]$Title,
            [string]$Description,
            [int]$Y,
            [string[]]$Buttons
        )

        $row = New-Object System.Windows.Forms.Panel
        $row.Location = [System.Drawing.Point]::new(0,$Y)
        $row.Size = [System.Drawing.Size]::new(900,46)
        $row.BackColor = [System.Drawing.Color]::White
        $Parent.Controls.Add($row)

        $titleLabel = New-Object System.Windows.Forms.Label
        $titleLabel.Text = $Title
        $titleLabel.Font = New-GuiFont -Size 8.3 -Style Bold
        $titleLabel.Location = [System.Drawing.Point]::new(14,5)
        $titleLabel.AutoSize = $true
        $row.Controls.Add($titleLabel)

        $descriptionLabel = New-Object System.Windows.Forms.Label
        $descriptionLabel.Text = $Description
        $descriptionLabel.Font = New-GuiFont -Size 8.2 -Style Regular
        $descriptionLabel.ForeColor = [System.Drawing.Color]::FromArgb(108,108,108)
        $descriptionLabel.Location = [System.Drawing.Point]::new(14,23)
        $descriptionLabel.AutoSize = $true
        $row.Controls.Add($descriptionLabel)

        $buttonList = New-Object System.Collections.Generic.List[object]
        $count = $Buttons.Count
        $buttonWidth = if ($count -eq 1) { 118 } elseif ($count -eq 2) { 112 } else { 88 }
        $gap = 7
        $right = 884

        for ($i = $count - 1; $i -ge 0; $i--) {
            $button = New-Object System.Windows.Forms.Button
            $button.Text = $Buttons[$i]
            $button.Font = New-GuiFont -Size 8.6 -Style Regular
            $button.Size = [System.Drawing.Size]::new($buttonWidth,28)
            $right -= $buttonWidth
            $button.Location = [System.Drawing.Point]::new($right,9)
            $button.FlatStyle = [System.Windows.Forms.FlatStyle]::Standard
            $button.UseVisualStyleBackColor = $true
            $row.Controls.Add($button)
            $buttonList.Insert(0,$button)
            $right -= $gap
        }

        $separator = New-Object System.Windows.Forms.Panel
        $separator.BackColor = [System.Drawing.Color]::FromArgb(232,232,232)
        $separator.Location = [System.Drawing.Point]::new(14,45)
        $separator.Size = [System.Drawing.Size]::new(860,1)
        $row.Controls.Add($separator)

        [pscustomobject]@{
            Panel = $row
            Buttons = $buttonList.ToArray()
            Description = $descriptionLabel
        }
    }

    $effectiveTenants = @{}

    if ($backendMode) {
        $backendTenants = @(Get-WindowsDeviceLinkBackendTenant -BackendUri $BackendUri -BackendApiKey $BackendApiKey)
        if ($backendTenants.Count -eq 0) { throw 'The Function backend returned no allowed tenants.' }
        foreach ($tenant in $backendTenants) {
            $effectiveTenants[[string]$tenant.Name] = [string]$tenant.TenantId
        }
    }

    if ($outerBoundParameters.ContainsKey('TenantsUri')) {
        foreach ($tenant in @(Get-WindowsDeviceLinkTenantCatalog -Uri $TenantsUri)) {
            $effectiveTenants[[string]$tenant.Name] = [string]$tenant.TenantId
        }
    }

    if ($outerBoundParameters.ContainsKey('TenantsPath')) {
        foreach ($tenant in @(Get-WindowsDeviceLinkTenantCatalog -Path $TenantsPath)) {
            $effectiveTenants[[string]$tenant.Name] = [string]$tenant.TenantId
        }
    }

    if ($Tenants) {
        foreach ($tenant in @(Get-WindowsDeviceLinkTenantCatalog -TenantMap $Tenants)) {
            $effectiveTenants[[string]$tenant.Name] = [string]$tenant.TenantId
        }
    }

    $tenantChoiceLookup = @{}
    $tenantChoices = New-Object System.Collections.Generic.List[string]
    $hasDirectTenantCatalog = -not $backendMode -and $effectiveTenants.Count -gt 0
    $autoLabel = if ($backendMode -or $hasDirectTenantCatalog) { 'Select target tenant...' } elseif ($outerBoundParameters.ContainsKey('TenantId')) { 'Explicit tenant parameter' } else { 'Tenant determined by sign-in' }
    $tenantChoiceLookup[$autoLabel] = $null
    $tenantChoices.Add($autoLabel)

    if ($effectiveTenants.Count -gt 0) {
        foreach ($name in @($effectiveTenants.Keys | Sort-Object)) {
            $id = [string]$effectiveTenants[$name]
            $label = [string]$name
            if ($tenantChoiceLookup.ContainsKey($label)) {
                $label = "$label ($id)"
            }
            $tenantChoiceLookup[$label] = $id
            $tenantChoices.Add($label)
        }
    }

    # A true Direct single-tenant workflow has no choice to present. The
    # authenticated sign-in context (or explicit TenantId) remains authoritative.
    $showTenantSelector = $backendMode -or $effectiveTenants.Count -gt 0

    function Get-SelectedTenantId {
        if ($tenantSelector.SelectedItem) {
            $selected = [string]$tenantSelector.SelectedItem
            if ($tenantChoiceLookup.ContainsKey($selected) -and $tenantChoiceLookup[$selected]) {
                return [string]$tenantChoiceLookup[$selected]
            }
        }

        if ($outerBoundParameters.ContainsKey('TenantId')) {
            return [string]$TenantId
        }

        $null
    }

    function Get-TenantDisplayName {
        param([string]$TenantId)

        if ([string]::IsNullOrWhiteSpace($TenantId)) { return 'Unavailable' }
        foreach ($name in $effectiveTenants.Keys) {
            if ([string]$effectiveTenants[$name] -ieq $TenantId) {
                return [string]$name
            }
        }

        return $TenantId
    }

    function Get-GuiAuthParameters {
        $selectedTenant = Get-SelectedTenantId

        if ($hasDirectTenantCatalog -and [string]::IsNullOrWhiteSpace($selectedTenant)) {
            throw 'Select a target tenant before signing in or performing a cloud action.'
        }

        if (-not $backendMode -and $Method -eq 'DeviceCode') {
            $cacheTenantMatches = -not $selectedTenant -or
                ([string]$script:WdlGuiSessionTenantId -ieq [string]$selectedTenant)
            $cacheValid = $script:WdlGuiSessionAccessToken -and
                $script:WdlGuiSessionExpiresUtc -and
                ([DateTime]::UtcNow -lt $script:WdlGuiSessionExpiresUtc) -and
                $cacheTenantMatches

            if (-not $cacheValid) {
                Clear-GuiSessionAuthentication
                Write-GuiConsole -Message 'Authenticating once for this Direct-mode UI session.' -Command

                $tokenParameters = @{}
                if ($selectedTenant) { $tokenParameters.TenantId = $selectedTenant }
                if ($outerBoundParameters.ContainsKey('ClientId')) { $tokenParameters.ClientId = $ClientId }
                $tokenResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                    Get-WindowsDeviceLinkDeviceCodeToken @tokenParameters
                })
                $token = $tokenResults | Select-Object -Last 1
                if (-not $token -or [string]::IsNullOrWhiteSpace([string]$token.AccessToken)) {
                    throw 'Device code authentication did not return an access token.'
                }

                $script:WdlGuiSessionAccessToken = ConvertTo-SecureString ([string]$token.AccessToken) -AsPlainText -Force
                $script:WdlGuiSessionTenantId = [string]$token.TenantId
                $expiresIn = if ($token.ExpiresIn) { [int]$token.ExpiresIn } else { 3600 }
                $script:WdlGuiSessionExpiresUtc = [DateTime]::UtcNow.AddSeconds([Math]::Max(60,$expiresIn - 120))
                $script:WdlGuiSessionAuthenticated = $true
                $token = $null
                $ui.Authentication.Text = "$Method - Signed in"
                if ($btnSignIn) { $btnSignIn.Text = 'Switch account' }
                Write-GuiConsole -Message 'Direct-mode session authenticated; the in-memory token will be reused for cloud actions.'
            }

            return @{
                Method = 'AccessToken'
                AccessToken = $script:WdlGuiSessionAccessToken
                TenantId = $script:WdlGuiSessionTenantId
                Environment = $Environment
                ClientTimeout = $ClientTimeout
            }
        }

        $parameters = @{
            Method = $Method
            Environment = $Environment
            ClientTimeout = $ClientTimeout
        }

        foreach ($name in @(
            'ClientId','AccessToken','Certificate','CertificateThumbprint',
            'CertificateSubjectName','SendCertificateChain','ClientSecret'
        )) {
            if ($outerBoundParameters.ContainsKey($name)) {
                $parameters[$name] = $outerBoundParameters[$name]
            }
        }

        if ($selectedTenant) {
            $parameters.TenantId = $selectedTenant
        }

        $parameters
    }

    function Clear-GuiSessionAuthentication {
        if ($script:WdlGuiSessionAccessToken -is [System.IDisposable]) {
            try { $script:WdlGuiSessionAccessToken.Dispose() } catch {}
        }
        $script:WdlGuiSessionAccessToken = $null
        $script:WdlGuiSessionTenantId = $null
        $script:WdlGuiSessionExpiresUtc = $null
        $script:WdlGuiSessionAuthenticated = $false
        if ($ui -and $ui.Authentication -and -not $backendMode) {
            $ui.Authentication.Text = if ($usesInteractiveUserAuthentication) { "$Method - Signed out" } else { "$Method - Non-interactive" }
        }
        if ($btnSignIn) { $btnSignIn.Text = 'Sign in' }
    }

    function Get-GuiBackendParameters {
        if (-not $backendMode) { throw 'This action requires -BackendUri and -BackendApiKey.' }
        @{ BackendUri=$BackendUri; BackendApiKey=$BackendApiKey; TimeoutSeconds=$TimeoutSeconds }
    }


    function Get-GuiRuntimeParameters {
        $parameters = @{}
        if ($outerBoundParameters.ContainsKey('WindowsManagementServicePath')) {
            $parameters.WindowsManagementServicePath = $WindowsManagementServicePath
        }
        $parameters
    }

    function Get-GuiOperationParameters {
        $parameters = Get-GuiRuntimeParameters
        $parameters.TimeoutSeconds = $TimeoutSeconds
        $parameters
    }

    $ui = @{}
    $script:WdlGuiBusy = $false
    $script:WdlGuiLocalAssociation = $null
    $script:WdlGuiCloudStatus = $null
    $script:WdlGuiSupport = $null
    $script:WdlGuiSessionAccessToken = $null
    $script:WdlGuiSessionTenantId = $null
    $script:WdlGuiSessionExpiresUtc = $null
    $script:WdlGuiSessionAuthenticated = $false

    $deviceCard = New-Card -Title 'Device' -X 14 -Y 12 -Width 508 -Height 126
    $connectionCard = New-Card -Title 'Connection' -X 536 -Y 12 -Width 508 -Height 126
    $associationCard = New-Card -Title 'Local association' -X 14 -Y 150 -Width 508 -Height 126
    $cloudCard = New-Card -Title 'Cloud association' -X 536 -Y 150 -Width 508 -Height 126
    $deviceCard.BackColor = $colorCardTint
    $connectionCard.BackColor = $colorCardTint
    $associationCard.BackColor = $colorCardTint
    $cloudCard.BackColor = $colorCardTint

    $deviceAccent = New-Object System.Windows.Forms.Panel
    $deviceAccent.BackColor = $colorCardAccent
    $deviceAccent.Location = [System.Drawing.Point]::new(0,0)
    $deviceAccent.Size = [System.Drawing.Size]::new(4,126)
    $deviceCard.Controls.Add($deviceAccent)

    $connectionAccent = New-Object System.Windows.Forms.Panel
    $connectionAccent.BackColor = $colorCardAccent
    $connectionAccent.Location = [System.Drawing.Point]::new(0,0)
    $connectionAccent.Size = [System.Drawing.Size]::new(4,126)
    $connectionCard.Controls.Add($connectionAccent)

    $localAccent = New-Object System.Windows.Forms.Panel
    $localAccent.BackColor = $colorCardAccent
    $localAccent.Location = [System.Drawing.Point]::new(0,0)
    $localAccent.Size = [System.Drawing.Size]::new(4,126)
    $associationCard.Controls.Add($localAccent)

    $ui.CloudAccent = New-Object System.Windows.Forms.Panel
    $ui.CloudAccent.BackColor = $colorCardAccent
    $ui.CloudAccent.Location = [System.Drawing.Point]::new(0,0)
    $ui.CloudAccent.Size = [System.Drawing.Size]::new(4,126)
    $cloudCard.Controls.Add($ui.CloudAccent)

    $ui.Manufacturer = New-ValuePair -Parent $deviceCard -Caption 'Manufacturer' -Y 34 -CaptionWidth 105 -ValueWidth 335
    $ui.Model        = New-ValuePair -Parent $deviceCard -Caption 'Model' -Y 56 -CaptionWidth 105 -ValueWidth 335
    $ui.Serial       = New-ValuePair -Parent $deviceCard -Caption 'Serial number' -Y 78 -CaptionWidth 105 -ValueWidth 335
    $ui.OperatingSystem = New-ValuePair -Parent $deviceCard -Caption 'Operating system' -Y 100 -CaptionWidth 105 -ValueWidth 335

    $ui.ConnectionMode = New-ValuePair -Parent $connectionCard -Caption 'Mode' -Y 34 -CaptionWidth 105 -ValueWidth 335
    $ui.Authentication = New-ValuePair -Parent $connectionCard -Caption 'Authentication' -Y 56 -CaptionWidth 105 -ValueWidth 335
    $ui.Endpoint       = New-ValuePair -Parent $connectionCard -Caption 'Endpoint' -Y 78 -CaptionWidth 105 -ValueWidth 335
    $ui.TenantScope    = New-ValuePair -Parent $connectionCard -Caption 'Tenant scope' -Y 100 -CaptionWidth 105 -ValueWidth 335

    $ui.LocalState = New-ValuePair -Parent $associationCard -Caption 'State' -Y 34 -CaptionWidth 105 -ValueWidth 335
    $ui.LocalState.Text = 'Not checked'
    $ui.Firmware   = New-ValuePair -Parent $associationCard -Caption 'Firmware' -Y 56 -CaptionWidth 105 -ValueWidth 335
    $ui.Firmware.Text = 'Not checked'
    $ui.LinkId     = New-ValuePair -Parent $associationCard -Caption 'Link ID' -Y 78 -CaptionWidth 105 -ValueWidth 335
    $ui.LinkId.Text = 'Not checked'
    $ui.LocalCreated = New-ValuePair -Parent $associationCard -Caption 'Created' -Y 100 -CaptionWidth 105 -ValueWidth 335
    $ui.LocalCreated.Text = 'Not checked'

    $ui.CloudState  = New-ValuePair -Parent $cloudCard -Caption 'State' -Y 34 -CaptionWidth 105 -ValueWidth 335
    $ui.CloudState.Text = 'Not checked'
    $ui.CloudTenant = New-ValuePair -Parent $cloudCard -Caption 'Tenant' -Y 56 -CaptionWidth 105 -ValueWidth 335
    $ui.CloudTenant.Text = 'Not checked'
    $ui.CloudId = New-ValuePair -Parent $cloudCard -Caption 'Association ID' -Y 78 -CaptionWidth 105 -ValueWidth 335
    $ui.CloudId.Text = 'Not checked'
    $ui.CloudChecked = New-ValuePair -Parent $cloudCard -Caption 'Last checked' -Y 100 -CaptionWidth 105 -ValueWidth 335
    $ui.CloudChecked.Text = 'Not checked'

    $actionsTitle = New-Object System.Windows.Forms.Label
    $actionsTitle.Text = 'Actions'
    $actionsTitle.Font = New-GuiFont -Size 11.5 -Style Bold
    $actionsTitle.Location = [System.Drawing.Point]::new(16,150)
    $actionsTitle.AutoSize = $true
    $content.Controls.Add($actionsTitle)

    $actionsPanel = New-Card -Title '' -X 14 -Y 176 -Width 1030 -Height 196

    $assignmentRow = New-Object System.Windows.Forms.Panel
    $assignmentRow.Location = [System.Drawing.Point]::new(0,0)
    $assignmentRow.Size = [System.Drawing.Size]::new(1030,46)
    $assignmentRow.BackColor = [System.Drawing.Color]::White
    $actionsPanel.Controls.Add($assignmentRow)

    $assignmentTitle = New-Object System.Windows.Forms.Label
    $assignmentTitle.Text = 'Device association'
    $assignmentTitle.Font = New-GuiFont -Size 8.5 -Style Bold
    $assignmentTitle.Location = [System.Drawing.Point]::new(14,5)
    $assignmentTitle.AutoSize = $true
    $assignmentRow.Controls.Add($assignmentTitle)

    $assignmentDescription = New-Object System.Windows.Forms.Label
    $assignmentDescription.Text = 'Select the destination and choose Pre-associate or Associate.'
    $assignmentDescription.Font = New-GuiFont -Size 8.2 -Style Regular
    $assignmentDescription.ForeColor = [System.Drawing.Color]::FromArgb(108,108,108)
    $assignmentDescription.Location = [System.Drawing.Point]::new(14,23)
    $assignmentDescription.AutoSize = $true
    $assignmentRow.Controls.Add($assignmentDescription)

    $tenantCaption = New-Object System.Windows.Forms.Label
    $tenantCaption.Text = 'Target tenant'
    $tenantCaption.Font = New-GuiFont -Size 8.5 -Style Regular
    $tenantCaption.ForeColor = [System.Drawing.Color]::FromArgb(102,102,102)
    $tenantCaption.Location = [System.Drawing.Point]::new(500,14)
    $tenantCaption.Size = [System.Drawing.Size]::new(88,18)
    $assignmentRow.Controls.Add($tenantCaption)

    $tenantSelector = New-Object System.Windows.Forms.ComboBox
    $tenantSelector.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
    $tenantSelector.Font = New-GuiFont -Size 8.5 -Style Regular
    $tenantSelector.Location = [System.Drawing.Point]::new(588,9)
    $tenantSelector.ItemHeight = 22
    $tenantSelector.Size = [System.Drawing.Size]::new(220,28)
    foreach ($choice in $tenantChoices) {
        [void]$tenantSelector.Items.Add($choice)
    }
    $tenantSelector.SelectedIndex = 0
    $tenantCaption.Visible = $showTenantSelector
    $tenantSelector.Visible = $showTenantSelector
    if (-not $showTenantSelector) {
        $assignmentDescription.Text = if ($outerBoundParameters.ContainsKey('TenantId')) {
            'The destination tenant is fixed by the supplied tenant ID. Choose Pre-associate or Associate.'
        }
        else {
            'The destination tenant is determined by sign-in. Choose Pre-associate or Associate.'
        }
    }
    $assignmentRow.Controls.Add($tenantSelector)

    $btnAssign = New-Object System.Windows.Forms.Button
    $btnAssign.Text = 'Pre-associate'
    $btnAssign.Font = New-GuiFont -Size 8.6 -Style Regular
    $btnAssign.Size = [System.Drawing.Size]::new(94,28)
    $btnAssign.Location = [System.Drawing.Point]::new(816,9)
    $btnAssign.FlatStyle = [System.Windows.Forms.FlatStyle]::Standard
    $btnAssign.UseVisualStyleBackColor = $true
    $assignmentRow.Controls.Add($btnAssign)

    # Host the Associate button in an enabled panel. WinForms does not
    # display ToolTips for disabled controls, while the host can still receive
    # hover messages when Associate is capability-disabled in Windows PE.
    $btnAssociateHost = New-Object System.Windows.Forms.Panel
    $btnAssociateHost.Size = [System.Drawing.Size]::new(96,28)
    $btnAssociateHost.Location = [System.Drawing.Point]::new(918,9)
    $btnAssociateHost.BackColor = [System.Drawing.Color]::Transparent
    $assignmentRow.Controls.Add($btnAssociateHost)

    $btnAssociate = New-Object System.Windows.Forms.Button
    $btnAssociate.Text = 'Associate'
    $btnAssociate.Font = New-GuiFont -Size 8.6 -Style Regular
    $btnAssociate.Size = [System.Drawing.Size]::new(96,28)
    $btnAssociate.Location = [System.Drawing.Point]::new(0,0)
    $btnAssociate.FlatStyle = [System.Windows.Forms.FlatStyle]::Standard
    $btnAssociate.UseVisualStyleBackColor = $true
    $btnAssociateHost.Controls.Add($btnAssociate)

    $assignmentSeparator = New-Object System.Windows.Forms.Panel
    $assignmentSeparator.BackColor = [System.Drawing.Color]::FromArgb(232,232,232)
    $assignmentSeparator.Location = [System.Drawing.Point]::new(14,45)
    $assignmentSeparator.Size = [System.Drawing.Size]::new(1002,1)
    $assignmentRow.Controls.Add($assignmentSeparator)

    $rowTools = New-ActionRow -Parent $actionsPanel -Title 'Status' -Description 'Sign in for cloud actions, or refresh local or cloud state.' -Y 46 -Buttons @('Sign in','Refresh cloud','Refresh local')
    $rowExport = New-ActionRow -Parent $actionsPanel -Title 'Export' -Description 'Export DeviceLink CSV for manual import in Intune.' -Y 92 -Buttons @('Export CSV')
    $rowOffboard = New-ActionRow -Parent $actionsPanel -Title 'Offboarding' -Description 'Remove the cloud association, reset local state, or both.' -Y 138 -Buttons @('Remove cloud','Reset local','Remove both')

    $offboardSeparator = @(
        $rowOffboard.Panel.Controls |
            Where-Object { $_ -is [System.Windows.Forms.Panel] -and $_.Height -eq 1 }
    ) | Select-Object -First 1
    if ($offboardSeparator) {
        $rowOffboard.Panel.Controls.Remove($offboardSeparator)
        $offboardSeparator.Dispose()
    }

    function Get-ActionButtonByText {
        param(
            [Parameter(Mandatory)][System.Windows.Forms.Control]$Row,
            [Parameter(Mandatory)][string]$Text
        )

        $button = @(
            $Row.Controls |
                Where-Object { $_ -is [System.Windows.Forms.Button] -and $_.Text -eq $Text }
        ) | Select-Object -First 1

        if (-not $button) {
            throw "GUI action button '$Text' could not be resolved."
        }

        $button
    }

    $btnSignIn = Get-ActionButtonByText -Row $rowTools.Panel -Text 'Sign in'
    $btnRefresh = Get-ActionButtonByText -Row $rowTools.Panel -Text 'Refresh local'
    $btnOnline = Get-ActionButtonByText -Row $rowTools.Panel -Text 'Refresh cloud'
    $btnExport = Get-ActionButtonByText -Row $rowExport.Panel -Text 'Export CSV'
    $btnCloudOffboard = Get-ActionButtonByText -Row $rowOffboard.Panel -Text 'Remove cloud'
    $btnLocalOffboard = Get-ActionButtonByText -Row $rowOffboard.Panel -Text 'Reset local'
    $btnFullOffboard = Get-ActionButtonByText -Row $rowOffboard.Panel -Text 'Remove both'

    $allActionButtons = @(
        $btnAssign,
        $btnSignIn,
        $btnRefresh,
        $btnOnline,
        $btnExport,
        $btnAssociate,
        $btnCloudOffboard,
        $btnLocalOffboard,
        $btnFullOffboard
    )

    $btnSignIn.Visible = $usesInteractiveUserAuthentication
    if (-not $usesInteractiveUserAuthentication) {
        $rowTools.Description.Text = 'Refresh local or cloud state.'
    }
    $actionsPanel.Height = 184

    $activityTitle = New-Object System.Windows.Forms.Label
    $activityTitle.Text = 'Activity'
    $activityTitle.Font = New-GuiFont -Size 11 -Style Bold
    $activityTitle.Location = [System.Drawing.Point]::new(16,564)
    $activityTitle.AutoSize = $true
    $content.Controls.Add($activityTitle)

    $btnClearActivity = New-Object System.Windows.Forms.Button
    $btnClearActivity.Text = 'Clear'
    $btnClearActivity.Font = New-GuiFont -Size 8.3 -Style Regular
    $btnClearActivity.Size = [System.Drawing.Size]::new(64,24)
    $btnClearActivity.FlatStyle = [System.Windows.Forms.FlatStyle]::Standard
    $content.Controls.Add($btnClearActivity)

    $activityCard = New-Card -Title '' -X 14 -Y 590 -Width 1030 -Height 118

    $consoleBox = New-Object System.Windows.Forms.TextBox
    $consoleBox.Location = [System.Drawing.Point]::new(12,10)
    $consoleBox.Size = [System.Drawing.Size]::new(1006,96)
    $consoleBox.Multiline = $true
    $consoleBox.ReadOnly = $true
    $consoleBox.ScrollBars = [System.Windows.Forms.ScrollBars]::Vertical
    $consoleBox.WordWrap = $false
    $consoleBox.Font = New-GuiFont -Family 'Consolas' -Size 8 -Style Regular
    $consoleBox.BackColor = [System.Drawing.Color]::FromArgb(250,250,250)
    $consoleBox.BorderStyle = [System.Windows.Forms.BorderStyle]::None
    $activityCard.Controls.Add($consoleBox)

    $statusStrip = New-Object System.Windows.Forms.StatusStrip
    $statusStrip.Dock = [System.Windows.Forms.DockStyle]::Bottom

    $statusLabel = New-Object System.Windows.Forms.ToolStripStatusLabel
    $statusLabel.Text = 'Ready'
    $statusLabel.Spring = $true
    $statusLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleLeft

    $statusProgress = New-Object System.Windows.Forms.ToolStripProgressBar
    $statusProgress.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
    $statusProgress.MarqueeAnimationSpeed = 30
    $statusProgress.Size = [System.Drawing.Size]::new(220,16)
    $statusProgress.Visible = $false

    [void]$statusStrip.Items.Add($statusLabel)
    [void]$statusStrip.Items.Add($statusProgress)
    $form.Controls.Add($statusStrip)

    function Set-GuiStatus {
        param([string]$Text)
        $statusLabel.Text = $Text
        [System.Windows.Forms.Application]::DoEvents()
    }

    function Write-GuiConsole {
        param(
            [AllowNull()][AllowEmptyString()][string]$Message,
            [switch]$Command,
            [switch]$ErrorMessage
        )

        if ([string]::IsNullOrWhiteSpace($Message)) { return }

        $timestamp = (Get-Date).ToString('HH:mm:ss')
        $prefix = if ($Command) { '>' } elseif ($ErrorMessage) { '!' } else { '-' }
        $line = "[$timestamp] $prefix $Message"

        $consoleBox.AppendText($line + [Environment]::NewLine)
        $consoleBox.SelectionStart = $consoleBox.TextLength
        $consoleBox.ScrollToCaret()
        [System.Windows.Forms.Application]::DoEvents()
        Write-Host $line
    }

    function Write-GuiObject {
        param($InputObject)

        if ($null -eq $InputObject) { return }

        foreach ($item in @($InputObject)) {
            if ($null -eq $item) { continue }

            $properties = @($item.PSObject.Properties)
            if ($properties.Count -eq 0) {
                $text = [string]$item
                if (-not [string]::IsNullOrWhiteSpace($text)) {
                    Write-GuiConsole -Message $text
                }
                continue
            }

            $rows = New-Object System.Collections.Generic.List[object]
            $compactHiddenProperties = @(
                'RegistrationResult',
                'BeforeStatus',
                'AfterStatus',
                'AssociationDetails'
            )

            foreach ($property in $properties) {
                if ([string]$property.Name -in $compactHiddenProperties) { continue }

                $value = $property.Value
                if ($null -eq $value) { continue }

                if ($value -is [string] -and [string]::IsNullOrWhiteSpace($value)) { continue }

                if ($value -is [datetime]) {
                    $displayValue = $value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
                }
                elseif ($value -is [datetimeoffset]) {
                    $displayValue = $value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
                }
                elseif ($value -is [System.Collections.IEnumerable] -and -not ($value -is [string])) {
                    $items = @($value)
                    if ($items.Count -eq 0) { continue }
                    $displayValue = ($items | ForEach-Object { [string]$_ }) -join ', '
                }
                else {
                    $displayValue = [string]$value
                }

                if ([string]::IsNullOrWhiteSpace($displayValue)) { continue }

                $rows.Add([pscustomobject]@{
                    Name = [string]$property.Name
                    Value = $displayValue
                })
            }

            if ($rows.Count -eq 0) { continue }

            $nameWidth = [Math]::Min(
                34,
                [Math]::Max(
                    8,
                    (@($rows | ForEach-Object { $_.Name.Length }) | Measure-Object -Maximum).Maximum
                )
            )

            foreach ($row in $rows) {
                $name = $row.Name
                if ($name.Length -gt $nameWidth) {
                    $name = $name.Substring(0,$nameWidth)
                }
                Write-GuiConsole -Message (("{0,-$nameWidth} : {1}" -f $name,$row.Value))
            }
        }
    }

    function Invoke-GuiInformationCommand {
        param(
            [Parameter(Mandatory)]
            [scriptblock]$ScriptBlock
        )

        $resultObjects = New-Object System.Collections.Generic.List[object]

        & $ScriptBlock 6>&1 | ForEach-Object {
            if ($_ -is [System.Management.Automation.InformationRecord]) {
                $message = [string]$_.MessageData
                if (-not [string]::IsNullOrWhiteSpace($message)) {
                    Write-GuiConsole -Message $message
                }
            }
            else {
                $resultObjects.Add($_)
            }

            [System.Windows.Forms.Application]::DoEvents()
        }

        return @($resultObjects.ToArray())
    }

    function Show-GuiError {
        param([string]$Message)

        Write-GuiConsole -Message $Message -ErrorMessage
        [void][System.Windows.Forms.MessageBox]::Show(
            $form,$Message,'WindowsDeviceLink',
            [System.Windows.Forms.MessageBoxButtons]::OK,
            [System.Windows.Forms.MessageBoxIcon]::Error
        )
    }

    function Confirm-GuiAction {
        param(
            [string]$Title,
            [string]$Message
        )

        $result = [System.Windows.Forms.MessageBox]::Show(
            $form,
            $Message,
            $Title,
            [System.Windows.Forms.MessageBoxButtons]::YesNo,
            [System.Windows.Forms.MessageBoxIcon]::Warning
        )

        return ($result -eq [System.Windows.Forms.DialogResult]::Yes)
    }

    function Set-GuiCapabilities {
        $support = $script:WdlGuiSupport
        $local = $script:WdlGuiLocalAssociation
        $cloud = $script:WdlGuiCloudStatus

        $runtimeReady = $support -and [bool]$support.Supported
        $canAssociate = $runtimeReady -and [string]$support.Environment -ne 'WindowsPE'

        $cloudState = if ($cloud) { ([string]$cloud.AssociationState).Trim().ToLowerInvariant() } else { '' }
        $cloudKnownAbsent = $cloud -and (
            $cloud.AssociationPresent -eq $false -or
            $cloudState -eq 'notassociated'
        )
        $cloudPresent = $cloud -and $cloud.AssociationPresent -eq $true
        $localStatePresent = $local -and ([string]$local.FirmwareState -in @('2/4','4/4'))
        $localAssociated = $local -and [string]$local.FirmwareState -eq '4/4'
        $offboardingStatePresent = $cloudPresent -or $localAssociated
        $selectedTenantId = Get-SelectedTenantId
        $selectedMatchesCloud = $cloudPresent -and -not [string]::IsNullOrWhiteSpace($selectedTenantId) -and
            [string]$cloud.TenantId -ieq $selectedTenantId
        $alreadyPreassociatedOrAssociatedInTarget = $selectedMatchesCloud -and $cloudState -in @('associated','preassociated')
        $alreadyAssociatedInTarget = $localAssociated -and $selectedMatchesCloud -and $cloudState -eq 'associated'

        $btnRefresh.Enabled = $true
        $directTenantReady = -not $hasDirectTenantCatalog -or -not [string]::IsNullOrWhiteSpace((Get-SelectedTenantId))
        $btnSignIn.Enabled = $usesInteractiveUserAuthentication -and $directTenantReady
        $btnOnline.Enabled = $runtimeReady -and ($backendMode -or $directTenantReady)
        $btnExport.Enabled = $runtimeReady
        $targetReadyForAssociation = if ($backendMode -or $hasDirectTenantCatalog) { -not [string]::IsNullOrWhiteSpace((Get-SelectedTenantId)) } else { $true }
        $btnAssign.Enabled = $runtimeReady -and $targetReadyForAssociation -and -not $alreadyPreassociatedOrAssociatedInTarget
        $btnAssociate.Enabled = $canAssociate -and $targetReadyForAssociation -and -not $alreadyAssociatedInTarget
        $btnCloudOffboard.Enabled = $runtimeReady -and $cloudPresent -and ($backendMode -or $directTenantReady)
        $btnLocalOffboard.Enabled = $runtimeReady -and $localStatePresent -and $cloudKnownAbsent
        $btnFullOffboard.Enabled = $runtimeReady -and $offboardingStatePresent -and ($backendMode -or $directTenantReady)

        $toolTip.SetToolTip($btnCloudOffboard, 'Remove only the tenant-side Device Association record.')
        $localResetToolTip = if (-not $cloud) {
            'Check cloud state before resetting the local DeviceLink firmware state.'
        }
        elseif (-not $cloudKnownAbsent) {
            'Local reset is blocked while a cloud association exists. Use pre-association, move, or Remove both to keep both states consistent.'
        }
        else {
            'Reset the local DeviceLink firmware state. Windows may create a new base identity automatically.'
        }
        $toolTip.SetToolTip($btnLocalOffboard, $localResetToolTip)
        $toolTip.SetToolTip($btnFullOffboard, 'Remove the cloud association and local DeviceLink firmware state.')
        $toolTip.SetToolTip($btnAssociate, 'Pre-associate the device with the selected tenant and complete Device Association on this Windows device.')
        $toolTip.SetToolTip($btnAssign, 'Pre-associate the device with the selected target tenant. Backend mode safely applies New, no-op, or Move.')
        $toolTip.SetToolTip($btnSignIn, 'Authenticate once for this Direct-mode UI session and load the cloud association.')
        if ($hasDirectTenantCatalog -and -not $directTenantReady) {
            $toolTip.SetToolTip($btnSignIn, 'Select a target tenant first. Sign-in will be scoped to that tenant.')
            $toolTip.SetToolTip($btnAssign, 'Select a target tenant first. Direct mode applies New or no-op only in that selected tenant.')
            $toolTip.SetToolTip($btnAssociate, 'Select a target tenant first, then sign in to associate the device.')
        }
        elseif (-not $backendMode) { $toolTip.SetToolTip($btnAssign, 'Direct mode applies New or no-op in the selected tenant, or in the tenant determined by sign-in when no catalog is configured.') }
        elseif (-not $cloud) { $toolTip.SetToolTip($btnAssign, 'Check all configured tenants, renew the local identity when required, and pre-associate the device with the selected target tenant.') }

        if ($alreadyAssociatedInTarget) {
            $toolTip.SetToolTip($btnAssociate, 'The device is already associated.')
        }
        if ($alreadyPreassociatedOrAssociatedInTarget) {
            $toolTip.SetToolTip($btnAssign, 'The device is already pre-associated or associated with the selected tenant. Select another tenant to move it.')
        }

        if ($cloudKnownAbsent) {
            $toolTip.SetToolTip($btnCloudOffboard, 'Cloud state is already known to be Not associated; there is nothing to remove.')
        }

        if ($support -and [string]$support.Environment -eq 'WindowsPE') {
            $winPeAssociationToolTip = 'Association is not available in Windows PE. Pre-associate the device now; association is completed automatically during Windows OOBE.'
            $toolTip.SetToolTip($btnAssociate,$winPeAssociationToolTip)
            $toolTip.SetToolTip($btnAssociateHost,$winPeAssociationToolTip)
            $onlineToolTip = if ($backendMode) {
                'Windows PE online operations are performed through the configured Function backend.'
            }
            else {
                "Windows PE online operations use authentication method '$Method'. Interactive browser authentication is not supported in WinPE."
            }
            $toolTip.SetToolTip($btnOnline,$onlineToolTip)
        }

        if (-not $runtimeReady -and $support) {
            $runtimeReason = if ([string]::IsNullOrWhiteSpace([string]$support.Reason)) {
                'The local DeviceLink runtime is unavailable.'
            }
            else {
                [string]$support.Reason
            }

            foreach ($button in @($btnAssign,$btnAssociate,$btnOnline,$btnExport,$btnFullOffboard)) {
                $toolTip.SetToolTip($button,$runtimeReason)
            }
        }
    }

    function Set-GuiBusy {
        param(
            [Parameter(Mandatory)][bool]$Busy,
            [string]$StatusText
        )

        $script:WdlGuiBusy = $Busy

        if ($Busy) {
            foreach ($button in $allActionButtons) {
                $button.Enabled = $false
            }
            $actionsPanel.Enabled = $false
            $tenantSelector.Enabled = $false
        }
        else {
            $actionsPanel.Enabled = $true
            $tenantSelector.Enabled = $showTenantSelector
            Set-GuiCapabilities
        }

        $btnClearActivity.Enabled = -not $Busy
        $statusProgress.Visible = $Busy
        $form.UseWaitCursor = $Busy

        if (-not [string]::IsNullOrWhiteSpace($StatusText)) {
            Set-GuiStatus $StatusText
        }

        foreach ($button in $allActionButtons) {
            $button.Invalidate()
            $button.Update()
        }

        $actionsPanel.Invalidate($true)
        $actionsPanel.Update()
        $tenantSelector.Invalidate()
        $tenantSelector.Update()
        [System.Windows.Forms.Application]::DoEvents()
    }

    function Refresh-LocalView {
        Set-GuiStatus 'Refreshing local state...'
        Write-GuiConsole -Message 'Refresh local state' -Command

        $ui.LocalState.Text = 'Checking...'
        $ui.Firmware.Text = 'Checking...'
        $ui.LinkId.Text = 'Checking...'
        $ui.LocalCreated.Text = 'Checking...'
        [System.Windows.Forms.Application]::DoEvents()

        $bios = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop
        $cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
        $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue
        $runtimeParameters = Get-GuiRuntimeParameters
        $support = Test-WindowsDeviceLinkSupport @runtimeParameters
        $local = Get-WindowsDeviceLinkLocalAssociation

        $script:WdlGuiSupport = $support
        $script:WdlGuiLocalAssociation = $local

        $ui.Manufacturer.Text = [string]$cs.Manufacturer
        $ui.Model.Text = [string]$cs.Model
        $ui.Serial.Text = [string]$bios.SerialNumber

        if ($support.Environment -eq 'WindowsPE') {
            $environmentText = 'Windows PE'
        }
        elseif ($os -and [string]$os.Caption -match 'Windows 11') {
            $environmentText = 'Windows 11'
        }
        elseif ($os -and -not [string]::IsNullOrWhiteSpace([string]$os.Caption)) {
            $environmentText = ([string]$os.Caption).Replace('Microsoft ','')
        }
        else {
            $environmentText = [string]$support.Environment
        }

        $ui.OperatingSystem.Text = $environmentText

        $environmentToolTip = if ($support.Supported) {
            @(
                $environmentText
                "Activation: $($support.ActivationMode)"
                "Runtime: $($support.DllVersion)"
            ) -join [Environment]::NewLine
        }
        else {
            @(
                $environmentText
                "Runtime unavailable: $($support.Reason)"
            ) -join [Environment]::NewLine
        }
        $toolTip.SetToolTip($ui.OperatingSystem,$environmentToolTip)

        if (-not $support.Supported) {
            Write-GuiConsole -Message "DeviceLink runtime unavailable: $($support.Reason)"
        }

        $selectedTenant = Get-SelectedTenantId
        if ($backendMode) {
            $ui.ConnectionMode.Text = 'Backend'
            $ui.Authentication.Text = 'Function API key'
            $ui.Endpoint.Text = [string]$BackendUri.Host
            $ui.TenantScope.Text = "$($effectiveTenants.Count) available tenants"
            $toolTip.SetToolTip($ui.Endpoint,[string]$BackendUri.AbsoluteUri)
        }
        else {
            $ui.ConnectionMode.Text = 'Direct'
            if ($usesInteractiveUserAuthentication) {
                $sessionState = if ($script:WdlGuiSessionAuthenticated) { 'Signed in' } else { 'Signed out' }
                $ui.Authentication.Text = "$Method - $sessionState"
            }
            else {
                $ui.Authentication.Text = "$Method - Non-interactive"
            }
            $ui.Endpoint.Text = 'Microsoft Graph'
            $ui.TenantScope.Text = if ($selectedTenant) { Get-TenantDisplayName -TenantId $selectedTenant } else { 'Determined by sign-in' }
        }

        $ui.Firmware.Text = [string]$local.FirmwareState

        $friendlyLocalState = switch ([string]$local.LocalAssociationState) {
            'CompleteAssociationFirmwareState' { 'Associated' }
            'BaseIdentity' { 'Base identity' }
            'NoFirmwareState' { 'No firmware state' }
            'IncompleteFirmwareState' { 'Incomplete firmware state' }
            default { [string]$local.LocalAssociationState }
        }
        $ui.LocalState.Text = $friendlyLocalState
        $localLinkId = if ($local.LinkId) { [string]$local.LinkId } else { $null }
        $ui.LinkId.Text = if ($localLinkId) { $localLinkId } else { 'Unavailable' }
        $ui.LocalCreated.Text = if ($local.FirmwareCreationTimeUtc) {
            try { ([datetime]$local.FirmwareCreationTimeUtc).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss UTC') }
            catch { [string]$local.FirmwareCreationTimeUtc }
        }
        else { 'Unavailable' }
        $toolTip.SetToolTip($ui.LinkId, $(if ($localLinkId) { $localLinkId } else { 'Unavailable' }))
        $toolTip.SetToolTip($ui.LocalCreated, [string]$ui.LocalCreated.Text)

        Set-GuiCapabilities
        Set-GuiStatus "Local state refreshed | $($local.FirmwareState)"
        Write-GuiConsole -Message "Local state: $($local.FirmwareState), tenant source: $($local.Source)"
    }

    function Get-GuiCloudStateText {
        param([AllowNull()][object]$State)

        switch (([string]$State).Trim().ToLowerInvariant()) {
            'associated'    { return 'Associated' }
            'preassociated' { return 'Pre-associated' }
            'notassociated' { return 'Not associated' }
            default {
                if ([string]::IsNullOrWhiteSpace([string]$State)) {
                    return 'Not checked'
                }
                return [string]$State
            }
        }
    }

    function Refresh-CloudView {
        param(
            [switch]$WriteCommand
        )

        $ui.CloudState.Text = 'Checking...'
        $ui.CloudTenant.Text = 'Checking...'
        $ui.CloudId.Text = 'Checking...'
        $ui.CloudChecked.Text = 'Checking...'
        [System.Windows.Forms.Application]::DoEvents()

        if ($backendMode) {
            $credential = New-Object System.Management.Automation.PSCredential('api-key',$BackendApiKey)
            $plainKey = $null
            try {
                $plainKey = $credential.GetNetworkCredential().Password
                $parameters = @{ BackendUri=$BackendUri; BackendApiKey=$plainKey }
                $runtimeParameters = Get-GuiOperationParameters
                foreach ($key in $runtimeParameters.Keys) { $parameters[$key]=$runtimeParameters[$key] }
                if ($WriteCommand) { Write-GuiConsole -Message 'Get-WindowsDeviceLinkStatus through Backend mode' -Command }
                $cloud = Get-WindowsDeviceLinkBackendStatus @parameters
            }
            finally { $plainKey=$null; $credential=$null }
        }
        else {
            $parameters = Get-GuiAuthParameters
            $runtimeParameters = Get-GuiOperationParameters
            foreach ($key in $runtimeParameters.Keys) { $parameters[$key]=$runtimeParameters[$key] }
            $parameters.Online = $true
            if ($WriteCommand) { Write-GuiConsole -Message "Get-WindowsDeviceLinkStatus -Online -Method $Method" -Command }
            $cloudResults = @(Invoke-GuiInformationCommand -ScriptBlock { Get-WindowsDeviceLinkStatus @parameters })
            $cloud = $cloudResults | Select-Object -Last 1
        }
        $script:WdlGuiCloudStatus = $cloud

        if ($usesInteractiveUserAuthentication) {
            $script:WdlGuiSessionAuthenticated = $true
            $ui.Authentication.Text = "$Method - Signed in"
            $btnSignIn.Text = 'Switch account'
        }

        $ui.CloudState.Text = Get-GuiCloudStateText -State $cloud.AssociationState
        $ui.CloudTenant.Text = if ($cloud.TenantId) { Get-TenantDisplayName -TenantId ([string]$cloud.TenantId) } else { 'None' }
        $cloudAssociationId = if ($cloud.AssociationId) { [string]$cloud.AssociationId } else { $null }
        $ui.CloudId.Text = if ($cloudAssociationId) { $cloudAssociationId } else { 'None' }
        $ui.CloudChecked.Text = (Get-Date).ToString('HH:mm:ss')
        $ui.CloudAccent.BackColor = $colorCardAccent
        $cloudCard.BackColor = $colorCardTint

        $cloudTenantIdText = if ($cloud.TenantId) { [string]$cloud.TenantId } else { 'Unavailable' }
        $toolTip.SetToolTip($ui.CloudTenant,$cloudTenantIdText)
        $toolTip.SetToolTip($ui.CloudId, $(if ($cloudAssociationId) { $cloudAssociationId } else { 'None' }))

        return $cloud
    }

    function Invoke-GuiRefresh {
        if ($script:WdlGuiBusy) { return }

        Set-GuiBusy -Busy $true -StatusText 'Refreshing local state...'
        try { Refresh-LocalView }
        catch { Show-GuiError $_.Exception.Message }
        finally { Set-GuiBusy -Busy $false }
    }

    function Invoke-GuiOnline {
        if ($script:WdlGuiBusy) { return }

        Set-GuiBusy -Busy $true -StatusText 'Checking tenant-side Device Association...'
        try {
            $cloud = Refresh-CloudView -WriteCommand
            Write-GuiObject $cloud

            Set-GuiStatus 'Cloud lookup completed'
        }
        catch {
            Set-GuiStatus 'Cloud lookup failed'
            Show-GuiError $_.Exception.Message
        }
        finally { Set-GuiBusy -Busy $false }
    }

    function Invoke-GuiSignIn {
        if ($script:WdlGuiBusy -or $backendMode) { return }

        Clear-GuiSessionAuthentication
        Set-GuiBusy -Busy $true -StatusText 'Signing in...'
        try {
            [void](Get-GuiAuthParameters)
            $cloud = Refresh-CloudView -WriteCommand
            Write-GuiObject $cloud
            Set-GuiStatus 'Signed in; cloud association loaded'
        }
        catch {
            Clear-GuiSessionAuthentication
            Set-GuiStatus 'Sign-in failed'
            Show-GuiError $_.Exception.Message
        }
        finally { Set-GuiBusy -Busy $false }
    }

    function Invoke-GuiTenantAssignment {
        if ($script:WdlGuiBusy) { return }
        $targetId = Get-SelectedTenantId
        if ($backendMode -and [string]::IsNullOrWhiteSpace($targetId)) { Show-GuiError 'Select a target tenant first.'; return }
        if ($hasDirectTenantCatalog -and [string]::IsNullOrWhiteSpace($targetId)) { Show-GuiError 'Select a target tenant first. Direct-mode sign-in and pre-association will use that tenant.'; return }
        $targetLabel = if ($targetId) { [string]$tenantSelector.SelectedItem } else { 'Tenant determined by sign-in' }
        $sourceText = if ($script:WdlGuiCloudStatus -and $script:WdlGuiCloudStatus.TenantId) { [string]$script:WdlGuiCloudStatus.TenantId } else { 'determined automatically' }
        $modeText = if ($backendMode) {
            'Backend mode can perform a verified Move when another configured tenant currently contains the device.'
        } else {
            'Direct mode checks only the chosen sign-in tenant and can perform New or no-op; it cannot prove absence from other tenants.'
        }
        $message = @("Current cloud tenant: $sourceText","Target tenant: $targetLabel",'',$modeText,'','Continue?') -join [Environment]::NewLine
        if (-not $backendMode -and -not (Confirm-GuiAction -Title 'Pre-associate device' -Message $message)) { return }

        Set-GuiBusy -Busy $true -StatusText $(if ($backendMode) { 'Checking cloud state and pre-associating device...' } else { 'Applying device association...' })
        try {
            $parameters = if ($backendMode) { Get-GuiBackendParameters } else { Get-GuiAuthParameters }
            if ($backendMode) { $parameters.TargetTenantId = [guid]$targetId }
            $parameters.Confirm = $false
            $runtimeParameters = Get-GuiOperationParameters
            foreach ($key in $runtimeParameters.Keys) { $parameters[$key]=$runtimeParameters[$key] }
            $commandText = if ($backendMode) { "Set-WindowsDeviceLinkTenant -BackendUri <configured> -TargetTenantId $targetId" } elseif ($targetId) { "Set-WindowsDeviceLinkTenant -Method $Method -TenantId $targetId" } else { "Set-WindowsDeviceLinkTenant -Method $Method" }
            Write-GuiConsole -Message $commandText -Command
            $results = @(Invoke-GuiInformationCommand -ScriptBlock { Set-WindowsDeviceLinkTenant @parameters })
            $result = $results | Select-Object -Last 1
            Write-GuiObject $result
            Refresh-LocalView
            if ($backendMode) {
                $cloud = Refresh-CloudView
            }
            else {
                $cloud = $result.Details.AfterStatus
                $script:WdlGuiCloudStatus = $cloud
                $ui.CloudState.Text = Get-GuiCloudStateText -State $cloud.AssociationState
                $ui.CloudTenant.Text = if ($cloud.TenantId) { Get-TenantDisplayName -TenantId ([string]$cloud.TenantId) } else { 'None' }
                $ui.CloudId.Text = if ($cloud.AssociationId) { [string]$cloud.AssociationId } else { 'None' }
                $ui.CloudChecked.Text = (Get-Date).ToString('HH:mm:ss')
                $ui.CloudAccent.BackColor = $colorCardAccent
                $cloudCard.BackColor = $colorCardTint
            }
            Write-GuiObject $cloud
            Set-GuiStatus ([string]$result.Message)
        }
        catch {
            Set-GuiStatus 'Tenant assignment failed or requires verification'
            if ($backendMode) { try { $cloud = Refresh-CloudView; Write-GuiObject $cloud } catch {} }
            Show-GuiError $_.Exception.Message
        }
        finally { Set-GuiBusy -Busy $false }
    }

    function Select-GuiExportDirectory {
        if (-not $isWinPE) {
            $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
            $dialog.Description = 'Choose a folder for the DeviceLink CSV'
            try {
                if ($dialog.ShowDialog($form) -ne [System.Windows.Forms.DialogResult]::OK) {
                    return $null
                }
                return [string]$dialog.SelectedPath
            }
            finally {
                $dialog.Dispose()
            }
        }

        $defaultPath = $null
        if ($outerBoundParameters.ContainsKey('WindowsManagementServicePath')) {
            try {
                $defaultPath = Split-Path -Parent $WindowsManagementServicePath
            }
            catch {}
        }
        if ([string]::IsNullOrWhiteSpace($defaultPath)) {
            try { $defaultPath = (Get-Location).Path } catch {}
        }
        if ([string]::IsNullOrWhiteSpace($defaultPath)) {
            $defaultPath = 'X:\'
        }

        $pathForm = New-Object System.Windows.Forms.Form
        $pathForm.Text = 'Export DeviceLink CSV'
        $pathForm.StartPosition = 'CenterParent'
        $pathForm.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedDialog
        $pathForm.MinimizeBox = $false
        $pathForm.MaximizeBox = $false
        $pathForm.ShowInTaskbar = $false
        $pathForm.Size = [System.Drawing.Size]::new(520,155)

        $pathLabel = New-Object System.Windows.Forms.Label
        $pathLabel.Text = 'Output folder'
        $pathLabel.Font = New-GuiFont -Size 9 -Style Regular
        $pathLabel.Location = [System.Drawing.Point]::new(14,14)
        $pathLabel.AutoSize = $true
        $pathForm.Controls.Add($pathLabel)

        $pathBox = New-Object System.Windows.Forms.TextBox
        $pathBox.Text = $defaultPath
        $pathBox.Font = New-GuiFont -Family 'Consolas' -Size 9 -Style Regular
        $pathBox.Location = [System.Drawing.Point]::new(16,40)
        $pathBox.Size = [System.Drawing.Size]::new(472,24)
        $pathForm.Controls.Add($pathBox)

        $okButton = New-Object System.Windows.Forms.Button
        $okButton.Text = 'Export'
        $okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
        $okButton.Location = [System.Drawing.Point]::new(308,78)
        $okButton.Size = [System.Drawing.Size]::new(86,30)
        $pathForm.Controls.Add($okButton)

        $cancelButton = New-Object System.Windows.Forms.Button
        $cancelButton.Text = 'Cancel'
        $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
        $cancelButton.Location = [System.Drawing.Point]::new(402,78)
        $cancelButton.Size = [System.Drawing.Size]::new(86,30)
        $pathForm.Controls.Add($cancelButton)

        $pathForm.AcceptButton = $okButton
        $pathForm.CancelButton = $cancelButton

        try {
            while ($true) {
                if ($pathForm.ShowDialog($form) -ne [System.Windows.Forms.DialogResult]::OK) {
                    return $null
                }

                $selectedPath = [string]$pathBox.Text
                if (-not [string]::IsNullOrWhiteSpace($selectedPath)) {
                    $selectedPath = $selectedPath.Trim()
                }

                if (-not [string]::IsNullOrWhiteSpace($selectedPath) -and [System.IO.Directory]::Exists($selectedPath)) {
                    return $selectedPath
                }

                [void][System.Windows.Forms.MessageBox]::Show(
                    $pathForm,
                    'The specified output folder does not exist. Enter an existing folder path.',
                    'WindowsDeviceLink',
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Warning
                )
            }
        }
        finally {
            $pathForm.Dispose()
        }
    }

    function Invoke-GuiExport {
        if ($script:WdlGuiBusy) { return }

        Set-GuiBusy -Busy $true -StatusText 'Choose export folder...'

        try {
            $selectedPath = Select-GuiExportDirectory
            if ([string]::IsNullOrWhiteSpace($selectedPath)) {
                Set-GuiStatus 'Export cancelled'
                return
            }

            Set-GuiStatus 'Exporting DeviceLink CSV...'
            Write-GuiConsole -Message "Get-WindowsDeviceLink -OutputDirectory '$selectedPath'" -Command

            $deviceLinkParameters = Get-GuiOperationParameters
            $deviceLinkParameters.OutputDirectory = $selectedPath
            $file = Get-WindowsDeviceLink @deviceLinkParameters
            Write-GuiObject $file

            [void][System.Windows.Forms.MessageBox]::Show(
                $form,
                ('Exported to:' + [Environment]::NewLine + $file.FullName),
                'WindowsDeviceLink',
                [System.Windows.Forms.MessageBoxButtons]::OK,
                [System.Windows.Forms.MessageBoxIcon]::Information
            )

            Refresh-LocalView
        }
        catch { Show-GuiError $_.Exception.Message }
        finally {
            Set-GuiBusy -Busy $false
        }
    }

    function Invoke-GuiAssociate {
        if ($script:WdlGuiBusy) { return }

        if ($isWinPE) {
            Show-GuiError 'Association is not available in Windows PE. Pre-associate the device now; association is completed automatically during Windows OOBE.'
            return
        }

        $targetId = Get-SelectedTenantId
        if (($backendMode -or $hasDirectTenantCatalog) -and [string]::IsNullOrWhiteSpace($targetId)) {
            Show-GuiError 'Select a target tenant first.'
            return
        }

        Set-GuiBusy -Busy $true -StatusText 'Associating device...'
        try {
            if ($backendMode) {
                $assignmentParameters = Get-GuiBackendParameters
                $assignmentParameters.TargetTenantId = [guid]$targetId
                $assignmentParameters.Confirm = $false
                $runtimeParameters = Get-GuiOperationParameters
                foreach ($key in $runtimeParameters.Keys) { $assignmentParameters[$key] = $runtimeParameters[$key] }

                $cloudState = if ($script:WdlGuiCloudStatus) { ([string]$script:WdlGuiCloudStatus.AssociationState).Trim().ToLowerInvariant() } else { '' }
                $cloudTenantId = if ($script:WdlGuiCloudStatus) { [string]$script:WdlGuiCloudStatus.TenantId } else { '' }
                if ($script:WdlGuiLocalAssociation -and [string]$script:WdlGuiLocalAssociation.FirmwareState -eq '2/4' -and
                    $cloudState -eq 'associated' -and $cloudTenantId -ieq $targetId) {
                    $assignmentParameters.RepairExistingAssociation = $true
                    Write-GuiConsole -Message 'Same-tenant repair required: replacing the stale cloud association with the current local identity.'
                }

                Write-GuiConsole -Message "Set-WindowsDeviceLinkTenant -BackendUri <configured> -TargetTenantId $targetId" -Command
                $assignmentResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                    Set-WindowsDeviceLinkTenant @assignmentParameters
                })
                $assignment = $assignmentResults | Select-Object -Last 1
                Write-GuiObject $assignment

                Write-GuiConsole -Message 'Complete-WindowsDeviceLinkAssociation' -Command
                $completionResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                    Complete-WindowsDeviceLinkAssociation -Confirm:$false
                })
                $result = $completionResults | Select-Object -Last 1
            }
            else {
                $parameters = Get-GuiAuthParameters
                $runtimeParameters = Get-GuiOperationParameters
                foreach ($key in $runtimeParameters.Keys) { $parameters[$key] = $runtimeParameters[$key] }
                $parameters.Associate = $true
                $parameters.Confirm = $false

                Write-GuiConsole -Message "Initialize-WindowsDeviceLink -Method $Method -Associate" -Command
                $resultObjects = New-Object System.Collections.Generic.List[object]
                & {
                    Initialize-WindowsDeviceLink @parameters
                } 6>&1 | ForEach-Object {
                    if ($_ -is [System.Management.Automation.InformationRecord]) {
                        $message = [string]$_.MessageData
                        if ($message) { Write-GuiConsole -Message $message }
                    }
                    else { $resultObjects.Add($_) }
                    [System.Windows.Forms.Application]::DoEvents()
                }
                $result = @($resultObjects.ToArray()) | Select-Object -Last 1
            }

            Write-GuiObject $result

            $script:WdlGuiCloudStatus = $null
            Refresh-LocalView
            $verifiedCloud = Refresh-CloudView -WriteCommand
            Write-GuiObject $verifiedCloud
            Set-GuiStatus 'Association completed and verified'
        }
        catch {
            Set-GuiStatus 'Association failed or requires verification'
            Show-GuiError $_.Exception.Message
        }
        finally { Set-GuiBusy -Busy $false }
    }

    function Invoke-GuiCloudOffboard {
        if ($script:WdlGuiBusy) { return }

        if (-not (Confirm-GuiAction -Title 'Cloud offboarding' -Message 'Remove only the tenant-side Device Association record? Local DeviceLink firmware will remain unchanged.')) {
            return
        }

        Set-GuiBusy -Busy $true -StatusText 'Removing cloud association...'
        try {
            if ($backendMode) {
                $serialNumber = ([string](Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber).Trim()
                $sourceTenantId = if ($script:WdlGuiCloudStatus) { [string]$script:WdlGuiCloudStatus.TenantId } else { $null }
                $credential = New-Object System.Management.Automation.PSCredential('api-key',$BackendApiKey)
                $plainKey = $null
                try {
                    $plainKey = $credential.GetNetworkCredential().Password
                    Write-GuiConsole -Message 'Invoke backend cloud offboarding' -Command
                    $result = Invoke-WindowsDeviceLinkBackendOffboard -BackendUri $BackendUri -BackendApiKey $plainKey -SerialNumber $serialNumber -SourceTenantId $sourceTenantId -TimeoutSeconds $TimeoutSeconds
                }
                finally { $plainKey=$null; $credential=$null }

                Write-GuiObject $result
                Refresh-LocalView
                $verifiedCloud = Refresh-CloudView -WriteCommand
                Write-GuiObject $verifiedCloud
                Set-GuiStatus 'Cloud offboarding completed and verified'
                return
            }

            $parameters = Get-GuiAuthParameters
            $parameters.Confirm = $false

            Write-GuiConsole -Message "Remove-WindowsDeviceLinkAssociation -Method $Method" -Command
            $removalResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                Remove-WindowsDeviceLinkAssociation @parameters
            })
            $result = $removalResults | Select-Object -Last 1
            Write-GuiObject $result

            $script:WdlGuiCloudStatus = $null
            $ui.CloudState.Text = 'Not checked'
            $ui.CloudTenant.Text = 'Not checked'
            $ui.CloudId.Text = 'Not checked'
            $ui.CloudChecked.Text = 'Not checked'
            $ui.CloudAccent.BackColor = $colorCardAccent
            $cloudCard.BackColor = $colorCardTint

            Refresh-LocalView
            Set-GuiStatus 'Cloud offboarding completed'
        }
        catch {
            $message = [string]$_.Exception.Message
            if ($message -like "No Device Association record was found for serial number *") {
                $noChangeMessage = 'No cloud association was found. Nothing was removed.'
                Write-GuiConsole -Message $noChangeMessage
                $script:WdlGuiCloudStatus = $null
                $ui.CloudState.Text = 'Not associated'
                $ui.CloudTenant.Text = 'None'
                $ui.CloudId.Text = 'None'
                $ui.CloudChecked.Text = (Get-Date).ToString('HH:mm:ss')
                $ui.CloudAccent.BackColor = $colorCardAccent
                $cloudCard.BackColor = $colorCardTint
                Refresh-LocalView
                Set-GuiStatus $noChangeMessage
                [void][System.Windows.Forms.MessageBox]::Show(
                    $form,
                    $noChangeMessage,
                    'WindowsDeviceLink',
                    [System.Windows.Forms.MessageBoxButtons]::OK,
                    [System.Windows.Forms.MessageBoxIcon]::Information
                )
            }
            else {
                Set-GuiStatus 'Cloud offboarding failed'
                Show-GuiError $message
            }
        }
        finally { Set-GuiBusy -Busy $false }
    }

    function Invoke-GuiLocalOffboard {
        if ($script:WdlGuiBusy) { return }

        if (-not (Confirm-GuiAction -Title 'Reset local state' -Message 'Reset all known local DeviceLink UEFI variables? Windows may create a new base identity automatically. The tenant-side Device Association record will remain unchanged.')) {
            return
        }

        Set-GuiBusy -Busy $true -StatusText 'Resetting local DeviceLink state...'
        try {
            Write-GuiConsole -Message 'Reset-WindowsDeviceLinkFirmwareState' -Command
            $result = Reset-WindowsDeviceLinkFirmwareState -Confirm:$false
            Write-GuiObject $result

            Refresh-LocalView
            Set-GuiStatus 'Local offboarding completed'
        }
        catch {
            Set-GuiStatus 'Local offboarding failed'
            Show-GuiError $_.Exception.Message
        }
        finally { Set-GuiBusy -Busy $false }
    }

    function Invoke-GuiFullOffboard {
        if ($script:WdlGuiBusy) { return }

        $message = @(
            'This performs full DeviceLink offboarding:'
            ''
            '1. Verify tenant-side Device Association state'
            '2. Remove the cloud Device Association if present'
            '3. Reset all known local DeviceLink UEFI variables'
            ''
            'This does not remove the Entra device or MDM enrollment record.'
            ''
            'Continue?'
        ) -join [Environment]::NewLine

        if (-not (Confirm-GuiAction -Title 'Full DeviceLink offboarding' -Message $message)) {
            return
        }

        Set-GuiBusy -Busy $true -StatusText 'Performing full offboarding...'
        $effectiveAccessToken = $null
        try {
            if ($backendMode) {
                $serialNumber = ([string](Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber).Trim()
                $sourceTenantId = if ($script:WdlGuiCloudStatus) { [string]$script:WdlGuiCloudStatus.TenantId } else { $null }
                $credential = New-Object System.Management.Automation.PSCredential('api-key',$BackendApiKey)
                $plainKey = $null
                try {
                    $plainKey = $credential.GetNetworkCredential().Password
                    Write-GuiConsole -Message 'Invoke backend cloud offboarding' -Command
                    $removed = Invoke-WindowsDeviceLinkBackendOffboard -BackendUri $BackendUri -BackendApiKey $plainKey -SerialNumber $serialNumber -SourceTenantId $sourceTenantId -TimeoutSeconds $TimeoutSeconds
                }
                finally { $plainKey=$null; $credential=$null }
                Write-GuiObject $removed

                Write-GuiConsole -Message 'Reset-WindowsDeviceLinkFirmwareState' -Command
                $reset = Reset-WindowsDeviceLinkFirmwareState -Confirm:$false
                Write-GuiObject $reset

                $script:WdlGuiCloudStatus = $null
                Refresh-LocalView
                $verifiedCloud = Refresh-CloudView -WriteCommand
                Write-GuiObject $verifiedCloud
                Set-GuiStatus 'Full offboarding completed and verified'
                return
            }

            $auth = Get-GuiAuthParameters
            $effectiveAuth = @{}
            foreach ($key in $auth.Keys) { $effectiveAuth[$key] = $auth[$key] }

            if ($effectiveAuth.Method -eq 'DeviceCode') {
                Write-GuiConsole -Message 'Acquiring one DeviceCode token for the complete offboarding flow.' -Command

                $tokenParameters = @{}
                if ($auth.ContainsKey('TenantId')) { $tokenParameters.TenantId = $auth.TenantId }
                if ($auth.ContainsKey('ClientId')) { $tokenParameters.ClientId = $auth.ClientId }

                $tokenResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                    Get-WindowsDeviceLinkDeviceCodeToken @tokenParameters
                })
                $token = $tokenResults | Select-Object -Last 1

                if (-not $token -or [string]::IsNullOrWhiteSpace([string]$token.AccessToken)) {
                    throw 'Device code authentication did not return an access token.'
                }

                $effectiveAccessToken = ConvertTo-SecureString ([string]$token.AccessToken) -AsPlainText -Force
                $effectiveAuth = @{
                    Method = 'AccessToken'
                    AccessToken = $effectiveAccessToken
                    Environment = $Environment
                    ClientTimeout = $ClientTimeout
                }
                if ($token.TenantId) {
                    $effectiveAuth.TenantId = [string]$token.TenantId
                }

                $token = $null
                Write-GuiConsole -Message 'DeviceCode token acquired once; reusing it for cloud verification and removal.'
            }

            $statusParameters = @{}
            foreach ($key in $effectiveAuth.Keys) { $statusParameters[$key] = $effectiveAuth[$key] }
            $runtimeParameters = Get-GuiOperationParameters
            foreach ($key in $runtimeParameters.Keys) {
                $statusParameters[$key] = $runtimeParameters[$key]
            }
            $statusParameters.Online = $true

            $displayMethod = [string]$effectiveAuth.Method
            Write-GuiConsole -Message "Get-WindowsDeviceLinkStatus -Online -Method $displayMethod" -Command
            $cloudResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                Get-WindowsDeviceLinkStatus @statusParameters
            })
            $cloud = $cloudResults | Select-Object -Last 1
            Write-GuiObject $cloud

            if ($null -eq $cloud.AssociationPresent -or [string]$cloud.AssociationState -eq 'Unknown') {
                throw 'Cloud Device Association state is indeterminate. Local firmware was not reset.'
            }

            if ($cloud.AssociationPresent) {
                $removeParameters = @{}
                foreach ($key in $effectiveAuth.Keys) { $removeParameters[$key] = $effectiveAuth[$key] }
                $removeParameters.Confirm = $false

                Write-GuiConsole -Message "Remove-WindowsDeviceLinkAssociation -Method $displayMethod" -Command
                $removeResults = @(Invoke-GuiInformationCommand -ScriptBlock {
                    Remove-WindowsDeviceLinkAssociation @removeParameters
                })
                $removed = $removeResults | Select-Object -Last 1
                Write-GuiObject $removed
            }
            else {
                Write-GuiConsole -Message 'No tenant-side Device Association exists. Cloud removal skipped.'
            }

            Write-GuiConsole -Message 'Reset-WindowsDeviceLinkFirmwareState' -Command
            $reset = Reset-WindowsDeviceLinkFirmwareState -Confirm:$false
            Write-GuiObject $reset

            $script:WdlGuiCloudStatus = $null
            $ui.CloudState.Text = 'Not checked'
            $ui.CloudTenant.Text = 'Not checked'
            $ui.CloudId.Text = 'Not checked'
            $ui.CloudChecked.Text = 'Not checked'
            $ui.CloudAccent.BackColor = $colorCardAccent
            $cloudCard.BackColor = $colorCardTint

            Refresh-LocalView
            Set-GuiStatus 'Full offboarding completed'
        }
        catch {
            Set-GuiStatus 'Full offboarding failed'
            Show-GuiError $_.Exception.Message
        }
        finally {
            $effectiveAccessToken = $null
            Set-GuiBusy -Busy $false
        }
    }

    $btnSignIn.Add_Click({ Invoke-GuiSignIn })
    $btnRefresh.Add_Click({ Invoke-GuiRefresh })
    $btnAssign.Add_Click({ Invoke-GuiTenantAssignment })
    $btnOnline.Add_Click({ Invoke-GuiOnline })
    $btnExport.Add_Click({ Invoke-GuiExport })
    $btnAssociate.Add_Click({ Invoke-GuiAssociate })
    $btnCloudOffboard.Add_Click({ Invoke-GuiCloudOffboard })
    $btnLocalOffboard.Add_Click({ Invoke-GuiLocalOffboard })
    $btnFullOffboard.Add_Click({ Invoke-GuiFullOffboard })

    $tenantSelector.Add_SelectedIndexChanged({
        if (-not $script:WdlGuiBusy) {
            $selectedTenant = Get-SelectedTenantId
            if (-not $backendMode) {
                if ($script:WdlGuiSessionAuthenticated) {
                    Clear-GuiSessionAuthentication
                    Write-GuiConsole -Message 'Target tenant changed; sign in again to create a Direct-mode session for the selected tenant.'
                }
                $ui.TenantScope.Text = if ($selectedTenant) { Get-TenantDisplayName -TenantId $selectedTenant } else { 'Determined by sign-in' }
            }
            Set-GuiCapabilities
        }
    })

    $btnClearActivity.Add_Click({
        $consoleBox.Clear()
        Write-GuiConsole -Message 'Activity log cleared.'
    })

    $form.Add_FormClosing({
        param($sender,$eventArgs)

        if ($script:WdlGuiBusy) {
            $eventArgs.Cancel = $true
            Write-GuiConsole -Message 'Close request ignored because an operation is still running.'
        }
        else {
            Clear-GuiSessionAuthentication
        }
    })

    $form.Add_Shown({
        $environmentName = if ($isWinPE) { 'Windows PE' } else { 'Windows' }
        $connectionText = if ($backendMode) { 'Backend mode' } else { "Direct mode; authentication method: $Method" }
        Write-GuiConsole -Message "WindowsDeviceLink dashboard opened in $environmentName. $connectionText."
        Set-GuiBusy -Busy $true -StatusText 'Loading local state...'
        try {
            Refresh-LocalView
            if ($backendMode) {
                try {
                    Set-GuiStatus 'Checking cloud association...'
                    $cloud = Refresh-CloudView -WriteCommand
                    Write-GuiObject $cloud
                    Set-GuiStatus 'Device and cloud state loaded'
                }
                catch {
                    $ui.CloudState.Text = 'Check failed'
                    $ui.CloudTenant.Text = 'Unavailable'
                    $ui.CloudId.Text = 'Unavailable'
                    $ui.CloudChecked.Text = (Get-Date).ToString('HH:mm:ss')
                    $ui.CloudAccent.BackColor = $colorCardAccent
                    $cloudCard.BackColor = $colorCardTint
                    Write-GuiConsole -Message ("Automatic cloud check failed: " + $_.Exception.Message) -ErrorMessage
                    Set-GuiStatus 'Local state loaded; cloud check unavailable'
                }
            }
            else {
                Set-GuiStatus 'Local state loaded; sign in to check the cloud association'
            }
        }
        catch { Show-GuiError $_.Exception.Message }
        finally { Set-GuiBusy -Busy $false }
    })

    function Resize-GuiLayout {
        $fullWidth = [Math]::Max(760,$content.ClientSize.Width - 32)
        $gap = 14
        $halfWidth = [Math]::Floor(($fullWidth - $gap) / 2)

        $deviceCard.Location = [System.Drawing.Point]::new(14,12)
        $deviceCard.Size = [System.Drawing.Size]::new([int]$halfWidth,126)
        $connectionCard.Location = [System.Drawing.Point]::new((14 + $halfWidth + $gap),12)
        $connectionCard.Size = [System.Drawing.Size]::new([int]$halfWidth,126)

        $associationCard.Location = [System.Drawing.Point]::new(14,150)
        $associationCard.Size = [System.Drawing.Size]::new([int]$halfWidth,126)
        $cloudCard.Location = [System.Drawing.Point]::new((14 + $halfWidth + $gap),150)
        $cloudCard.Size = [System.Drawing.Size]::new([int]$halfWidth,126)
        $cardsBottom = 276

        foreach ($card in @($deviceCard,$connectionCard,$associationCard,$cloudCard)) {
            foreach ($control in $card.Controls) {
                if ([string]$control.Tag -like 'Value:*') {
                    $control.Width = [Math]::Max(150,$halfWidth - $control.Left - 14)
                }
            }
        }

        $actionsY = $cardsBottom + 12
        $actionsTitle.Location = [System.Drawing.Point]::new(16,$actionsY)
        $actionsPanel.Location = [System.Drawing.Point]::new(14,($actionsY + 26))
        $actionsPanel.Width = $fullWidth

        $assignmentRow.Width = $fullWidth
        $btnAssociateHost.Left = $fullWidth - 16 - $btnAssociateHost.Width
        $btnAssign.Left = $btnAssociateHost.Left - 8 - $btnAssign.Width
        if ($showTenantSelector) {
            $tenantSelector.Left = $btnAssign.Left - 8 - $tenantSelector.Width
            $tenantCaption.Left = $tenantSelector.Left - 88
        }
        $assignmentSeparator.Width = [Math]::Max(480,$fullWidth - 28)

        $activityY = $actionsPanel.Bottom + 14
        $activityTitle.Location = [System.Drawing.Point]::new(16,$activityY)

        # Align Clear to the same right edge used by the action buttons.
        # Action buttons sit 16 px inside the right edge of the Actions panel.
        $clearX = $actionsPanel.Right - 16 - $btnClearActivity.Width
        $clearY = $activityY - 5
        $btnClearActivity.Location = [System.Drawing.Point]::new($clearX,$clearY)

        $activityCard.Location = [System.Drawing.Point]::new(14,($activityY + 26))
        $activityCard.Width = $fullWidth
        $consoleBox.Width = $fullWidth - 24

        # Let Activity consume the remaining client area instead of leaving an
        # unused band below the log on taller WinPE and Windows displays.
        $activityHeight = [Math]::Max(118,$content.ClientSize.Height - $activityCard.Top - 12)
        $activityCard.Height = $activityHeight
        $consoleBox.Height = [Math]::Max(96,$activityHeight - 22)


        foreach ($row in @($rowTools,$rowExport,$rowOffboard)) {
            $row.Panel.Width = $fullWidth

            $buttons = @($row.Buttons)
            $right = $fullWidth - 16
            for ($i = $buttons.Count - 1; $i -ge 0; $i--) {
                $right -= $buttons[$i].Width
                $buttons[$i].Left = $right
                $right -= 7
            }

            $row.Panel.Controls |
                Where-Object { $_ -is [System.Windows.Forms.Panel] -and $_.Height -eq 1 } |
                ForEach-Object { $_.Width = [Math]::Max(480,$fullWidth - 28) }
        }

        $requiredHeight = $activityCard.Bottom + 2
        $availableHeight = [Math]::Max(0,$content.ClientSize.Height - 2)
        $scrollTolerance = 10

        if ($requiredHeight -gt ($availableHeight + $scrollTolerance)) {
            $content.AutoScroll = $true
            $content.HorizontalScroll.Enabled = $false
            $content.HorizontalScroll.Visible = $false
            $content.AutoScrollMinSize = [System.Drawing.Size]::new(1,$requiredHeight)
        }
        else {
            $content.AutoScrollMinSize = [System.Drawing.Size]::Empty
            $content.AutoScroll = $false
            $content.VerticalScroll.Value = 0
        }
    }

    $form.Add_Resize({ Resize-GuiLayout })

    try {
        Resize-GuiLayout
        [void]$form.ShowDialog()
    }
    finally {
        $toolTip.Dispose()
        $form.Dispose()
        if ($formIcon) { $formIcon.Dispose() }
        Remove-Variable WdlGuiLocalAssociation -Scope Script -ErrorAction SilentlyContinue
        Remove-Variable WdlGuiCloudStatus -Scope Script -ErrorAction SilentlyContinue
        Remove-Variable WdlGuiBusy -Scope Script -ErrorAction SilentlyContinue
        Remove-Variable WdlGuiSupport -Scope Script -ErrorAction SilentlyContinue
    }
}