fix: repair popup install binding, dialog titles, presets switch, radio groups, JSON, tests (#4893)

* fix: repair popup install binding, dialog titles, presets switch, radio groups, JSON, tests

- Invoke-WPFInstall: add param block so the right-click app popup binds
  -PackagesToInstall. It previously dropped the argument into $args and
  installed the whole selected list instead of the clicked app (or warned
  that nothing was selected).
- Replace undefined $AppTitle with "WinUtil" in Install/UnInstall warning
  boxes, matching the XAML window title and sibling dialogs.
- tweaks.json: remove stray nested "link" inside the WPFToggleScrollbars
  registry entry (dead property; registry entries expose only the six
  consumed fields).
- Invoke-WPFPresets: fix dead switch cases to the wildcard-pattern
  convention so selectedFeatures/selectedToggles clear correctly.
- Invoke-WPFUIElements: store the group StackPanel in radioButtonGroups so
  grouped radio buttons share one container (else branch was unreachable).
- sanity.Tests.ps1: pass the Windows PowerShell parser script via
  -EncodedCommand; -Command string marshaling corrupted backticks/quotes and
  produced false parse failures.
- install-workflow.Tests.ps1: remove the AppTitle workaround and update
  title assertions to "WinUtil"; drop orphaned cleanup lines.
- Add CHANGES.md with technical and plain-language explanations.

Verified: each bug reproduced against HEAD, fixes demonstrated to change
behavior, full Pester suite 471/471.

* Delete

* test: cover the explicit popup install parameter path

Add a regression test invoking Invoke-WPFInstall -PackagesToInstall with a
package different from the default sync.selectedApps selection and assert
the runspace receives the explicit object, mirroring the Initialize-WPFUI
popup call shape. Existing default-path coverage is unchanged.
This commit is contained in:
Mohammad Faiz
2026-08-03 12:03:03 -05:00
committed by GitHub
parent f3cf1587e0
commit f7361fdb72
7 changed files with 35 additions and 20 deletions
+1 -2
View File
@@ -1454,8 +1454,7 @@
"Value": "0", "Value": "0",
"Type": "DWord", "Type": "DWord",
"OriginalValue": "1", "OriginalValue": "1",
"DefaultState": "false", "DefaultState": "false"
"link": "https://winutil.christitus.com/dev/tweaks/customize-preferences/scrollbars"
} }
], ],
"link": "https://winutil.christitus.com/code-reference/tweaks/customize-preferences/scrollbars" "link": "https://winutil.christitus.com/code-reference/tweaks/customize-preferences/scrollbars"
+6 -4
View File
@@ -3,19 +3,21 @@ function Invoke-WPFInstall {
.SYNOPSIS .SYNOPSIS
Installs the selected programs using winget, if one or more of the selected programs are already installed on the system, winget will try and perform an upgrade if there's a newer version to install. Installs the selected programs using winget, if one or more of the selected programs are already installed on the system, winget will try and perform an upgrade if there's a newer version to install.
#> #>
param(
$PackagesToInstall = $sync.selectedApps | Foreach-Object { $sync.configs.applicationsHashtable.$_ } [Parameter(Mandatory = $false)]
[PSObject[]]$PackagesToInstall = $($sync.selectedApps | Foreach-Object { $sync.configs.applicationsHashtable.$_ })
)
if($sync.ProcessRunning) { if($sync.ProcessRunning) {
$msg = "[Invoke-WPFInstall] An Install process is currently running." $msg = "[Invoke-WPFInstall] An Install process is currently running."
Show-WinUtilMessage -Message $msg -Title "Winutil" -Button "OK" -Icon "Warning" Show-WinUtilMessage -Message $msg -Title "WinUtil" -Button "OK" -Icon "Warning"
return return
} }
if ($PackagesToInstall.Count -eq 0) { if ($PackagesToInstall.Count -eq 0) {
$WarningMsg = "Please select the program(s) to install or upgrade." $WarningMsg = "Please select the program(s) to install or upgrade."
Show-WinUtilMessage -Message $WarningMsg -Title $AppTitle -Button "OK" -Icon "Warning" Show-WinUtilMessage -Message $WarningMsg -Title "WinUtil" -Button "OK" -Icon "Warning"
return return
} }
+2 -2
View File
@@ -38,8 +38,8 @@ function Invoke-WPFPresets {
"WPFTweak*" { $sync.selectedTweaks = [System.Collections.Generic.List[string]]::new() } "WPFTweak*" { $sync.selectedTweaks = [System.Collections.Generic.List[string]]::new() }
"WPFInstall*" { $sync.selectedApps = [System.Collections.Generic.List[string]]::new() } "WPFInstall*" { $sync.selectedApps = [System.Collections.Generic.List[string]]::new() }
"WPFAppx*" { $sync.selectedAppx = [System.Collections.Generic.List[string]]::new() } "WPFAppx*" { $sync.selectedAppx = [System.Collections.Generic.List[string]]::new() }
"WPFeatures" { $sync.selectedFeatures = [System.Collections.Generic.List[string]]::new() } "WPFFeature*" { $sync.selectedFeatures = [System.Collections.Generic.List[string]]::new() }
"WPFToggle" { $sync.selectedToggles = [System.Collections.Generic.List[string]]::new() } "WPFToggle*" { $sync.selectedToggles = [System.Collections.Generic.List[string]]::new() }
default {} default {}
} }
@@ -327,6 +327,7 @@ function Invoke-WPFUIElements {
$groupStackPanel = New-Object Windows.Controls.StackPanel $groupStackPanel = New-Object Windows.Controls.StackPanel
$groupStackPanel.Orientation = "Vertical" $groupStackPanel.Orientation = "Vertical"
[System.Windows.Automation.AutomationProperties]::SetName($groupStackPanel, $entryInfo.GroupName) [System.Windows.Automation.AutomationProperties]::SetName($groupStackPanel, $entryInfo.GroupName)
$radioButtonGroups[$entryInfo.GroupName] = $groupStackPanel
# Add the group container to the ItemsControl # Add the group container to the ItemsControl
$itemsControl.Items.Add($groupStackPanel) | Out-Null $itemsControl.Items.Add($groupStackPanel) | Out-Null
+2 -2
View File
@@ -11,13 +11,13 @@ function Invoke-WPFUnInstall {
if($sync.ProcessRunning) { if($sync.ProcessRunning) {
$msg = "[Invoke-WPFUnInstall] Install process is currently running" $msg = "[Invoke-WPFUnInstall] Install process is currently running"
Show-WinUtilMessage -Message $msg -Title "Winutil" -Button "OK" -Icon "Warning" Show-WinUtilMessage -Message $msg -Title "WinUtil" -Button "OK" -Icon "Warning"
return return
} }
if ($PackagesToUninstall.Count -eq 0) { if ($PackagesToUninstall.Count -eq 0) {
$WarningMsg = "Please select the program(s) to uninstall" $WarningMsg = "Please select the program(s) to uninstall"
Show-WinUtilMessage -Message $WarningMsg -Title $AppTitle -Button "OK" -Icon "Warning" Show-WinUtilMessage -Message $WarningMsg -Title "WinUtil" -Button "OK" -Icon "Warning"
return return
} }
+21 -9
View File
@@ -66,7 +66,6 @@ BeforeAll {
$selectedApps.Add($key) $selectedApps.Add($key)
} }
$script:AppTitle = "Winutil"
$script:sync = [Hashtable]::Synchronized(@{ $script:sync = [Hashtable]::Synchronized(@{
ProcessRunning = $ProcessRunning ProcessRunning = $ProcessRunning
selectedApps = $selectedApps selectedApps = $selectedApps
@@ -122,7 +121,6 @@ Describe "Invoke-WPFInstall entrypoint" {
AfterEach { AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name AppTitle -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedInstallScriptBlock -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name capturedInstallScriptBlock -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedInstallParameterList -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name capturedInstallParameterList -Scope Script -ErrorAction SilentlyContinue
} }
@@ -146,6 +144,23 @@ Describe "Invoke-WPFInstall entrypoint" {
} }
} }
It "queues the explicit app popup package over the selected apps" {
$explicitPackage = New-WinUtilPackage -Name "VLC" -Winget "VideoLAN.VLC" -Choco "vlc"
Invoke-WPFInstall -PackagesToInstall $explicitPackage
Should -Invoke -CommandName Invoke-WPFRunspace -Times 1 -Exactly -ParameterFilter {
$ScriptBlock -is [scriptblock] -and
$ParameterList.Count -eq 2 -and
$ParameterList[0][0] -eq "PackagesToInstall" -and
@($ParameterList[0][1]).Count -eq 1 -and
@($ParameterList[0][1])[0].winget -eq "VideoLAN.VLC" -and
$ParameterList[1][0] -eq "ManagerPreference" -and
$ParameterList[1][1] -eq "Winget"
}
Should -Invoke -CommandName Show-WinUtilMessage -Times 0 -Exactly
}
It "prompts and exits when no packages are selected" { It "prompts and exits when no packages are selected" {
New-WinUtilInstallTestContext New-WinUtilInstallTestContext
@@ -153,7 +168,7 @@ Describe "Invoke-WPFInstall entrypoint" {
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Message -eq "Please select the program(s) to install or upgrade." -and $Message -eq "Please select the program(s) to install or upgrade." -and
$Title -eq "Winutil" -and $Title -eq "WinUtil" -and
$Button -eq "OK" -and $Button -eq "OK" -and
$Icon -eq "Warning" $Icon -eq "Warning"
} }
@@ -167,7 +182,7 @@ Describe "Invoke-WPFInstall entrypoint" {
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Message -eq "[Invoke-WPFInstall] An Install process is currently running." -and $Message -eq "[Invoke-WPFInstall] An Install process is currently running." -and
$Title -eq "Winutil" -and $Title -eq "WinUtil" -and
$Button -eq "OK" -and $Button -eq "OK" -and
$Icon -eq "Warning" $Icon -eq "Warning"
} }
@@ -201,7 +216,6 @@ Describe "Invoke-WPFInstall runspace body" {
AfterEach { AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name AppTitle -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedInstallScriptBlock -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name capturedInstallScriptBlock -Scope Script -ErrorAction SilentlyContinue
} }
@@ -289,7 +303,6 @@ Describe "Invoke-WPFUnInstall entrypoint" {
AfterEach { AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name AppTitle -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedUninstallScriptBlock -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name capturedUninstallScriptBlock -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedUninstallParameterList -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name capturedUninstallParameterList -Scope Script -ErrorAction SilentlyContinue
} }
@@ -324,7 +337,7 @@ Describe "Invoke-WPFUnInstall entrypoint" {
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Message -eq "Please select the program(s) to uninstall" -and $Message -eq "Please select the program(s) to uninstall" -and
$Title -eq "Winutil" -and $Title -eq "WinUtil" -and
$Button -eq "OK" -and $Button -eq "OK" -and
$Icon -eq "Warning" $Icon -eq "Warning"
} }
@@ -338,7 +351,7 @@ Describe "Invoke-WPFUnInstall entrypoint" {
Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Message -eq "[Invoke-WPFUnInstall] Install process is currently running" -and $Message -eq "[Invoke-WPFUnInstall] Install process is currently running" -and
$Title -eq "Winutil" -and $Title -eq "WinUtil" -and
$Button -eq "OK" -and $Button -eq "OK" -and
$Icon -eq "Warning" $Icon -eq "Warning"
} }
@@ -379,7 +392,6 @@ Describe "Invoke-WPFUnInstall runspace body" {
AfterEach { AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name AppTitle -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedUninstallScriptBlock -Scope Script -ErrorAction SilentlyContinue Remove-Variable -Name capturedUninstallScriptBlock -Scope Script -ErrorAction SilentlyContinue
} }
+2 -1
View File
@@ -53,7 +53,8 @@ if ($failed.Count -gt 0) {
} }
'@ '@
$output = & $windowsPowerShell.Source -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command $parseScript 2>&1 $encodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($parseScript))
$output = & $windowsPowerShell.Source -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encodedCommand 2>&1
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
throw "Windows PowerShell parser failed:`n$($output | Out-String)" throw "Windows PowerShell parser failed:`n$($output | Out-String)"
} }