Add Clear List button to Save/Import functionality and fixed sorting issues with winget app list.

This commit is contained in:
rbalsleyMSFT
2025-02-18 20:21:37 -08:00
parent 02835579c1
commit 802865c1a6
2 changed files with 756 additions and 748 deletions
+92 -89
View File
@@ -11,8 +11,7 @@ $AppsPath = Join-Path $FFUDevelopmentPath "Apps"
# Add the new function for USB drive detection
function Get-USBDrives {
Get-WmiObject Win32_DiskDrive | Where-Object {
($_.MediaType -eq 'Removable Media' -or $_.MediaType -eq 'External hard disk media') -and
$_.InterfaceType -eq 'USB'
($_.MediaType -eq 'Removable Media' -or $_.MediaType -eq 'External hard disk media')
} | ForEach-Object {
$size = [math]::Round($_.Size / 1GB, 2)
$serialNumber = if ($_.SerialNumber) { $_.SerialNumber.Trim() } else { "N/A" }
@@ -76,7 +75,7 @@ function Update-WingetVersionFields {
)
# Force UI update on the UI thread
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Normal, [Action]{
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Normal, [Action] {
$script:txtWingetVersion.Text = $wingetText
$script:txtWingetModuleVersion.Text = $moduleText
# Force immediate UI refresh
@@ -213,40 +212,40 @@ $defaultFFUPrefix = "_FFU" # <-- new default for VM Name Prefix
# Large list from the ValidateSet in BuildFFUVM.ps1 ($OptionalFeatures parameter)
$allowedFeatures = @(
"AppServerClient","Client-DeviceLockdown","Client-EmbeddedBootExp","Client-EmbeddedLogon",
"Client-EmbeddedShellLauncher","Client-KeyboardFilter","Client-ProjFS","Client-UnifiedWriteFilter",
"Containers","Containers-DisposableClientVM","Containers-HNS","Containers-SDN","DataCenterBridging",
"DirectoryServices-ADAM-Client","DirectPlay","HostGuardian","HypervisorPlatform","IIS-ApplicationDevelopment",
"IIS-ApplicationInit","IIS-ASP","IIS-ASPNET45","IIS-BasicAuthentication","IIS-CertProvider",
"IIS-CGI","IIS-ClientCertificateMappingAuthentication","IIS-CommonHttpFeatures","IIS-CustomLogging",
"IIS-DefaultDocument","IIS-DirectoryBrowsing","IIS-DigestAuthentication","IIS-ESP","IIS-FTPServer",
"IIS-FTPExtensibility","IIS-FTPSvc","IIS-HealthAndDiagnostics","IIS-HostableWebCore","IIS-HttpCompressionDynamic",
"IIS-HttpCompressionStatic","IIS-HttpErrors","IIS-HttpLogging","IIS-HttpRedirect","IIS-HttpTracing",
"IIS-IPSecurity","IIS-IIS6ManagementCompatibility","IIS-IISCertificateMappingAuthentication",
"IIS-ISAPIExtensions","IIS-ISAPIFilter","IIS-LoggingLibraries","IIS-ManagementConsole","IIS-ManagementService",
"IIS-ManagementScriptingTools","IIS-Metabase","IIS-NetFxExtensibility","IIS-NetFxExtensibility45",
"IIS-ODBCLogging","IIS-Performance","IIS-RequestFiltering","IIS-RequestMonitor","IIS-Security","IIS-ServerSideIncludes",
"IIS-StaticContent","IIS-URLAuthorization","IIS-WebDAV","IIS-WebServer","IIS-WebServerManagementTools",
"IIS-WebServerRole","IIS-WebSockets","LegacyComponents","MediaPlayback","Microsoft-Hyper-V","Microsoft-Hyper-V-All",
"Microsoft-Hyper-V-Hypervisor","Microsoft-Hyper-V-Management-Clients","Microsoft-Hyper-V-Management-PowerShell",
"Microsoft-Hyper-V-Services","Microsoft-Windows-Subsystem-Linux","MSMQ-ADIntegration","MSMQ-Container","MSMQ-DCOMProxy",
"MSMQ-HTTP","MSMQ-Multicast","MSMQ-Server","MSMQ-Triggers","MultiPoint-Connector","MultiPoint-Connector-Services",
"MultiPoint-Tools","NetFx3","NetFx4-AdvSrvs","NetFx4Extended-ASPNET45","NFS-Administration","Printing-Foundation-Features",
"Printing-Foundation-InternetPrinting-Client","Printing-Foundation-LPDPrintService","Printing-Foundation-LPRPortMonitor",
"Printing-PrintToPDFServices-Features","Printing-XPSServices-Features","SearchEngine-Client-Package",
"ServicesForNFS-ClientOnly","SimpleTCP","SMB1Protocol","SMB1Protocol-Client","SMB1Protocol-Deprecation",
"SMB1Protocol-Server","SmbDirect","TFTP","TelnetClient","TIFFIFilter","VirtualMachinePlatform","WAS-ConfigurationAPI",
"WAS-NetFxEnvironment","WAS-ProcessModel","WAS-WindowsActivationService","WCF-HTTP-Activation","WCF-HTTP-Activation45",
"WCF-MSMQ-Activation45","WCF-MSMQ-Activation","WCF-NonHTTP-Activation","WCF-Pipe-Activation45","WCF-Services45",
"WCF-TCP-Activation45","WCF-TCP-PortSharing45","Windows-Defender-ApplicationGuard",
"Windows-Defender-Default-Definitions","Windows-Identity-Foundation","WindowsMediaPlayer","WorkFolders-Client"
"AppServerClient", "Client-DeviceLockdown", "Client-EmbeddedBootExp", "Client-EmbeddedLogon",
"Client-EmbeddedShellLauncher", "Client-KeyboardFilter", "Client-ProjFS", "Client-UnifiedWriteFilter",
"Containers", "Containers-DisposableClientVM", "Containers-HNS", "Containers-SDN", "DataCenterBridging",
"DirectoryServices-ADAM-Client", "DirectPlay", "HostGuardian", "HypervisorPlatform", "IIS-ApplicationDevelopment",
"IIS-ApplicationInit", "IIS-ASP", "IIS-ASPNET45", "IIS-BasicAuthentication", "IIS-CertProvider",
"IIS-CGI", "IIS-ClientCertificateMappingAuthentication", "IIS-CommonHttpFeatures", "IIS-CustomLogging",
"IIS-DefaultDocument", "IIS-DirectoryBrowsing", "IIS-DigestAuthentication", "IIS-ESP", "IIS-FTPServer",
"IIS-FTPExtensibility", "IIS-FTPSvc", "IIS-HealthAndDiagnostics", "IIS-HostableWebCore", "IIS-HttpCompressionDynamic",
"IIS-HttpCompressionStatic", "IIS-HttpErrors", "IIS-HttpLogging", "IIS-HttpRedirect", "IIS-HttpTracing",
"IIS-IPSecurity", "IIS-IIS6ManagementCompatibility", "IIS-IISCertificateMappingAuthentication",
"IIS-ISAPIExtensions", "IIS-ISAPIFilter", "IIS-LoggingLibraries", "IIS-ManagementConsole", "IIS-ManagementService",
"IIS-ManagementScriptingTools", "IIS-Metabase", "IIS-NetFxExtensibility", "IIS-NetFxExtensibility45",
"IIS-ODBCLogging", "IIS-Performance", "IIS-RequestFiltering", "IIS-RequestMonitor", "IIS-Security", "IIS-ServerSideIncludes",
"IIS-StaticContent", "IIS-URLAuthorization", "IIS-WebDAV", "IIS-WebServer", "IIS-WebServerManagementTools",
"IIS-WebServerRole", "IIS-WebSockets", "LegacyComponents", "MediaPlayback", "Microsoft-Hyper-V", "Microsoft-Hyper-V-All",
"Microsoft-Hyper-V-Hypervisor", "Microsoft-Hyper-V-Management-Clients", "Microsoft-Hyper-V-Management-PowerShell",
"Microsoft-Hyper-V-Services", "Microsoft-Windows-Subsystem-Linux", "MSMQ-ADIntegration", "MSMQ-Container", "MSMQ-DCOMProxy",
"MSMQ-HTTP", "MSMQ-Multicast", "MSMQ-Server", "MSMQ-Triggers", "MultiPoint-Connector", "MultiPoint-Connector-Services",
"MultiPoint-Tools", "NetFx3", "NetFx4-AdvSrvs", "NetFx4Extended-ASPNET45", "NFS-Administration", "Printing-Foundation-Features",
"Printing-Foundation-InternetPrinting-Client", "Printing-Foundation-LPDPrintService", "Printing-Foundation-LPRPortMonitor",
"Printing-PrintToPDFServices-Features", "Printing-XPSServices-Features", "SearchEngine-Client-Package",
"ServicesForNFS-ClientOnly", "SimpleTCP", "SMB1Protocol", "SMB1Protocol-Client", "SMB1Protocol-Deprecation",
"SMB1Protocol-Server", "SmbDirect", "TFTP", "TelnetClient", "TIFFIFilter", "VirtualMachinePlatform", "WAS-ConfigurationAPI",
"WAS-NetFxEnvironment", "WAS-ProcessModel", "WAS-WindowsActivationService", "WCF-HTTP-Activation", "WCF-HTTP-Activation45",
"WCF-MSMQ-Activation45", "WCF-MSMQ-Activation", "WCF-NonHTTP-Activation", "WCF-Pipe-Activation45", "WCF-Services45",
"WCF-TCP-Activation45", "WCF-TCP-PortSharing45", "Windows-Defender-ApplicationGuard",
"Windows-Defender-Default-Definitions", "Windows-Identity-Foundation", "WindowsMediaPlayer", "WorkFolders-Client"
)
$skuList = @(
'Home','Home N','Home Single Language','Education','Education N','Pro',
'Pro N','Pro Education','Pro Education N','Pro for Workstations',
'Pro N for Workstations','Enterprise','Enterprise N','Standard',
'Standard (Desktop Experience)','Datacenter','Datacenter (Desktop Experience)'
'Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro',
'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations',
'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Standard',
'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)'
)
# Full list of Windows releases (if ISO path != blank)
@@ -268,7 +267,7 @@ $mctWindowsReleases = @(
# Windows version sets
$windowsVersionMap = @{
10 = @("22H2")
11 = @("22H2","23H2","24H2")
11 = @("22H2", "23H2", "24H2")
2016 = @("1607")
2019 = @("1809")
2022 = @("21H2")
@@ -336,7 +335,8 @@ function Get-UIConfig {
VMLocation = $window.FindName('txtVMLocation').Text
VMSwitchName = if ($window.FindName('cmbVMSwitchName').SelectedItem -eq 'Other') {
$window.FindName('txtCustomVMSwitchName').Text
} else {
}
else {
$window.FindName('cmbVMSwitchName').SelectedItem
}
WindowsArch = $window.FindName('cmbWindowsArch').SelectedItem
@@ -420,7 +420,7 @@ $script:RefreshWindowsUI = {
}
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName PresentationCore,PresentationFramework
Add-Type -AssemblyName PresentationCore, PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
# Load XAML
@@ -495,7 +495,8 @@ $script:UpdateInstallAppsBasedOnUpdates = {
}
$window.FindName('chkInstallApps').IsChecked = $true
$window.FindName('chkInstallApps').IsEnabled = $false
} else {
}
else {
if ($script:installAppsForcedByUpdates) {
$window.FindName('chkInstallApps').IsChecked = $script:prevInstallAppsStateBeforeUpdates
$script:installAppsForcedByUpdates = $false
@@ -543,11 +544,11 @@ function Add-SortableColumn {
$column.DisplayMemberBinding = New-Object System.Windows.Data.Binding($binding)
}
# Create the header with sorting support
$columnHeader = New-Object System.Windows.Controls.GridViewColumnHeader
$columnHeader.Content = $header
$columnHeader.Tag = $binding
$column.Header = $columnHeader
# Create a header with the binding information stored in its Tag
$headerTemplate = New-Object System.Windows.Controls.GridViewColumnHeader
$headerTemplate.Content = $header
$headerTemplate.Tag = $binding
$column.Header = $headerTemplate
if ($width -ne 'Auto') {
$column.Width = $width
@@ -570,12 +571,13 @@ function Invoke-ListViewSort {
# Toggle sort direction if clicking the same column
if ($script:lastSortProperty -eq $property) {
$script:lastSortAscending = -not $script:lastSortAscending
} else {
}
else {
$script:lastSortAscending = $true
}
$script:lastSortProperty = $property
# Convert ListView items to array and split into selected and unselected
# Store selected items
$items = @($listView.Items)
$selectedItems = @($items | Where-Object { $_.IsSelected })
$unselectedItems = @($items | Where-Object { -not $_.IsSelected })
@@ -583,19 +585,22 @@ function Invoke-ListViewSort {
# Sort unselected items
$sortedUnselected = if ($script:lastSortAscending) {
@($unselectedItems | Sort-Object -Property $property)
} else {
@($unselectedUnselected | Sort-Object -Property $property -Descending)
}
else {
@($unselectedItems | Sort-Object -Property $property -Descending)
}
# Clear and repopulate ListView
$listView.Items.Clear()
# Add items back in correct order (selected first, then sorted unselected)
# Add selected items first
foreach ($item in $selectedItems) {
$listView.Items.Add($item)
[void]$listView.Items.Add($item)
}
# Add sorted unselected items
foreach ($item in $sortedUnselected) {
$listView.Items.Add($item)
[void]$listView.Items.Add($item)
}
}
@@ -668,14 +673,14 @@ $window.Add_Loaded({
})
# Windows Arch, Lang, SKU, etc.
$script:cmbWindowsArch = $window.FindName('cmbWindowsArch')
foreach ($a in 'x86','x64','arm64') { [void]$script:cmbWindowsArch.Items.Add($a) }
foreach ($a in 'x86', 'x64', 'arm64') { [void]$script:cmbWindowsArch.Items.Add($a) }
$script:cmbWindowsArch.SelectedItem = $defaultWindowsArch
$script:cmbWindowsLang = $window.FindName('cmbWindowsLang')
$allowedLangs = @(
'ar-sa','bg-bg','cs-cz','da-dk','de-de','el-gr','en-gb','en-us','es-es','es-mx','et-ee',
'fi-fi','fr-ca','fr-fr','he-il','hr-hr','hu-hu','it-it','ja-jp','ko-kr','lt-lt','lv-lv',
'nb-no','nl-nl','pl-pl','pt-br','pt-pt','ro-ro','ru-ru','sk-sk','sl-si','sr-latn-rs',
'sv-se','th-th','tr-tr','uk-ua','zh-cn','zh-tw'
'ar-sa', 'bg-bg', 'cs-cz', 'da-dk', 'de-de', 'el-gr', 'en-gb', 'en-us', 'es-es', 'es-mx', 'et-ee',
'fi-fi', 'fr-ca', 'fr-fr', 'he-il', 'hr-hr', 'hu-hu', 'it-it', 'ja-jp', 'ko-kr', 'lt-lt', 'lv-lv',
'nb-no', 'nl-nl', 'pl-pl', 'pt-br', 'pt-pt', 'ro-ro', 'ru-ru', 'sk-sk', 'sl-si', 'sr-latn-rs',
'sv-se', 'th-th', 'tr-tr', 'uk-ua', 'zh-cn', 'zh-tw'
)
foreach ($lang in $allowedLangs) { [void]$script:cmbWindowsLang.Items.Add($lang) }
$script:cmbWindowsLang.SelectedItem = $defaultWindowsLang
@@ -684,7 +689,7 @@ $window.Add_Loaded({
foreach ($sku in $skuList) { [void]$script:cmbWindowsSKU.Items.Add($sku) }
$script:cmbWindowsSKU.SelectedItem = $defaultWindowsSKU
$script:cmbMediaType = $window.FindName('cmbMediaType')
foreach ($mt in "Consumer","Business") { [void]$script:cmbMediaType.Items.Add($mt) } # updated options
foreach ($mt in "Consumer", "Business") { [void]$script:cmbMediaType.Items.Add($mt) } # updated options
$script:cmbMediaType.SelectedItem = $defaultMediaType
$script:txtOptionalFeatures = $window.FindName('txtOptionalFeatures')
$script:txtOptionalFeatures.Text = $defaultOptionalFeatures
@@ -694,7 +699,7 @@ $window.Add_Loaded({
$script:cmbMake = $window.FindName('cmbMake')
$script:cmbModel = $window.FindName('cmbModel')
$script:spMakeModelSection = $window.FindName('spMakeModelSection')
$makeList = @('Microsoft','Dell','HP','Lenovo')
$makeList = @('Microsoft', 'Dell', 'HP', 'Lenovo')
foreach ($m in $makeList) { [void]$script:cmbMake.Items.Add($m) }
if ($script:cmbMake.Items.Count -gt 0) { $script:cmbMake.SelectedIndex = 0 }
$script:chkDownloadDrivers.Add_Checked({
@@ -775,7 +780,8 @@ $window.Add_Loaded({
}
$window.FindName('chkInstallApps').IsChecked = $true
$window.FindName('chkInstallApps').IsEnabled = $false
} else {
}
else {
if ($script:installAppsForcedByUpdates) {
$window.FindName('chkInstallApps').IsChecked = $script:prevInstallAppsStateBeforeUpdates
$script:installAppsForcedByUpdates = $false
@@ -955,7 +961,7 @@ $window.Add_Loaded({
Update-WingetVersionFields -wingetText "Checking..." -moduleText "Checking..."
# Run checks in background to prevent UI freezing
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action]{
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [Action] {
try {
# Check Winget CLI first
$cliStatus = Test-WingetCLI
@@ -996,6 +1002,7 @@ $window.Add_Loaded({
$script:lstWingetResults = $window.FindName('lstWingetResults')
$script:btnSaveWingetList = $window.FindName('btnSaveWingetList')
$script:btnImportWingetList = $window.FindName('btnImportWingetList')
$script:btnClearWingetList = $window.FindName('btnClearWingetList')
# Initialize ListView with GridView columns
$gridView = New-Object System.Windows.Controls.GridView
@@ -1006,6 +1013,18 @@ $window.Add_Loaded({
Add-SortableColumn -gridView $gridView -header "Source" -binding "Source" -width 100
$script:lstWingetResults.View = $gridView
# Add column header click handler for sorting
$script:lstWingetResults.AddHandler(
[System.Windows.Controls.GridViewColumnHeader]::ClickEvent,
[System.Windows.RoutedEventHandler] {
param($sender, $e)
$header = $e.OriginalSource
if ($header -is [System.Windows.Controls.GridViewColumnHeader] -and $header.Tag) {
Invoke-ListViewSort -listView $script:lstWingetResults -property $header.Tag
}
}
)
# Hide search panel initially
$script:wingetSearchPanel.Visibility = 'Collapsed'
@@ -1022,15 +1041,6 @@ $window.Add_Loaded({
}
})
# Save and Import buttons
$script:btnSaveWingetList.Add_Click({
Save-WingetList
})
$script:btnImportWingetList.Add_Click({
Import-WingetList
})
# Show search panel after successful Winget validation
$script:btnCheckWingetModule.Add_Click({
# ...existing code...
@@ -1054,24 +1064,17 @@ $window.Add_Loaded({
}
})
$script:lstWingetResults.Add_PreviewMouseLeftButtonUp({
param($eventSrc, $mouseEventArgs)
$originalSource = $mouseEventArgs.OriginalSource
$header = [System.Windows.Media.VisualTreeHelper]::GetParent($originalSource)
while ($header -ne $null -and $header -isnot [System.Windows.Controls.GridViewColumnHeader]) {
$header = [System.Windows.Media.VisualTreeHelper]::GetParent($header)
}
if ($header -is [System.Windows.Controls.GridViewColumnHeader]) {
$headerContent = $header.Column.Header
if ($headerContent -is [System.Windows.Controls.GridViewColumnHeader]) {
if ($headerContent.Tag) {
Invoke-ListViewSort -listView $script:lstWingetResults -property $headerContent.Tag
}
}
# Add handlers for Winget app list buttons
$script:btnSaveWingetList.Add_Click({ Save-WingetList })
$script:btnImportWingetList.Add_Click({ Import-WingetList })
$script:btnClearWingetList.Add_Click({
$script:lstWingetResults.Items.Clear()
$script:txtWingetSearch.Text = "" # Clear the Winget search textbox
if ($script:txtStatus) {
$script:txtStatus.Text = "Cleared all applications from the list"
}
})
})
})
# Function to search for Winget apps
function Search-WingetApps {
@@ -1207,7 +1210,7 @@ $btnRun.Add_Click({
finally {
$window.FindName('progressBar').Visibility = 'Collapsed'
}
})
})
# Button: Build Config
$btnBuildConfig = $window.FindName('btnBuildConfig')
@@ -1230,9 +1233,9 @@ $btnBuildConfig.Add_Click({
}
}
catch {
[System.Windows.MessageBox]::Show("Error saving config file:`n$_","Error","OK","Error")
[System.Windows.MessageBox]::Show("Error saving config file:`n$_", "Error", "OK", "Error")
}
})
})
# Button: Load Config File
$btnLoadConfig = $window.FindName('btnLoadConfig')
@@ -1343,8 +1346,8 @@ $btnLoadConfig.Add_Click({
}
}
catch {
[System.Windows.MessageBox]::Show("Error loading config file:`n$_","Error","OK","Error")
[System.Windows.MessageBox]::Show("Error loading config file:`n$_", "Error", "OK", "Error")
}
})
})
[void]$window.ShowDialog()
+6 -1
View File
@@ -637,7 +637,7 @@
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"/>
<!-- Save/Import Buttons -->
<!-- Save/Import/Clear Buttons -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button x:Name="btnSaveWingetList"
Content="Save Applist.json"
@@ -647,7 +647,12 @@
<Button x:Name="btnImportWingetList"
Content="Import Applist.json"
Padding="15,5"
Margin="0,0,10,0"
ToolTip="Import applications from a JSON file"/>
<Button x:Name="btnClearWingetList"
Content="Clear List"
Padding="15,5"
ToolTip="Clear all applications from the list"/>
</StackPanel>
</StackPanel>
</StackPanel>