From 3875181d89a8fb9e54a723d8bdad5549200ff983 Mon Sep 17 00:00:00 2001
From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com>
Date: Sat, 15 Feb 2025 21:42:23 -0800
Subject: [PATCH] Enhance UI for application installation options and add
Winget status panel
---
FFUDevelopment/BuildFFUVM_UI.ps1 | 257 ++++++++++++++++++++++++++++++
FFUDevelopment/BuildFFUVM_UI.xaml | 80 +++++++++-
2 files changed, 330 insertions(+), 7 deletions(-)
diff --git a/FFUDevelopment/BuildFFUVM_UI.ps1 b/FFUDevelopment/BuildFFUVM_UI.ps1
index a9e2f92..410e356 100644
--- a/FFUDevelopment/BuildFFUVM_UI.ps1
+++ b/FFUDevelopment/BuildFFUVM_UI.ps1
@@ -27,6 +27,181 @@ function Get-USBDrives {
}
}
+# --------------------------------------------------------------------------
+# SECTION: Winget Management Functions
+# --------------------------------------------------------------------------
+function Test-WingetCLI {
+ [CmdletBinding()]
+ param()
+
+ $minVersion = [version]"1.8.1911"
+
+ # Check Winget CLI
+ $wingetCmd = Get-Command -Name winget -ErrorAction SilentlyContinue
+ if (-not $wingetCmd) {
+ return @{
+ Version = "Not installed"
+ Status = "Not installed - Install from Microsoft Store"
+ }
+ }
+
+ # Get and check version
+ $wingetVersion = & winget.exe --version
+ if ($wingetVersion -match 'v?(\d+\.\d+.\d+)') {
+ $version = [version]$matches[1]
+ if ($version -lt $minVersion) {
+ return @{
+ Version = $version.ToString()
+ Status = "Update required - Install from Microsoft Store"
+ }
+ }
+ return @{
+ Version = $version.ToString()
+ Status = $version.ToString()
+ }
+ }
+
+ return @{
+ Version = "Unknown"
+ Status = "Version check failed"
+ }
+}
+
+function Update-WingetVersionFields {
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory)]
+ [string]$wingetText,
+ [Parameter(Mandatory)]
+ [string]$moduleText
+ )
+
+ # Force UI update on the UI thread
+ $window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Normal, [Action]{
+ $script:txtWingetVersion.Text = $wingetText
+ $script:txtWingetModuleVersion.Text = $moduleText
+ # Force immediate UI refresh
+ [System.Windows.Forms.Application]::DoEvents()
+ })
+}
+
+function Install-WingetComponents {
+ [CmdletBinding()]
+ param(
+ [string]$currentWingetVersion = "Checking..."
+ )
+
+ $minVersion = [version]"1.8.1911"
+
+ try {
+ # Check and update PowerShell Module
+ $module = Get-InstalledModule -Name Microsoft.WinGet.Client -ErrorAction SilentlyContinue
+ if (-not $module -or $module.Version -lt $minVersion) {
+ Update-WingetVersionFields -wingetText $currentWingetVersion -moduleText "Installing..."
+
+ # Store and modify PSGallery trust setting temporarily if needed
+ $PSGalleryTrust = (Get-PSRepository -Name 'PSGallery').InstallationPolicy
+ if ($PSGalleryTrust -eq 'Untrusted') {
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ }
+
+ # Install/Update the module
+ Install-Module -Name Microsoft.WinGet.Client -Force -Repository 'PSGallery'
+
+ # Restore original PSGallery trust setting
+ if ($PSGalleryTrust -eq 'Untrusted') {
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Untrusted
+ }
+
+ $module = Get-InstalledModule -Name Microsoft.WinGet.Client -ErrorAction Stop
+ }
+
+ return $module
+ }
+ catch {
+ Write-Error "Failed to install/update Winget PowerShell module: $_"
+ throw
+ }
+}
+
+# Winget Module Check Function
+function Confirm-WinGetInstallation {
+ param(
+ [System.Windows.Controls.TextBlock]$txtWingetVersion,
+ [System.Windows.Controls.TextBlock]$txtWingetModuleVersion
+ )
+
+ $minVersion = [version]"1.8.1911"
+ $result = @{
+ Success = $false
+ Message = ""
+ RequiresRestart = $false
+ }
+
+ # Check if winget executable exists and is accessible
+ if (-not (Get-Command -Name winget -ErrorAction SilentlyContinue)) {
+ Update-VersionTextFields -wingetText "Not installed" -moduleText "Not installed"
+ $result.Message = "WinGet not found. Installing..."
+ $result.RequiresRestart = $true
+ return $result
+ }
+
+ # Get winget version
+ $wingetVersion = & winget.exe --version
+ if ($wingetVersion -match 'v?(\d+\.\d+.\d+)') {
+ $currentVersion = [version]$matches[1]
+ Update-VersionTextFields -wingetText $matches[1] -moduleText $txtWingetModuleVersion.Text
+
+ if ($currentVersion -lt $minVersion) {
+ Update-VersionTextFields -wingetText "Updating..." -moduleText $txtWingetModuleVersion.Text
+ $result.Message = "WinGet version $currentVersion is outdated. Minimum required version is $minVersion"
+ $result.RequiresRestart = $true
+ return $result
+ }
+ }
+
+ # Check if Winget PowerShell module is installed and up to date
+ $wingetModule = Get-InstalledModule -Name Microsoft.WinGet.Client -ErrorAction SilentlyContinue
+ if ($null -eq $wingetModule) {
+ Update-VersionTextFields -wingetText $txtWingetVersion.Text -moduleText "Installing..."
+ $result.Message = "Microsoft.WinGet.Client module needs to be installed..."
+ }
+ elseif ($wingetModule.Version -lt $minVersion) {
+ Update-VersionTextFields -wingetText $txtWingetVersion.Text -moduleText "Updating..."
+ $result.Message = "Microsoft.WinGet.Client module needs to be updated..."
+ }
+ else {
+ Update-VersionTextFields -wingetText $txtWingetVersion.Text -moduleText $wingetModule.Version.ToString()
+ $result.Success = $true
+ $result.Message = "Winget and its PowerShell module are installed and up to date."
+ return $result
+ }
+
+ # Install/Update module if needed
+ try {
+ # Check if PSGallery is trusted
+ $PSGalleryTrust = (Get-PSRepository -Name 'PSGallery').InstallationPolicy
+ if ($PSGalleryTrust -eq 'Untrusted') {
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ }
+
+ # Install/Update the module
+ Install-Module -Name Microsoft.WinGet.Client -Force -Repository 'PSGallery'
+
+ # Restore PSGallery trust setting if it was untrusted
+ if ($PSGalleryTrust -eq 'Untrusted') {
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Untrusted
+ }
+ }
+ catch {
+ Update-VersionTextFields -wingetText $txtWingetVersion.Text -moduleText "Error"
+ throw
+ }
+
+ $result.RequiresRestart = $true
+ return $result
+}
+
# Some default values
$defaultISOPath = ""
$defaultWindowsRelease = 11 # numeric
@@ -184,6 +359,7 @@ function Get-UIConfig {
# --- Applications tab ---
$installApps = $window.FindName('chkInstallApps').IsChecked
+ $installWingetApps = $window.FindName('chkInstallWingetApps').IsChecked
# Add USB drive selection to config
$selectedUSBDrives = @{}
@@ -243,6 +419,8 @@ function Get-UIConfig {
WindowsVersion = $windowsVersion
FFUPrefix = $ffuPrefix # <-- new option for VM Name Prefix
USBDriveList = $selectedUSBDrives # Add USB drive selection
+ InstallApps = $installApps
+ InstallWingetApps = $installWingetApps
}
# Sort the configuration hashtable alphabetically by key
@@ -404,6 +582,23 @@ $script:UpdateInstallAppsBasedOnUpdates = {
}
}
+# Create data context class for version binding
+$script:versionData = [PSCustomObject]@{
+ WingetVersion = "Not checked"
+ ModuleVersion = "Not checked"
+}
+
+# Add observable property support
+$script:versionData | Add-Member -MemberType ScriptMethod -Name NotifyPropertyChanged -Value {
+ param($PropertyName)
+ if ($this.PropertyChanged) {
+ $this.PropertyChanged.Invoke($this, [System.ComponentModel.PropertyChangedEventArgs]::new($PropertyName))
+ }
+}
+
+$script:versionData | Add-Member -MemberType NoteProperty -Name PropertyChanged -Value $null
+$script:versionData | Add-Member -TypeName "System.ComponentModel.INotifyPropertyChanged"
+
$window.Add_Loaded({
$script:cmbWindowsRelease = $window.FindName('cmbWindowsRelease')
$script:cmbWindowsVersion = $window.FindName('cmbWindowsVersion')
@@ -718,6 +913,67 @@ $window.Add_Loaded({
$script:chkPromptExternalHardDiskMedia.IsEnabled = $false
$script:chkPromptExternalHardDiskMedia.IsChecked = $false
})
+
+ # Add Winget panel visibility handler
+ $script:chkInstallApps = $window.FindName('chkInstallApps')
+ $script:chkInstallWingetApps = $window.FindName('chkInstallWingetApps')
+ $script:wingetPanel = $window.FindName('wingetPanel')
+ $script:btnCheckWingetModule = $window.FindName('btnCheckWingetModule')
+ $script:txtWingetVersion = $window.FindName('txtWingetVersion')
+ $script:txtWingetModuleVersion = $window.FindName('txtWingetModuleVersion')
+
+ # Hide Winget Apps checkbox initially if Install Apps is unchecked
+ $script:chkInstallWingetApps.Visibility = if ($script:chkInstallApps.IsChecked) { 'Visible' } else { 'Collapsed' }
+
+ # Show/Hide Winget Apps checkbox based on Install Apps state
+ $script:chkInstallApps.Add_Checked({
+ $script:chkInstallWingetApps.Visibility = 'Visible'
+ })
+ $script:chkInstallApps.Add_Unchecked({
+ $script:chkInstallWingetApps.IsChecked = $false
+ $script:chkInstallWingetApps.Visibility = 'Collapsed'
+ $script:wingetPanel.Visibility = 'Collapsed'
+ })
+
+ # Show/Hide Winget panel based on checkbox state
+ $script:chkInstallWingetApps.Add_Checked({ $script:wingetPanel.Visibility = 'Visible' })
+ $script:chkInstallWingetApps.Add_Unchecked({ $script:wingetPanel.Visibility = 'Collapsed' })
+
+ # Handle Winget component check/installation
+ $script:btnCheckWingetModule.Add_Click({
+ $this.IsEnabled = $false
+ $window.Cursor = [System.Windows.Input.Cursors]::Wait
+
+ # Show initial checking status
+ Update-WingetVersionFields -wingetText "Checking..." -moduleText "Checking..."
+
+ # Run checks in background to prevent UI freezing
+ $window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action]{
+ try {
+ # Check Winget CLI first
+ $cliStatus = Test-WingetCLI
+
+ # Install/Update PowerShell module if needed
+ $module = Install-WingetComponents -currentWingetVersion $cliStatus.Status
+
+ # Update UI with final status
+ Update-WingetVersionFields -wingetText $cliStatus.Status -moduleText $module.Version
+ }
+ catch {
+ Update-WingetVersionFields -wingetText "Error" -moduleText "Error"
+ [System.Windows.MessageBox]::Show(
+ "Error checking winget components: $_",
+ "Error",
+ "OK",
+ "Error"
+ )
+ }
+ finally {
+ $this.IsEnabled = $true
+ $window.Cursor = $null
+ }
+ })
+ })
})
# Button: Build FFU
@@ -855,6 +1111,7 @@ $btnLoadConfig.Add_Click({
$window.FindName('chkUpdatePreviewCU').IsChecked = $configContent.UpdatePreviewCU
# Applications tab
$window.FindName('chkInstallApps').IsChecked = $configContent.InstallApps
+ $window.FindName('chkInstallWingetApps').IsChecked = $configContent.InstallWingetApps
# Update USB Drive selection if present in config
if ($configContent.USBDriveList) {
diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml
index 3bdceee..57409e4 100644
--- a/FFUDevelopment/BuildFFUVM_UI.xaml
+++ b/FFUDevelopment/BuildFFUVM_UI.xaml
@@ -264,9 +264,9 @@
-
+
@@ -526,13 +526,79 @@
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+