private/Initialize-SplitterUi.ps1
|
function Initialize-SplitterUi { [CmdletBinding()] param( [Parameter(Mandatory)] [object] $Window, [hashtable] $Controls ) if ($Controls) { $controls = $Controls } else { $controls = @{ CloseWindowButton = $Window.FindControl('CloseWindowButton') ExitMenuItem = $Window.FindControl('ExitMenuItem') StatusText = $Window.FindControl('StatusText') FooterText = $Window.FindControl('FooterText') SourceIsoTextBox = $Window.FindControl('SourceIsoTextBox') BrowseSourceIsoButton = $Window.FindControl('BrowseSourceIsoButton') EditionNamesTextBox = $Window.FindControl('EditionNamesTextBox') WorkingRootTextBox = $Window.FindControl('WorkingRootTextBox') OutputRootTextBox = $Window.FindControl('OutputRootTextBox') OutputBaseNameTextBox = $Window.FindControl('OutputBaseNameTextBox') LabelPrefixTextBox = $Window.FindControl('LabelPrefixTextBox') SkipBootableIsoCheckBox = $Window.FindControl('SkipBootableIsoCheckBox') DesktopExperienceCheckBox = $Window.FindControl('DesktopExperienceCheckBox') ServerCoreCheckBox = $Window.FindControl('ServerCoreCheckBox') DiscoverImagesButton = $Window.FindControl('DiscoverImagesButton') RunSplitButton = $Window.FindControl('RunSplitButton') RunPreflightButton = $Window.FindControl('RunPreflightButton') ImagesListBox = $Window.FindControl('ImagesListBox') MediaLogTextBox = $Window.FindControl('MediaLogTextBox') ServiceMediaRootTextBox = $Window.FindControl('ServiceMediaRootTextBox') DriverPathTextBox = $Window.FindControl('DriverPathTextBox') PackagePathTextBox = $Window.FindControl('PackagePathTextBox') ServiceIndexesTextBox = $Window.FindControl('ServiceIndexesTextBox') UnattendPathTextBox = $Window.FindControl('UnattendPathTextBox') UnattendDestinationTextBox = $Window.FindControl('UnattendDestinationTextBox') ServiceRecurseDriversCheckBox = $Window.FindControl('ServiceRecurseDriversCheckBox') ServiceForceUnsignedDriversCheckBox = $Window.FindControl('ServiceForceUnsignedDriversCheckBox') ServiceIgnoreCheckCheckBox = $Window.FindControl('ServiceIgnoreCheckCheckBox') ServicePreventPendingCheckBox = $Window.FindControl('ServicePreventPendingCheckBox') AddDriverButton = $Window.FindControl('AddDriverButton') AddPackageButton = $Window.FindControl('AddPackageButton') AddUnattendButton = $Window.FindControl('AddUnattendButton') AddPayloadFileButton = $Window.FindControl('AddPayloadFileButton') PayloadSourceTextBox = $Window.FindControl('PayloadSourceTextBox') PayloadDestinationTextBox = $Window.FindControl('PayloadDestinationTextBox') ServicingLogTextBox = $Window.FindControl('ServicingLogTextBox') ServicingSelectionsListBox = $Window.FindControl('ServicingSelectionsListBox') BuildMediaRootTextBox = $Window.FindControl('BuildMediaRootTextBox') BuildOutputIsoTextBox = $Window.FindControl('BuildOutputIsoTextBox') BuildLabelTextBox = $Window.FindControl('BuildLabelTextBox') BuildSourceImageTextBox = $Window.FindControl('BuildSourceImageTextBox') BuildDestinationWimTextBox = $Window.FindControl('BuildDestinationWimTextBox') BuildIndexesTextBox = $Window.FindControl('BuildIndexesTextBox') BuildBootableIsoButton = $Window.FindControl('BuildBootableIsoButton') ExportInstallImageButton = $Window.FindControl('ExportInstallImageButton') BuildLogTextBox = $Window.FindControl('BuildLogTextBox') LogTextBox = $Window.FindControl('LogTextBox') ResultsListBox = $Window.FindControl('ResultsListBox') } } $optionalControls = @( 'CloseWindowButton', 'ExitMenuItem', 'MediaLogTextBox', 'ServiceMediaRootTextBox', 'DriverPathTextBox', 'PackagePathTextBox', 'ServiceIndexesTextBox', 'UnattendPathTextBox', 'UnattendDestinationTextBox', 'ServiceRecurseDriversCheckBox', 'ServiceForceUnsignedDriversCheckBox', 'ServiceIgnoreCheckCheckBox', 'ServicePreventPendingCheckBox', 'AddDriverButton', 'AddPackageButton', 'AddUnattendButton', 'AddPayloadFileButton', 'PayloadSourceTextBox', 'PayloadDestinationTextBox', 'ServicingLogTextBox', 'ServicingSelectionsListBox', 'BuildMediaRootTextBox', 'BuildOutputIsoTextBox', 'BuildLabelTextBox', 'BuildSourceImageTextBox', 'BuildDestinationWimTextBox', 'BuildIndexesTextBox', 'BuildBootableIsoButton', 'ExportInstallImageButton', 'BuildLogTextBox' ) foreach ($key in $controls.Keys) { if ($null -eq $controls[$key]) { if ($key -in $optionalControls) { continue } throw "Missing expected UI control '$key'." } } $invokeGetSplitterUiPreflight = ${function:Get-SplitterUiPreflight}.GetNewClosure() $invokeGetSplitterUiExecutionPlan = ${function:Get-SplitterUiExecutionPlan}.GetNewClosure() $invokeInvokeSplitterUiSplit = ${function:Invoke-SplitterUiSplit}.GetNewClosure() $invokeGetInstallImagePath = ${function:Get-InstallImagePath}.GetNewClosure() $invokeGetOscdimgPath = ${function:Get-OscdimgPath}.GetNewClosure() $invokeGetSplitterUiSourceIsoPath = ${function:Get-SplitterUiSourceIsoPath}.GetNewClosure() $appendLog = { param( [string] $Message ) $timestamp = Get-Date -Format 'HH:mm:ss' $line = "[$timestamp] $Message" if ([string]::IsNullOrWhiteSpace($controls.LogTextBox.Text)) { $controls.LogTextBox.Text = $line return } $controls.LogTextBox.Text = "{0}{1}{2}" -f $controls.LogTextBox.Text, [Environment]::NewLine, $line }.GetNewClosure() $appendMediaLog = { param( [string] $Message ) if ($null -eq $controls.MediaLogTextBox) { $appendLog.Invoke($Message) return } $timestamp = Get-Date -Format 'HH:mm:ss' $line = "[$timestamp] $Message" if ([string]::IsNullOrWhiteSpace($controls.MediaLogTextBox.Text)) { $controls.MediaLogTextBox.Text = $line return } $controls.MediaLogTextBox.Text = "{0}{1}{2}" -f $controls.MediaLogTextBox.Text, [Environment]::NewLine, $line }.GetNewClosure() $appendServicingLog = { param( [string] $Message ) if ($null -eq $controls.ServicingLogTextBox) { $appendLog.Invoke($Message) return } $timestamp = Get-Date -Format 'HH:mm:ss' $line = "[$timestamp] $Message" if ([string]::IsNullOrWhiteSpace($controls.ServicingLogTextBox.Text)) { $controls.ServicingLogTextBox.Text = $line return } $controls.ServicingLogTextBox.Text = "{0}{1}{2}" -f $controls.ServicingLogTextBox.Text, [Environment]::NewLine, $line }.GetNewClosure() $appendBuildLog = { param( [string] $Message ) if ($null -eq $controls.BuildLogTextBox) { $appendLog.Invoke($Message) return } $timestamp = Get-Date -Format 'HH:mm:ss' $line = "[$timestamp] $Message" if ([string]::IsNullOrWhiteSpace($controls.BuildLogTextBox.Text)) { $controls.BuildLogTextBox.Text = $line return } $controls.BuildLogTextBox.Text = "{0}{1}{2}" -f $controls.BuildLogTextBox.Text, [Environment]::NewLine, $line }.GetNewClosure() $setListBoxItems = { param( [Parameter(Mandatory)] [object] $ListBox, [Parameter(Mandatory)] [object[]] $Items ) $ListBox.Items.Clear() foreach ($item in $Items) { $ListBox.Items.Add($item) | Out-Null } }.GetNewClosure() $getIndexListFromText = { param( [string] $Text ) if ([string]::IsNullOrWhiteSpace($Text)) { return @() } $values = $Text -split ',' | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } $parsed = [System.Collections.Generic.List[int]]::new() foreach ($value in $values) { [int] $indexValue = 0 if (-not [int]::TryParse($value, [ref] $indexValue)) { throw "Index value '$value' is not a valid integer." } $parsed.Add($indexValue) } return @($parsed.ToArray()) }.GetNewClosure() $appendStreamLines = { param( [Parameter(Mandatory)] [object[]] $StreamRecords, [Parameter(Mandatory)] [scriptblock] $AppendAction ) foreach ($streamRecord in $StreamRecords) { if ($streamRecord -is [System.Management.Automation.VerboseRecord]) { $AppendAction.Invoke($streamRecord.Message) } elseif ($null -ne $streamRecord) { $AppendAction.Invoke($streamRecord.ToString()) } } }.GetNewClosure() $addServicingSelectionItem = { param( [string] $Message ) if ($null -eq $controls.ServicingSelectionsListBox) { return } $timestamp = Get-Date -Format 'HH:mm:ss' $controls.ServicingSelectionsListBox.Items.Add("[$timestamp] $Message") | Out-Null }.GetNewClosure() $selectFilePath = { param( [string] $Title, [string] $InitialPath ) $options = [GliderUI.Avalonia.Platform.Storage.FilePickerOpenOptions]::new() $options.Title = $Title $options.AllowMultiple = $false if (-not [string]::IsNullOrWhiteSpace($InitialPath)) { try { $parentDirectory = if (Test-Path -LiteralPath $InitialPath -PathType Container) { $InitialPath } else { Split-Path -Path $InitialPath -Parent } if (-not [string]::IsNullOrWhiteSpace($parentDirectory) -and (Test-Path -LiteralPath $parentDirectory)) { $directoryUri = [System.Uri]::new($parentDirectory) $options.SuggestedStartLocation = $Window.StorageProvider.TryGetFolderFromPathAsync($directoryUri).WaitForCompleted() } } catch { # Ignore start location errors and continue with platform default picker location. } } $files = $Window.StorageProvider.OpenFilePickerAsync($options).WaitForCompleted() $selectedFile = @($files) | Select-Object -First 1 if ($null -eq $selectedFile -or $null -eq $selectedFile.Path) { return $null } return [System.Uri]::UnescapeDataString($selectedFile.Path.AbsolutePath) }.GetNewClosure() $selectFolderPath = { param( [string] $Title, [string] $InitialPath ) $options = [GliderUI.Avalonia.Platform.Storage.FolderPickerOpenOptions]::new() $options.Title = $Title $options.AllowMultiple = $false if (-not [string]::IsNullOrWhiteSpace($InitialPath)) { try { $startDirectory = if (Test-Path -LiteralPath $InitialPath -PathType Container) { $InitialPath } else { Split-Path -Path $InitialPath -Parent } if (-not [string]::IsNullOrWhiteSpace($startDirectory) -and (Test-Path -LiteralPath $startDirectory)) { $directoryUri = [System.Uri]::new($startDirectory) $options.SuggestedStartLocation = $Window.StorageProvider.TryGetFolderFromPathAsync($directoryUri).WaitForCompleted() } } catch { # Ignore start location errors and continue with platform default picker location. } } $folders = $Window.StorageProvider.OpenFolderPickerAsync($options).WaitForCompleted() $selectedFolder = @($folders) | Select-Object -First 1 if ($null -eq $selectedFolder -or $null -eq $selectedFolder.Path) { return $null } return [System.Uri]::UnescapeDataString($selectedFolder.Path.AbsolutePath) }.GetNewClosure() $controls.WorkingRootTextBox.Text = Join-Path $env:TEMP 'SplitterWork' $controls.OutputRootTextBox.Text = Join-Path $env:TEMP 'SplitterOut' $controls.EditionNamesTextBox.Text = 'Standard' if ($controls.ServiceMediaRootTextBox) { $controls.ServiceMediaRootTextBox.Text = $controls.OutputRootTextBox.Text } if ($controls.BuildMediaRootTextBox) { $controls.BuildMediaRootTextBox.Text = $controls.OutputRootTextBox.Text } if ($controls.BuildOutputIsoTextBox) { $controls.BuildOutputIsoTextBox.Text = Join-Path $controls.OutputRootTextBox.Text 'custom.iso' } if ($controls.BuildLabelTextBox) { $controls.BuildLabelTextBox.Text = 'WINCUSTOM' } if ($controls.UnattendDestinationTextBox -and [string]::IsNullOrWhiteSpace($controls.UnattendDestinationTextBox.Text)) { $controls.UnattendDestinationTextBox.Text = 'autounattend.xml' } if ($controls.PayloadDestinationTextBox -and [string]::IsNullOrWhiteSpace($controls.PayloadDestinationTextBox.Text)) { $controls.PayloadDestinationTextBox.Text = 'sources\$OEM$\$1\Install\PowerShell-7.6.3-win-x64.msi' } $controls.BrowseSourceIsoButton.AddClick({ try { $getSplitterUiSourceIsoPathParams = @{ Window = $Window InitialPath = $controls.SourceIsoTextBox.Text } $selectedPath = & $invokeGetSplitterUiSourceIsoPath @getSplitterUiSourceIsoPathParams if (-not [string]::IsNullOrWhiteSpace($selectedPath)) { $controls.SourceIsoTextBox.Text = $selectedPath $appendMediaLog.Invoke("Selected source ISO: $selectedPath") } else { $appendMediaLog.Invoke('Source ISO selection canceled.') } } catch { $appendMediaLog.Invoke("Source ISO browse failed. $($_.Exception.Message)") } }.GetNewClosure()) if ($controls.ExitMenuItem) { $controls.ExitMenuItem.AddClick({ $Window.Close() }.GetNewClosure()) } if ($controls.CloseWindowButton) { $controls.CloseWindowButton.AddClick({ $Window.Close() }.GetNewClosure()) } $controls.RunPreflightButton.AddClick({ $appendMediaLog.Invoke('Running preflight checks...') $getSplitterUiPreflightParams = @{ SourceIso = $controls.SourceIsoTextBox.Text GetOscdimgPathScript = $invokeGetOscdimgPath } $preflight = & $invokeGetSplitterUiPreflight @getSplitterUiPreflightParams $summary = "Preflight checks: $($preflight.PassedCount)/$($preflight.TotalCount) passed." $controls.StatusText.Text = $summary $appendMediaLog.Invoke($summary) foreach ($check in $preflight.Checks) { $prefix = if ($check.Passed) { 'PASS' } else { 'FAIL' } $appendMediaLog.Invoke("$prefix $($check.Name): $($check.Message)") } }.GetNewClosure()) $controls.DiscoverImagesButton.AddClick({ $setListBoxItems.Invoke($controls.ImagesListBox, @()) if ([string]::IsNullOrWhiteSpace($controls.SourceIsoTextBox.Text)) { $appendMediaLog.Invoke('Source ISO is required before discovery.') return } if (-not (Test-Path -LiteralPath $controls.SourceIsoTextBox.Text)) { $appendMediaLog.Invoke("Source ISO does not exist: $($controls.SourceIsoTextBox.Text)") return } $mount = $null try { $appendMediaLog.Invoke("Mounting source ISO: $($controls.SourceIsoTextBox.Text)") $mountWindowsInstallMediaParams = @{ Path = $controls.SourceIsoTextBox.Text } $mount = Mount-WindowsInstallMedia @mountWindowsInstallMediaParams $getInstallImagePathParams = @{ MediaRoot = $mount.DriveRoot } $imagePath = & $invokeGetInstallImagePath @getInstallImagePathParams $getWindowsInstallImageParams = @{ ImagePath = $imagePath } $images = @(Get-WindowsInstallImage @getWindowsInstallImageParams) $displayLines = [System.Collections.Generic.List[string]]::new() foreach ($image in $images) { $displayLine = '[{0}] {1} | {2}' -f $image.Index, $image.Name, $image.Description $displayLines.Add($displayLine) } $setListBoxItems.Invoke($controls.ImagesListBox, @($displayLines.ToArray())) $appendMediaLog.Invoke("Discovered $($images.Count) source image index(es).") $controls.StatusText.Text = "Discovered $($images.Count) source image index(es)." } catch { $appendMediaLog.Invoke("Image discovery failed. $($_.Exception.Message)") $controls.StatusText.Text = 'Image discovery failed.' } finally { if ($mount) { $appendMediaLog.Invoke('Dismounting source ISO...') $dismountWindowsInstallMediaParams = @{ InputObject = $mount } Dismount-WindowsInstallMedia @dismountWindowsInstallMediaParams } } }.GetNewClosure()) $controls.RunSplitButton.AddClick({ try { $appendLog.Invoke('Preparing execution plan...') $inputState = @{ SourceIso = $controls.SourceIsoTextBox.Text EditionNames = $controls.EditionNamesTextBox.Text WorkingRoot = $controls.WorkingRootTextBox.Text OutputRoot = $controls.OutputRootTextBox.Text OutputBaseName = $controls.OutputBaseNameTextBox.Text LabelPrefix = $controls.LabelPrefixTextBox.Text SkipBootableIso = [bool] $controls.SkipBootableIsoCheckBox.IsChecked IncludeDesktopExperience = [bool] $controls.DesktopExperienceCheckBox.IsChecked IncludeServerCore = [bool] $controls.ServerCoreCheckBox.IsChecked } $getSplitterUiExecutionPlanParams = @{ InputState = $inputState } $plan = & $invokeGetSplitterUiExecutionPlan @getSplitterUiExecutionPlanParams $appendLog.Invoke('Running Split-WindowsInstallMedia...') $appendLog.Invoke(("Editions: {0}" -f ($plan.EditionNames -join ', '))) $invokeSplitterUiSplitParams = @{ SplitParams = $plan.SplitParams } $execution = & $invokeInvokeSplitterUiSplit @invokeSplitterUiSplitParams foreach ($verboseLine in $execution.VerboseLines) { $appendLog.Invoke($verboseLine) } $resultLines = [System.Collections.Generic.List[string]]::new() foreach ($result in $execution.Results) { $isoState = if ($result.IsoBuilt) { 'ISO built' } else { 'Media only' } $resultLine = '{0} | {1} | {2}' -f $result.Edition, $isoState, $result.OutputIso $resultLines.Add($resultLine) } $setListBoxItems.Invoke($controls.ResultsListBox, @($resultLines.ToArray())) if ($execution.Results.Count -gt 0) { $controls.StatusText.Text = "Completed split for $($execution.Results.Count) edition(s)." $appendLog.Invoke($controls.StatusText.Text) } else { $controls.StatusText.Text = 'Split finished with no result objects returned.' $appendLog.Invoke($controls.StatusText.Text) } } catch { $controls.StatusText.Text = 'Split failed.' $appendLog.Invoke("Split failed. $($_.Exception.Message)") } }.GetNewClosure()) if ($controls.AddDriverButton) { $controls.AddDriverButton.AddClick({ try { if ([string]::IsNullOrWhiteSpace($controls.ServiceMediaRootTextBox.Text)) { throw 'Servicing media root is required.' } if ([string]::IsNullOrWhiteSpace($controls.DriverPathTextBox.Text)) { $selectedPath = $selectFolderPath.Invoke('Select Driver Folder', $controls.DriverPathTextBox.Text) if (-not [string]::IsNullOrWhiteSpace($selectedPath)) { $controls.DriverPathTextBox.Text = $selectedPath $appendServicingLog.Invoke("Selected driver path: $selectedPath") $addServicingSelectionItem.Invoke("Selected driver path: $selectedPath") } } if ([string]::IsNullOrWhiteSpace($controls.DriverPathTextBox.Text)) { throw 'Driver path is required.' } $indexes = $getIndexListFromText.Invoke($controls.ServiceIndexesTextBox.Text) $addWindowsDriverParams = @{ MediaRoot = $controls.ServiceMediaRootTextBox.Text DriverPath = $controls.DriverPathTextBox.Text Recurse = [bool] $controls.ServiceRecurseDriversCheckBox.IsChecked ForceUnsigned = [bool] $controls.ServiceForceUnsignedDriversCheckBox.IsChecked Verbose = $true } if ($indexes.Count -gt 0) { $addWindowsDriverParams.Index = $indexes } $appendServicingLog.Invoke('Adding driver(s) to selected image indexes...') $driverStream = & { Add-WindowsDriver @addWindowsDriverParams } 4>&1 $appendStreamLines.Invoke(@($driverStream), $appendServicingLog) $controls.StatusText.Text = 'Driver servicing completed.' $appendServicingLog.Invoke($controls.StatusText.Text) $addServicingSelectionItem.Invoke("Added drivers from $($controls.DriverPathTextBox.Text)") } catch { $controls.StatusText.Text = 'Driver servicing failed.' $appendServicingLog.Invoke("Driver servicing failed. $($_.Exception.Message)") } }.GetNewClosure()) } if ($controls.AddPackageButton) { $controls.AddPackageButton.AddClick({ try { if ([string]::IsNullOrWhiteSpace($controls.ServiceMediaRootTextBox.Text)) { throw 'Servicing media root is required.' } if ([string]::IsNullOrWhiteSpace($controls.PackagePathTextBox.Text)) { $selectedPath = $selectFilePath.Invoke('Select Package File', $controls.PackagePathTextBox.Text) if (-not [string]::IsNullOrWhiteSpace($selectedPath)) { $controls.PackagePathTextBox.Text = $selectedPath $appendServicingLog.Invoke("Selected package path: $selectedPath") $addServicingSelectionItem.Invoke("Selected package path: $selectedPath") } } if ([string]::IsNullOrWhiteSpace($controls.PackagePathTextBox.Text)) { throw 'Package path is required.' } $indexes = $getIndexListFromText.Invoke($controls.ServiceIndexesTextBox.Text) $addWindowsPackageParams = @{ MediaRoot = $controls.ServiceMediaRootTextBox.Text PackagePath = $controls.PackagePathTextBox.Text IgnoreCheck = [bool] $controls.ServiceIgnoreCheckCheckBox.IsChecked PreventPending = [bool] $controls.ServicePreventPendingCheckBox.IsChecked Verbose = $true } if ($indexes.Count -gt 0) { $addWindowsPackageParams.Index = $indexes } $appendServicingLog.Invoke('Adding package(s) to selected image indexes...') $packageStream = & { Add-WindowsPackage @addWindowsPackageParams } 4>&1 $appendStreamLines.Invoke(@($packageStream), $appendServicingLog) $controls.StatusText.Text = 'Package servicing completed.' $appendServicingLog.Invoke($controls.StatusText.Text) $addServicingSelectionItem.Invoke("Added package $($controls.PackagePathTextBox.Text)") } catch { $controls.StatusText.Text = 'Package servicing failed.' $appendServicingLog.Invoke("Package servicing failed. $($_.Exception.Message)") } }.GetNewClosure()) } if ($controls.AddUnattendButton) { $controls.AddUnattendButton.AddClick({ try { if ([string]::IsNullOrWhiteSpace($controls.ServiceMediaRootTextBox.Text)) { throw 'Servicing media root is required.' } if ([string]::IsNullOrWhiteSpace($controls.UnattendPathTextBox.Text)) { $selectedPath = $selectFilePath.Invoke('Select Unattend XML', $controls.UnattendPathTextBox.Text) if (-not [string]::IsNullOrWhiteSpace($selectedPath)) { $controls.UnattendPathTextBox.Text = $selectedPath $appendServicingLog.Invoke("Selected unattend path: $selectedPath") $addServicingSelectionItem.Invoke("Selected unattend path: $selectedPath") } } if ([string]::IsNullOrWhiteSpace($controls.UnattendPathTextBox.Text)) { throw 'Unattend XML path is required.' } $addUnattendFileParams = @{ MediaRoot = $controls.ServiceMediaRootTextBox.Text UnattendPath = $controls.UnattendPathTextBox.Text DestinationRelativePath = $controls.UnattendDestinationTextBox.Text Verbose = $true } $appendServicingLog.Invoke('Adding unattend file...') $unattendStream = & { Add-UnattendFile @addUnattendFileParams } 4>&1 $appendStreamLines.Invoke(@($unattendStream), $appendServicingLog) $controls.StatusText.Text = 'Unattend copy completed.' $appendServicingLog.Invoke($controls.StatusText.Text) $addServicingSelectionItem.Invoke("Added unattend $($controls.UnattendPathTextBox.Text) to $($controls.UnattendDestinationTextBox.Text)") } catch { $controls.StatusText.Text = 'Unattend copy failed.' $appendServicingLog.Invoke("Unattend copy failed. $($_.Exception.Message)") } }.GetNewClosure()) } if ($controls.AddPayloadFileButton) { $controls.AddPayloadFileButton.AddClick({ try { if ([string]::IsNullOrWhiteSpace($controls.ServiceMediaRootTextBox.Text)) { throw 'Servicing media root is required.' } if ([string]::IsNullOrWhiteSpace($controls.PayloadSourceTextBox.Text)) { $selectedPath = $selectFilePath.Invoke('Select Payload File', $controls.PayloadSourceTextBox.Text) if (-not [string]::IsNullOrWhiteSpace($selectedPath)) { $controls.PayloadSourceTextBox.Text = $selectedPath $appendServicingLog.Invoke("Selected payload source: $selectedPath") $addServicingSelectionItem.Invoke("Selected payload source: $selectedPath") } } if ([string]::IsNullOrWhiteSpace($controls.PayloadSourceTextBox.Text)) { throw 'Payload source file path is required.' } $sourcePath = $controls.PayloadSourceTextBox.Text if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) { throw "Payload source file does not exist: $sourcePath" } $destinationRelativePath = $controls.PayloadDestinationTextBox.Text if ([string]::IsNullOrWhiteSpace($destinationRelativePath)) { throw 'Payload destination relative path is required.' } $destinationPath = Join-Path $controls.ServiceMediaRootTextBox.Text $destinationRelativePath $destinationDirectory = Split-Path -Path $destinationPath -Parent $newItemParams = @{ Path = $destinationDirectory ItemType = 'Directory' Force = $true } New-Item @newItemParams | Out-Null $copyItemParams = @{ LiteralPath = $sourcePath Destination = $destinationPath Force = $true } $appendServicingLog.Invoke('Copying payload file into media...') Copy-Item @copyItemParams $controls.StatusText.Text = 'Payload file copy completed.' $appendServicingLog.Invoke("Payload copied to $destinationRelativePath") $addServicingSelectionItem.Invoke("Added payload $(Split-Path -Path $sourcePath -Leaf) to $destinationRelativePath") } catch { $controls.StatusText.Text = 'Payload file copy failed.' $appendServicingLog.Invoke("Payload file copy failed. $($_.Exception.Message)") } }.GetNewClosure()) } if ($controls.BuildBootableIsoButton) { $controls.BuildBootableIsoButton.AddClick({ try { if ([string]::IsNullOrWhiteSpace($controls.BuildMediaRootTextBox.Text)) { throw 'Build media root is required.' } if ([string]::IsNullOrWhiteSpace($controls.BuildOutputIsoTextBox.Text)) { throw 'Output ISO path is required.' } if ([string]::IsNullOrWhiteSpace($controls.BuildLabelTextBox.Text)) { throw 'ISO label is required.' } $buildBootableIsoParams = @{ MediaRoot = $controls.BuildMediaRootTextBox.Text OutputIso = $controls.BuildOutputIsoTextBox.Text Label = $controls.BuildLabelTextBox.Text Verbose = $true } $appendBuildLog.Invoke('Building bootable ISO...') $buildIsoStream = & { Build-BootableIso @buildBootableIsoParams } 4>&1 $appendStreamLines.Invoke(@($buildIsoStream), $appendBuildLog) $controls.StatusText.Text = 'Bootable ISO build completed.' $appendBuildLog.Invoke($controls.StatusText.Text) } catch { $controls.StatusText.Text = 'Bootable ISO build failed.' $appendBuildLog.Invoke("Bootable ISO build failed. $($_.Exception.Message)") } }.GetNewClosure()) } if ($controls.ExportInstallImageButton) { $controls.ExportInstallImageButton.AddClick({ try { if ([string]::IsNullOrWhiteSpace($controls.BuildSourceImageTextBox.Text)) { throw 'Source image path is required.' } if ([string]::IsNullOrWhiteSpace($controls.BuildDestinationWimTextBox.Text)) { throw 'Destination WIM path is required.' } $selectedIndexes = $getIndexListFromText.Invoke($controls.BuildIndexesTextBox.Text) $getWindowsInstallImageParams = @{ ImagePath = $controls.BuildSourceImageTextBox.Text } $images = @(Get-WindowsInstallImage @getWindowsInstallImageParams) if ($selectedIndexes.Count -gt 0) { $images = @($images | Where-Object { $_.Index -in $selectedIndexes }) } if ($images.Count -eq 0) { throw 'No matching image indexes were found for export.' } $appendBuildLog.Invoke('Exporting selected install image indexes...') $exportWindowsInstallImageParams = @{ SourceImagePath = $controls.BuildSourceImageTextBox.Text DestinationWim = $controls.BuildDestinationWimTextBox.Text Image = $images Verbose = $true } $exportStream = & { Export-WindowsInstallImage @exportWindowsInstallImageParams } 4>&1 $appendStreamLines.Invoke(@($exportStream), $appendBuildLog) $controls.StatusText.Text = "Install image export completed for $($images.Count) index(es)." $appendBuildLog.Invoke($controls.StatusText.Text) } catch { $controls.StatusText.Text = 'Install image export failed.' $appendBuildLog.Invoke("Install image export failed. $($_.Exception.Message)") } }.GetNewClosure()) } $controls.FooterText.Text = 'Preflight checks help avoid ADK, DISM, and path issues before long-running operations.' } |