workflow/default/ux/MainWindow.ps1

<#!
.SYNOPSIS
    Interactive picker for OSDCloud operating system catalog entries.
#>

[CmdletBinding()]
param()
#================================================
Add-Type -AssemblyName PresentationCore, PresentationFramework, WindowsBase
#================================================
# XAML
# Load the WPF layout from the companion XAML file so this script can wire data
# and event handlers without embedding UI markup in PowerShell.
$xamlfile = Get-Item -Path "$PSScriptRoot\MainWindow.xaml"
$xaml = Get-Content $xamlfile.FullName
$stringReader = [System.IO.StringReader]::new($xaml)
$xmlReader = [System.Xml.XmlReader]::Create($stringReader)
$window = [Windows.Markup.XamlReader]::Load($xmlReader)
#================================================
# XAML - Window Title
$deviceTitleParts = @()
$OSDCloudModuleVersion = Get-OSDCloudModuleVersion
if (-not [string]::IsNullOrWhiteSpace($OSDCloudModuleVersion)) {
    $deviceTitleParts += $OSDCloudModuleVersion
}
if ($deviceTitleParts.Count -gt 0) {
    $window.Title = "OSDCloud version $($deviceTitleParts -join ' - ')"
}
#================================================
# Logo
$logoImage = $window.FindName('LogoImage')
if ($logoImage) {
    $logoImage.Source = "$PSScriptRoot\logo.png"
}
#================================================
# Menu Items
# Resolve named controls once and attach event handlers for tools that are useful
# during WinPE deployment troubleshooting.
$RunCmdPrompt = $window.FindName("RunCmdPrompt")
$RunPowerShell = $window.FindName("RunPowerShell")
$RunPwsh = $window.FindName("RunPwsh")
$PrivacyMenuItem = $window.FindName("PrivacyMenuItem")
$LogsMenuItem = $window.FindName("LogsMenuItem")
$WMIMenuItem = $window.FindName("WMIMenuItem")

$RunCmdPrompt.Add_Click({
        try {
            Start-Process -FilePath "cmd.exe"
        }
        catch {
            [System.Windows.MessageBox]::Show("Failed to open CMD Prompt: $($_.Exception.Message)", "Error", "OK", "Error") | Out-Null
        }
    })

$RunPowerShell.Add_Click({
        try {
            Start-Process -FilePath "powershell.exe"
        }
        catch {
            [System.Windows.MessageBox]::Show("Failed to open PowerShell: $($_.Exception.Message)", "Error", "OK", "Error") | Out-Null
        }
    })

if ($RunPwsh) {
    # PowerShell 7 is optional in the environment, so only show the menu item when
    # pwsh.exe can be resolved on the current path.
    $pwshCommand = Get-Command -Name 'pwsh.exe' -ErrorAction Ignore
    if ($pwshCommand) {
        $script:PwshPath = $pwshCommand.Source
        $RunPwsh.Visibility = [System.Windows.Visibility]::Visible
        $RunPwsh.Add_Click({
                try {
                    Start-Process -FilePath $script:PwshPath
                }
                catch {
                    [System.Windows.MessageBox]::Show("Failed to open PowerShell 7: $($_.Exception.Message)", "Error", "OK", "Error") | Out-Null
                }
            })
    }
 else {
        $RunPwsh.Visibility = [System.Windows.Visibility]::Collapsed
    }
}

$PrivacyMenuItem.Add_Click({
        $privacyMessage = @"
OSDCloud collects analytic data during the deployment process to help improve the product and user experience.
No personally identifiable information (PII) is collected, and all data is anonymized to protect user privacy.
 
Collected data includes information about the deployment environment and system configuration.
By using OSDCloud, you consent to the collection of analytic data as outlined in the privacy policy
 
https://github.com/OSDeploy/OSDCloud/blob/main/PRIVACY.md
"@

        [System.Windows.MessageBox]::Show($privacyMessage, "OSDCloud Privacy Statement", "OK", "Information") | Out-Null
    })

function Add-NoLogsMenuEntry {
    param(
        [Parameter(Mandatory)]
        [System.Windows.Controls.MenuItem]$MenuItem
    )

    $noLogsItem = [System.Windows.Controls.MenuItem]::new()
    $noLogsItem.Header = 'No logs found'
    $noLogsItem.IsEnabled = $false
    $MenuItem.Items.Add($noLogsItem) | Out-Null
}
function Set-ClipboardText {
    param([string]$Text)
    # Clipboard access can be temporarily locked by another process; retry briefly
    # so click-to-copy still works in most interactive sessions.
    $maxRetries = 5
    for ($i = 0; $i -lt $maxRetries; $i++) {
        try {
            [System.Windows.Clipboard]::SetText($Text)
            return
        }
        catch {
            if ($i -lt ($maxRetries - 1)) {
                Start-Sleep -Milliseconds 100
            }
            else {
                Write-Warning "Failed to copy to clipboard: $_"
            }
        }
    }
}
function Set-LogsMenuItems {
    # Rebuild the log menu from the temp log folder, excluding WMI captures that
    # have their own menu.
    $LogsMenuItem.Items.Clear()

    $logsRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'osdcloud-logs'
    if (-not (Test-Path -LiteralPath $logsRoot)) {
        Add-NoLogsMenuEntry -MenuItem $LogsMenuItem
        return
    }

    $logFiles = Get-ChildItem -LiteralPath $logsRoot -File -ErrorAction SilentlyContinue | Sort-Object -Property Name
    $logFiles = $logFiles | Where-Object { $_.Name -NotLike "Win32_*.txt" }
    if (-not $logFiles) {
        Add-NoLogsMenuEntry -MenuItem $LogsMenuItem
        return
    }

    foreach ($logFile in $logFiles) {
        $logMenuItem = [System.Windows.Controls.MenuItem]::new()
        # Double underscores so WPF renders underscores literally instead of mnemonics
        $logMenuItem.Header = $logFile.Name -replace '_', '__'
        $logMenuItem.Tag = $logFile.FullName

        $logMenuItem.Add_Click({
                $eventSender = $args[0]
                $logPath = [string]$eventSender.Tag
                if (-not (Test-Path -LiteralPath $logPath)) {
                    [System.Windows.MessageBox]::Show('Log file not found.', 'Open Log', 'OK', 'Warning') | Out-Null
                    return
                }

                try {
                    Start-Process -FilePath 'notepad.exe' -ArgumentList @("`"$logPath`"") -ErrorAction Stop
                }
                catch {
                    [System.Windows.MessageBox]::Show("Failed to open log: $($_.Exception.Message)", 'Open Log', 'OK', 'Error') | Out-Null
                }
            })

        $LogsMenuItem.Items.Add($logMenuItem) | Out-Null
    }
}
Set-LogsMenuItems
function Set-WMIMenuItems {
    # WMI inventory files are separated from deployment logs to keep hardware data
    # easy to inspect during troubleshooting.
    $WMIMenuItem.Items.Clear()

    $logsRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'osdcloud-logs'
    if (-not (Test-Path -LiteralPath $logsRoot)) {
        Add-NoLogsMenuEntry -MenuItem $WMIMenuItem
        return
    }

    $logFiles = Get-ChildItem -LiteralPath $logsRoot -File -ErrorAction SilentlyContinue | Sort-Object -Property Name
    $logFiles = $logFiles | Where-Object { $_.Name -Like "Win32_*.txt" }
    if (-not $logFiles) {
        Add-NoLogsMenuEntry -MenuItem $WMIMenuItem
        return
    }

    foreach ($logFile in $logFiles) {
        $logMenuItem = [System.Windows.Controls.MenuItem]::new()
        # Double underscores so WPF renders underscores literally instead of mnemonics
        $logMenuItem.Header = $logFile.Name -replace '_', '__'
        $logMenuItem.Tag = $logFile.FullName

        $logMenuItem.Add_Click({
                $eventSender = $args[0]
                $logPath = [string]$eventSender.Tag
                if (-not (Test-Path -LiteralPath $logPath)) {
                    [System.Windows.MessageBox]::Show('Log file not found.', 'Open Log', 'OK', 'Warning') | Out-Null
                    return
                }

                try {
                    Start-Process -FilePath 'notepad.exe' -ArgumentList @("`"$logPath`"") -ErrorAction Stop
                }
                catch {
                    [System.Windows.MessageBox]::Show("Failed to open log: $($_.Exception.Message)", 'Open Log', 'OK', 'Error') | Out-Null
                }
            })

        $WMIMenuItem.Items.Add($logMenuItem) | Out-Null
    }
}
Set-WMIMenuItems
#================================================
# TaskSequence
$TaskSequenceCombo = $window.FindName("TaskSequenceCombo")
$taskSequenceFlows = $global:OSDCloudDeploy.WorkflowTasks.Name
if ($null -eq $taskSequenceFlows) { $taskSequenceFlows = @() }
$TaskSequenceCombo.ItemsSource = $taskSequenceFlows
$TaskSequenceCombo.SelectedIndex = 0
$TaskSequenceCombo.Add_SelectionChanged({
        if ($SummaryTaskSequenceText) {
            $value = [string]$TaskSequenceCombo.SelectedItem
            $SummaryTaskSequenceText.Text = if (-not [string]::IsNullOrWhiteSpace($value)) { $value } else { 'Not selected' }
        }
    })
#================================================
# OperatingSystemValues
# Prefer workflow-provided choices when present; otherwise derive selectable OS
# names from the loaded operating system catalog.
if ($global:OSDCloudDeploy.OperatingSystemValues) {
    $OperatingSystemValues = $global:OSDCloudDeploy.OperatingSystemValues
}
else {
    $OperatingSystemValues = $global:OSDCoreOperatingSystems.OperatingSystem | Sort-Object -Unique | Sort-Object -Descending
}
$OperatingSystemCombo = $window.FindName("OperatingSystemCombo")
$OperatingSystemCombo.ItemsSource = $OperatingSystemValues
#================================================
# OperatingSystemDefault
if ($global:OSDCloudDeploy.OperatingSystem) {
    $OperatingSystemDefault = $global:OSDCloudDeploy.OperatingSystem
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OperatingSystem = $OperatingSystemDefault"
}
if ($OperatingSystemDefault -and ($OperatingSystemValues -contains $OperatingSystemDefault)) {
    $OperatingSystemCombo.SelectedItem = $OperatingSystemDefault
}
elseif ($OperatingSystemValues) {
    $OperatingSystemCombo.SelectedIndex = 0
}
else {
    $OperatingSystemCombo.SelectedIndex = -1
}
#================================================
# OSEditionValues
# GlobalVariable Configuration
# Environment Configuration
# Workflow Configuration
if ($global:OSDCloudDeploy.OSEditionValues.Edition) {
    $OSEditionValues = $global:OSDCloudDeploy.OSEditionValues.Edition
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OSEditionValues = $OSEditionValues"
}
else {
    @()
}
$OSEditionCombo = $window.FindName("OSEditionCombo")
$OSEditionCombo.ItemsSource = $OSEditionValues
#================================================
# OSEditionDefault
if ($global:OSDCloudDeploy.OSEdition) {
    $OSEditionDefault = $global:OSDCloudDeploy.OSEdition
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OSEdition = $OSEditionDefault"
}
if ($OSEditionDefault) {
    $OSEditionCombo.SelectedItem = $OSEditionDefault
}
elseif ($OperatingSystemValues) {
    $OSEditionCombo.SelectedIndex = 0
}
else {
    $OSEditionCombo.SelectedIndex = -1
}
#================================================
# OSActivationValues
# GlobalVariable Configuration
# Environment Configuration
# Workflow Configuration
if ($global:OSDCloudDeploy.OSActivationValues) {
    $OSActivationValues = $global:OSDCloudDeploy.OSActivationValues
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OSActivationValues = $OSActivationValues"
}
else {
    @()
}
$OSActivationCombo = $window.FindName("OSActivationCombo")
$OSActivationCombo.ItemsSource = $OSActivationValues
#================================================
# OSActivationDefault
if ($global:OSDCloudDeploy.OSActivation) {
    $OSActivationDefault = $global:OSDCloudDeploy.OSActivation
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OSActivation = $OSActivationDefault"
}
if ($OSActivationDefault -and ($OSActivationValues -contains $OSActivationDefault)) {
    $OSActivationCombo.SelectedItem = $OSActivationDefault
}
elseif ($OSActivationValues) {
    $OSActivationCombo.SelectedIndex = 0
}
else {
    $OSActivationCombo.SelectedIndex = -1
}
#================================================
# OSLanguageCodeValues
# GlobalVariable Configuration
# Environment Configuration
# Workflow Configuration
if ($global:OSDCloudDeploy.OSLanguageCodeValues) {
    $OSLanguageCodeValues = $global:OSDCloudDeploy.OSLanguageCodeValues
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OSLanguageCodeValues = $OSLanguageCodeValues"
}
# Catalog Configuration
else {
    $OSLanguageCodeValues = $global:OSDCoreOperatingSystems.OSLanguageCode | Sort-Object -Unique | Sort-Object -Descending
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] Catalog OSLanguageCodeValues = $OSLanguageCodeValues"
}
$OSLanguageCodeCombo = $window.FindName("OSLanguageCodeCombo")
$OSLanguageCodeCombo.ItemsSource = $OSLanguageCodeValues
#================================================
# OSLanguageCodeDefault
if ($global:OSDCloudDeploy.OSLanguageCode) {
    $OSLanguageCodeDefault = $global:OSDCloudDeploy.OSLanguageCode
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] OSLanguage = $OSLanguageCodeDefault"
}
if ($OSLanguageCodeDefault -and ($OSLanguageCodeValues -contains $OSLanguageCodeDefault)) {
    $OSLanguageCodeCombo.SelectedItem = $OSLanguageCodeDefault
}
elseif ($OSLanguageCodeValues) {
    $OSLanguageCodeCombo.SelectedIndex = 0
}
else {
    $OSLanguageCodeCombo.SelectedIndex = -1
}
#================================================
# DriverPackCombo
# GlobalVariable Configuration
# Environment Configuration
# Workflow Configuration
#================================================
# Import the DriverPack Catalog
# Build the driver pack picker from the module catalog, while keeping explicit
# fallback choices for no driver pack or Microsoft Update Catalog lookup.
$DriverPackCatalog = @('None', 'Microsoft Update Catalog')
if ($global:OSDCoreDriverPacks) {
    $DriverPackCatalog += $global:OSDCoreDriverPacks | ForEach-Object { $_.Name }
}
$DriverPackCombo = $window.FindName("DriverPackCombo")
$DriverPackCombo.ItemsSource = $DriverPackCatalog
if ($global:OSDCloudDeploy.DriverPackName) {
    $DriverPackCombo.SelectedValue = $global:OSDCloudDeploy.DriverPackName
}
else {
    $DriverPackCombo.SelectedIndex = 0
}
#================================================
# OSDCoreDevice
$deviceTextBindings = @{
    deviceBiosReleaseDateText   = 'BiosReleaseDate'
    deviceBiosVersionText       = 'BiosVersion'
    deviceComputerSystemSKUText = 'ComputerSystemSKU'
    deviceIsAutopilotSpecText   = 'IsAutopilotSpec'
    deviceIsTpmSpecText         = 'IsTpmSpec'
    deviceOSDManufacturerText   = 'OSDManufacturer'
    deviceOSDModelText          = 'OSDModel'
    deviceOSDProductText        = 'OSDProduct'
    deviceSerialNumberText      = 'SerialNumber'
    deviceUUIDText              = 'UUID'
}
foreach ($deviceTextBinding in $deviceTextBindings.GetEnumerator()) {
    $window.FindName($deviceTextBinding.Key).Text = $global:OSDCoreDevice.$($deviceTextBinding.Value)
}

function Add-ClipboardTextBlockHandler {
    param(
        [Parameter(Mandatory)]
        [System.Windows.Controls.TextBlock]$TextBlock
    )

    $TextBlock.Add_MouseLeftButtonUp({
            $eventSender = $args[0]
            $text = [string]$eventSender.Text
            if ([string]::IsNullOrWhiteSpace($text)) {
                return
            }

            Set-ClipboardText -Text $text
        })
}
#================================================
# deviceSerialNumber
Add-ClipboardTextBlockHandler -TextBlock $window.FindName("deviceSerialNumberText")
#================================================
# deviceHardwareHash
$deviceHardwareHash = $global:OSDCoreDevice.HardwareHash
$deviceHardwareHashLabelText = $window.FindName("deviceHardwareHashLabelText")
$deviceHardwareHashText = $window.FindName("deviceHardwareHashText")
if (-not [string]::IsNullOrWhiteSpace([string]$deviceHardwareHash)) {
    $deviceHardwareHashLabelText.Visibility = [System.Windows.Visibility]::Visible
    $deviceHardwareHashText.Text = 'Copy to Clipboard'
    $deviceHardwareHashText.Visibility = [System.Windows.Visibility]::Visible
    $deviceHardwareHashText.Add_MouseLeftButtonUp({
            Set-ClipboardText -Text ([string]$deviceHardwareHash)
        })
}
#================================================
# deviceUUID
Add-ClipboardTextBlockHandler -TextBlock $window.FindName("deviceUUIDText")
#================================================
$SelectedOSLanguageText = $window.FindName("SelectedOSLanguageText")
$SelectedIdText = $window.FindName("SelectedIdText")
$SelectedFileNameText = $window.FindName("SelectedFileNameText")
$DriverPackUrlText = $window.FindName("DriverPackUrlText")
$DriverPackUrlText.Text = [string]$global:OSDCloudDeploy.DriverPackCloudObject.Url
$StartButton = $window.FindName("StartButton")
$StartButton.IsEnabled = $false

function Get-ComboValue {
    # Normalize ComboBox selections so downstream filters can treat empty UI values
    # as missing input instead of empty strings.
    param(
        [Parameter(Mandatory)]
        [System.Windows.Controls.ComboBox]$ComboBox
    )

    $value = $ComboBox.SelectedItem
    if ($null -eq $value) {
        return $null
    }

    $text = [string]$value
    if ([string]::IsNullOrWhiteSpace($text)) {
        return $null
    }

    return $text
}
function Set-StartButtonState {
    # Deployment can only proceed after the current picker state resolves to an OS
    # catalog object.
    $StartButton.IsEnabled = ($null -ne $global:OSDCloudDeploy.OperatingSystemCloudObject)
}
function Update-SelectedDetails {
    param(
        [Parameter()]
        $Item
    )

    if (-not $Item) {
        $SelectedIdText.Text = 'No matching catalog entry.'
        $SelectedOSLanguageText.Text = '-'
        $SelectedFileNameText.Text = '-'
        return
    }

    $SelectedIdText.Text = [string]$Item.Id
    $SelectedOSLanguageText.Text = if ($Item.OSLanguage) {
        [string]$Item.OSLanguage
    }
    elseif ($Item.OSLanguageCode) {
        [string]$Item.OSLanguageCode
    }
    else {
        '-'
    }
    $SelectedFileNameText.Text = [string]$Item.FileName
}
function Update-OsResults {
    # Keep catalog filtering centralized so every picker change refreshes the same
    # selected OS object and detail summary.
    $updateOperatingSystem = Get-ComboValue -ComboBox $OperatingSystemCombo
    $updateOSEdition = Get-ComboValue -ComboBox $OSEditionCombo
    $updateOSActivation = Get-ComboValue -ComboBox $OSActivationCombo
    $updateOSLanguageCode = Get-ComboValue -ComboBox $OSLanguageCodeCombo

    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] updateOperatingSystem = $updateOperatingSystem"
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] updateOSEdition = $updateOSEdition"
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] updateOSActivation = $updateOSActivation"
    Write-Verbose "[$(Get-Date -format s)] [MainWindow.ps1] updateOSLanguageCode = $updateOSLanguageCode"

    $global:OSDCloudDeploy.OperatingSystemCloudObject = $global:OSDCoreOperatingSystems | `
        Where-Object { $_.OperatingSystem -match $updateOperatingSystem } | `
        Where-Object { $_.OSActivation -eq $updateOSActivation } | `
        Where-Object { $_.OSLanguageCode -eq $updateOSLanguageCode } | Select-Object -First 1

    if (-not $global:OSDCloudDeploy.OperatingSystemCloudObject) {
        throw "No Operating System found for OperatingSystem: $updateOperatingSystem, OSActivation: $updateOSActivation, OSLanguageCode: $updateOSLanguageCode. Please check your OSDCloud OperatingSystems."
    }

    $global:OSDCloudDeploy.OperatingSystemCacheObject = Get-OSDCoreOperatingSystemCacheObject -OperatingSystemCloudObject $global:OSDCloudDeploy.OperatingSystemCloudObject
    $script:SelectedImage = $global:OSDCloudDeploy.OperatingSystemCloudObject

    if ($updateOSEdition -match 'Home') {
        $OSActivationCombo.SelectedValue = 'Retail'
        $OSActivationCombo.IsEnabled = $false
    }
    if ($updateOSEdition -match 'Education') {
        $OSActivationCombo.IsEnabled = $true
    }
    if ($updateOSEdition -match 'Enterprise') {
        $OSActivationCombo.SelectedValue = 'Volume'
        $OSActivationCombo.IsEnabled = $false
    }
    if ($updateOSEdition -match 'Pro') {
        $OSActivationCombo.IsEnabled = $true
    }

    Update-SelectedDetails -Item $script:SelectedImage

    Set-StartButtonState
}
function Update-DriverPackResults {
    # Persist the selected driver pack name and matching catalog object immediately
    # so the final deployment state reflects the latest UI selection.
    $selectedDriverPackName = Get-ComboValue -ComboBox $DriverPackCombo
    $global:OSDCloudDeploy.DriverPackName = $selectedDriverPackName
    $global:OSDCloudDeploy.DriverPackCloudObject = $global:OSDCoreDriverPacks | Where-Object { $_.Name -eq $selectedDriverPackName }
    $global:OSDCloudDeploy.DriverPackCacheObject = Get-OSDCoreDriverPackCacheObject -DriverPackCloudObject $global:OSDCloudDeploy.DriverPackCloudObject
    $DriverPackUrlText.Text = [string]$global:OSDCloudDeploy.DriverPackCloudObject.Url
}
$DriverPackCombo.Add_SelectionChanged({ Update-DriverPackResults })
$OperatingSystemCombo.Add_SelectionChanged({ Update-OsResults })
$OSActivationCombo.Add_SelectionChanged({ Update-OsResults })
$OSEditionCombo.Add_SelectionChanged({ Update-OsResults })
$OSLanguageCodeCombo.Add_SelectionChanged({ Update-OsResults })
$script:SelectionConfirmed = $false

$StartButton.Add_Click({
        $script:SelectionConfirmed = $true
        $window.DialogResult = $true
        $window.Close()
    })

Update-OsResults

# Initialize Configuration summary with current values
if ($SummaryTaskSequenceText) {
    $value = [string]$TaskSequenceCombo.SelectedItem
    $SummaryTaskSequenceText.Text = if (-not [string]::IsNullOrWhiteSpace($value)) { $value } else { 'Not selected' }
}

$null = $window.ShowDialog()

if ($script:SelectionConfirmed) {
    #================================================
    # Local Variables
    # Convert the selected UI values into workflow and catalog objects that the
    # deployment engine expects after the dialog closes.
    $OSDCloudWorkflowTaskName = $TaskSequenceCombo.SelectedValue
    $OSDCloudWorkflowTaskObject = $global:OSDCloudDeploy.WorkflowTasks | Where-Object { $_.Name -eq $OSDCloudWorkflowTaskName } | Select-Object -First 1
    $OperatingSystemCloudObject = $global:OSDCloudDeploy.OperatingSystemCloudObject
    $OSEditionId = $global:OSDCloudDeploy.OSEditionValues | Where-Object { $_.Edition -eq $OSEditionCombo.SelectedValue } | Select-Object -ExpandProperty EditionId
    #================================================
    # Global Variables
    # Write the confirmed selection back to OSDCloudDeploy so the next deployment
    # phase can download the image and run the selected workflow task.
    # $global:OSDCloudDeploy.DriverPackName = $DriverPackName
    # $global:OSDCloudDeploy.DriverPackCloudObject = $DriverPackCloudObject
    $global:OSDCloudDeploy.WorkflowTaskName = $OSDCloudWorkflowTaskName
    $global:OSDCloudDeploy.WorkflowTaskObject = $OSDCloudWorkflowTaskObject
    $global:OSDCloudDeploy.OperatingSystemCloudObject = $OperatingSystemCloudObject
    $global:OSDCloudDeploy.OperatingSystemCacheObject = Get-OSDCoreOperatingSystemCacheObject -OperatingSystemCloudObject $OperatingSystemCloudObject
    $global:OSDCloudDeploy.DriverPackCacheObject = Get-OSDCoreDriverPackCacheObject -DriverPackCloudObject $global:OSDCloudDeploy.DriverPackCloudObject
    $global:OSDCloudDeploy.OperatingSystem = $OperatingSystemCloudObject.OperatingSystem
    $global:OSDCloudDeploy.OSActivation = $OperatingSystemCloudObject.OSActivation
    $global:OSDCloudDeploy.OSBuild = $OperatingSystemCloudObject.OSBuild
    $global:OSDCloudDeploy.OSBuildVersion = $OperatingSystemCloudObject.OSBuildVersion
    $global:OSDCloudDeploy.OSEdition = Get-ComboValue -ComboBox $OSEditionCombo
    $global:OSDCloudDeploy.OSEditionId = $OSEditionId
    $global:OSDCloudDeploy.OSLanguageCode = $OperatingSystemCloudObject.OSLanguageCode
    $global:OSDCloudDeploy.OSVersion = $OperatingSystemCloudObject.OSVersion
    $global:OSDCloudDeploy.TimeStart = (Get-Date)

    $LogsPath = "$env:TEMP\osdcloud-logs"
    if (-not (Test-Path -Path $LogsPath)) {
        New-Item -Path $LogsPath -ItemType Directory -Force | Out-Null
    }
    # Persist the resolved deployment state for post-run troubleshooting.
    $global:OSDCloudDeploy | ConvertTo-Json | Out-File -FilePath "$LogsPath\OSDCloudDeploy.json" -Encoding utf8 -Width 2000 -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
}