diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1 index 5ab9b2d..ae5c509 100644 --- a/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1 +++ b/FFUDevelopment/FFU.Common/FFU.Common.Winget.psm1 @@ -426,12 +426,16 @@ function Get-Apps { $overrideMap = @{} foreach ($app in $apps.apps) { if ($app.source -in @('winget', 'msstore')) { - $hasCmd = ($app.PSObject.Properties['CommandLine'] -and -not [string]::IsNullOrWhiteSpace($app.CommandLine)) - $hasArgs = ($app.PSObject.Properties['Arguments'] -and -not [string]::IsNullOrWhiteSpace($app.Arguments)) - if ($hasCmd -or $hasArgs) { + $hasCmd = ($app.PSObject.Properties['CommandLine'] -and -not [string]::IsNullOrWhiteSpace($app.CommandLine)) + $hasArgs = ($app.PSObject.Properties['Arguments'] -and -not [string]::IsNullOrWhiteSpace($app.Arguments)) + $hasAdd = ($app.PSObject.Properties['AdditionalExitCodes'] -and -not [string]::IsNullOrWhiteSpace($app.AdditionalExitCodes)) + $hasIgnore = ($app.PSObject.Properties['IgnoreNonZeroExitCodes']) + if ($hasCmd -or $hasArgs -or $hasAdd -or $hasIgnore) { $overrideMap[$app.name] = @{ - CommandLine = if ($hasCmd) { $app.CommandLine } else { $null } - Arguments = if ($hasArgs) { $app.Arguments } else { $null } + CommandLine = if ($hasCmd) { $app.CommandLine } else { $null } + Arguments = if ($hasArgs) { $app.Arguments } else { $null } + AdditionalExitCodes = if ($hasAdd) { $app.AdditionalExitCodes } else { $null } + IgnoreNonZeroExitCodes = if ($hasIgnore) { [bool]$app.IgnoreNonZeroExitCodes } else { $null } } } } @@ -455,6 +459,16 @@ function Get-Apps { $entry.Arguments = $ov.Arguments $changed = $true } + if ($ov.ContainsKey('AdditionalExitCodes') -and $null -ne $ov.AdditionalExitCodes) { + WriteLog "Override (AppList.json) AdditionalExitCodes for $($entry.Name)" + $entry | Add-Member -NotePropertyName AdditionalExitCodes -NotePropertyValue $ov.AdditionalExitCodes -Force + $changed = $true + } + if ($ov.ContainsKey('IgnoreNonZeroExitCodes') -and $null -ne $ov.IgnoreNonZeroExitCodes) { + WriteLog "Override (AppList.json) IgnoreNonZeroExitCodes for $($entry.Name)" + $entry | Add-Member -NotePropertyName IgnoreNonZeroExitCodes -NotePropertyValue ([bool]$ov.IgnoreNonZeroExitCodes) -Force + $changed = $true + } } } if ($changed) { diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 index 68f536b..9d7ba3d 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 @@ -885,13 +885,15 @@ function Import-ConfigSupplementalAssets { if ($appInfo.PSObject.Properties['architecture']) { $appInfo.architecture } else { $defaultArch } } $appsBuffer.Add([PSCustomObject]@{ - IsSelected = $true - Name = $appInfo.name - Id = $appInfo.id - Version = "" - Source = $appInfo.source - Architecture = $arch - DownloadStatus = "" + IsSelected = $true + Name = $appInfo.name + Id = $appInfo.id + Version = "" + Source = $appInfo.source + Architecture = $arch + AdditionalExitCodes = if ($appInfo.PSObject.Properties['AdditionalExitCodes']) { $appInfo.AdditionalExitCodes } else { "" } + IgnoreNonZeroExitCodes = if ($appInfo.PSObject.Properties['IgnoreNonZeroExitCodes']) { [bool]$appInfo.IgnoreNonZeroExitCodes } else { $false } + DownloadStatus = "" }) } $State.Controls.lstWingetResults.ItemsSource = $appsBuffer.ToArray() diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 index 21771f8..d870a96 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 @@ -446,6 +446,75 @@ function Initialize-DynamicUIElements { $wingetGridView.Columns.Add($archColumn) # --- END: Add Architecture Column --- + # --- START: Add Additional Exit Codes Column --- + $exitCodesColumn = New-Object System.Windows.Controls.GridViewColumn + $exitCodesHeader = New-Object System.Windows.Controls.GridViewColumnHeader + $exitCodesHeader.Tag = "AdditionalExitCodes" + $exitCodesHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left + + $exitHeaderTextFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock]) + $exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Additional Exit Codes") + $exitHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5,2,5,2))) + $exitHeaderTextFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) + + $exitHeaderTemplate = New-Object System.Windows.DataTemplate + $exitHeaderTemplate.VisualTree = $exitHeaderTextFactory + $exitCodesHeader.ContentTemplate = $exitHeaderTemplate + + $exitCodesColumn.Header = $exitCodesHeader + $exitCodesColumn.Width = 140 + + $exitCodesCellTemplate = New-Object System.Windows.DataTemplate + $exitCodesTextBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBox]) + $exitBinding = New-Object System.Windows.Data.Binding("AdditionalExitCodes") + $exitBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay + $exitCodesTextBoxFactory.SetBinding([System.Windows.Controls.TextBox]::TextProperty, $exitBinding) + $exitCodesCellTemplate.VisualTree = $exitCodesTextBoxFactory + $exitCodesColumn.CellTemplate = $exitCodesCellTemplate + $wingetGridView.Columns.Add($exitCodesColumn) + # --- END: Add Additional Exit Codes Column --- + + # --- START: Add Ignore Non-Zero Exit Codes Column --- + $ignoreColumn = New-Object System.Windows.Controls.GridViewColumn + $ignoreHeader = New-Object System.Windows.Controls.GridViewColumnHeader + $ignoreHeader.Tag = "IgnoreNonZeroExitCodes" + $ignoreHeader.HorizontalContentAlignment = [System.Windows.HorizontalAlignment]::Left + + $ignoreHeaderTextFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBlock]) + $ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::TextProperty, "Ignore Exit Codes") + $ignoreHeaderTextFactory.SetValue([System.Windows.Controls.TextBlock]::PaddingProperty, (New-Object System.Windows.Thickness(5,2,5,2))) + $ignoreHeaderTextFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) + + $ignoreHeaderTemplate = New-Object System.Windows.DataTemplate + $ignoreHeaderTemplate.VisualTree = $ignoreHeaderTextFactory + $ignoreHeader.ContentTemplate = $ignoreHeaderTemplate + + $ignoreColumn.Header = $ignoreHeader + $ignoreColumn.Width = 140 + + $ignoreCellTemplate = New-Object System.Windows.DataTemplate + + # Center the checkbox in the cell + $ignoreCellGridFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.Grid]) + $ignoreCellGridFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch) + $ignoreCellGridFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Stretch) + + $ignoreCheckFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.CheckBox]) + $ignoreCheckFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Center) + $ignoreCheckFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center) + + $ignoreBinding = New-Object System.Windows.Data.Binding("IgnoreNonZeroExitCodes") + $ignoreBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay + $ignoreCheckFactory.SetBinding([System.Windows.Controls.Primitives.ToggleButton]::IsCheckedProperty, $ignoreBinding) + + # Build the visual tree: Grid -> CheckBox + $ignoreCellGridFactory.AppendChild($ignoreCheckFactory) + $ignoreCellTemplate.VisualTree = $ignoreCellGridFactory + + $ignoreColumn.CellTemplate = $ignoreCellTemplate + $wingetGridView.Columns.Add($ignoreColumn) + # --- END: Add Ignore Non-Zero Exit Codes Column --- + Add-SortableColumn -gridView $wingetGridView -header "Download Status" -binding "DownloadStatus" -width 150 -headerHorizontalAlignment Left $State.Controls.lstWingetResults.AddHandler( [System.Windows.Controls.GridViewColumnHeader]::ClickEvent, diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 index 0505e7b..91e1058 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Winget.psm1 @@ -98,10 +98,12 @@ function Save-WingetList { $appList = @{ apps = @($selectedApps | ForEach-Object { [ordered]@{ - name = (ConvertTo-SafeName -Name $_.Name) - id = $_.Id - source = $_.Source.ToLower() - architecture = $_.Architecture + name = (ConvertTo-SafeName -Name $_.Name) + id = $_.Id + source = $_.Source.ToLower() + architecture = $_.Architecture + AdditionalExitCodes = if ($_.PSObject.Properties['AdditionalExitCodes']) { $_.AdditionalExitCodes } else { "" } + IgnoreNonZeroExitCodes = if ($_.PSObject.Properties['IgnoreNonZeroExitCodes']) { [bool]$_.IgnoreNonZeroExitCodes } else { $false } } }) } @@ -148,13 +150,15 @@ function Import-WingetList { foreach ($appInfo in $importedAppsData.apps) { $arch = if ($appInfo.source -eq 'msstore') { 'NA' } else { if ($appInfo.PSObject.Properties['architecture']) { $appInfo.architecture } else { $defaultArch } } $newAppListForItemsSource.Add([PSCustomObject]@{ - IsSelected = $true # Imported apps are marked as selected - Name = $appInfo.name - Id = $appInfo.id - Version = "" # Will be populated when searching or if data exists - Source = $appInfo.source - Architecture = $arch - DownloadStatus = "" + IsSelected = $true # Imported apps are marked as selected + Name = $appInfo.name + Id = $appInfo.id + Version = "" # Will be populated when searching or if data exists + Source = $appInfo.source + Architecture = $arch + AdditionalExitCodes = if ($appInfo.PSObject.Properties['AdditionalExitCodes']) { $appInfo.AdditionalExitCodes } else { "" } + IgnoreNonZeroExitCodes = if ($appInfo.PSObject.Properties['IgnoreNonZeroExitCodes']) { [bool]$appInfo.IgnoreNonZeroExitCodes } else { $false } + DownloadStatus = "" }) } } @@ -191,13 +195,15 @@ function Search-WingetPackagesPublic { $output = $results | ForEach-Object -Parallel { $arch = if ($_.Source -eq 'msstore') { 'NA' } else { $using:DefaultArchitecture } [PSCustomObject]@{ - IsSelected = [bool]$false - Name = [string]$_.Name - Id = [string]$_.Id - Version = [string]$_.Version - Source = [string]$_.Source - Architecture = [string]$arch - DownloadStatus = [string]::Empty + IsSelected = [bool]$false + Name = [string]$_.Name + Id = [string]$_.Id + Version = [string]$_.Version + Source = [string]$_.Source + Architecture = [string]$arch + AdditionalExitCodes = [string]::Empty + IgnoreNonZeroExitCodes = [bool]$false + DownloadStatus = [string]::Empty } } -ThrottleLimit 20 WriteLog "Winget search completed. Created $($output.Count) output objects." @@ -724,6 +730,42 @@ function Invoke-WingetDownload { # Select only necessary properties before passing to Invoke-ParallelProcessing $itemsToProcess = $selectedApps | Select-Object Name, Id, Source, Version, Architecture # Include Version and Architecture if needed + + # Before downloading, persist the selected apps to AppList.json including exit-code fields (parity with Save-WingetList) + try { + # Determine AppList.json path; default if empty + if ([string]::IsNullOrWhiteSpace($localAppListJsonPath)) { + $localAppListJsonPath = Join-Path -Path $localAppsPath -ChildPath "AppList.json" + $taskArguments.AppListJsonPath = $localAppListJsonPath + WriteLog "AppListJsonPath was empty. Defaulting to: $localAppListJsonPath" + } + + # Build apps payload from current selection, preserving AdditionalExitCodes/IgnoreNonZeroExitCodes + $appListToSave = @{ + apps = @($selectedApps | ForEach-Object { + [ordered]@{ + name = (ConvertTo-SafeName -Name $_.Name) + id = $_.Id + source = $_.Source.ToLower() + architecture = $_.Architecture + AdditionalExitCodes = if ($_.PSObject.Properties['AdditionalExitCodes']) { $_.AdditionalExitCodes } else { "" } + IgnoreNonZeroExitCodes = if ($_.PSObject.Properties['IgnoreNonZeroExitCodes']) { [bool]$_.IgnoreNonZeroExitCodes } else { $false } + } + }) + } + + # Ensure destination directory exists and write AppList.json + $destDir = Split-Path -Parent $localAppListJsonPath + if (-not (Test-Path -LiteralPath $destDir)) { + [void][System.IO.Directory]::CreateDirectory($destDir) + } + $appListToSave | ConvertTo-Json -Depth 10 | Set-Content -Path $localAppListJsonPath -Encoding UTF8 + WriteLog "Persisted AppList.json with selected apps and exit-code fields to: $localAppListJsonPath" + } + catch { + WriteLog "Warning: Failed to persist AppList.json prior to download. Error: $($_.Exception.Message)" + } + # Invoke the centralized parallel processing function # Pass task type and task-specific arguments Invoke-ParallelProcessing -ItemsToProcess $itemsToProcess `