From 9ed06e793ad560756ee5b6cca78f39dda8145381 Mon Sep 17 00:00:00 2001 From: Chris Titus Date: Wed, 15 Jul 2026 22:35:58 -0500 Subject: [PATCH] Replace install progress overlay with shared indicator (#4844) * Use shared progress for app workflows * Remove legacy install progress overlay * Document shared app progress indicator * Address progress review feedback * Track all Win11 ISO progress * Restore Win11 Creator work on relaunch --- config/themes.json | 4 - .../userguide/getting-started/_index.md | 2 +- functions/private/Hide-WPFInstallAppBusy.ps1 | 12 --- .../private/Initialize-InstallAppArea.ps1 | 66 ---------------- .../private/Initialize-WinUtilTabContent.ps1 | 2 +- functions/private/Invoke-WinUtilISO.ps1 | 70 ++++++++++------- functions/private/Invoke-WinUtilISOUSB.ps1 | 16 ++-- functions/private/Set-WinUtilProgressbar.ps1 | 26 ------- .../Set-WinUtilTweaksProgressIndicator.ps1 | 4 +- functions/private/Show-WPFInstallAppBusy.ps1 | 23 ------ functions/public/Invoke-WPFButton.ps1 | 3 +- functions/public/Invoke-WPFInstall.ps1 | 60 ++++++++++++-- functions/public/Invoke-WPFUnInstall.ps1 | 59 ++++++++++++-- functions/public/Invoke-WPFtweaksbutton.ps1 | 4 - functions/public/Invoke-WPFundoall.ps1 | 2 - pester/appx.Tests.ps1 | 4 - pester/install-workflow.Tests.ps1 | 78 +++++++++++++++---- pester/lazy-tabs.Tests.ps1 | 17 ++++ pester/tweaks.Tests.ps1 | 4 - pester/ui-state.Tests.ps1 | 18 ++--- pester/win11creator.Tests.ps1 | 16 ++++ pester/xaml.Tests.ps1 | 7 +- scripts/start.ps1 | 1 + xaml/inputXML.xaml | 2 +- 24 files changed, 270 insertions(+), 230 deletions(-) delete mode 100644 functions/private/Hide-WPFInstallAppBusy.ps1 delete mode 100644 functions/private/Set-WinUtilProgressbar.ps1 delete mode 100644 functions/private/Show-WPFInstallAppBusy.ps1 diff --git a/config/themes.json b/config/themes.json index 821738dd..3d0cd9cf 100644 --- a/config/themes.json +++ b/config/themes.json @@ -46,7 +46,6 @@ "AppInstallUnselectedColor": "#F7F7F7", "AppInstallHighlightedColor": "#CFCFCF", "AppInstallSelectedColor": "#C2C2C2", - "AppInstallOverlayBackgroundColor": "#6A6D72", "ComboBoxForegroundColor": "#232629", "ComboBoxBackgroundColor": "#F7F7F7", "LabelboxForegroundColor": "#232629", @@ -60,7 +59,6 @@ "ScrollBarDraggingColor": "#6A6D72", "ProgressBarForegroundColor": "#2E77FF", "ProgressBarBackgroundColor": "Transparent", - "ProgressBarTextColor": "#232629", "ButtonInstallBackgroundColor": "#F7F7F7", "ButtonTweaksBackgroundColor": "#F7F7F7", "ButtonConfigBackgroundColor": "#F7F7F7", @@ -88,7 +86,6 @@ "AppInstallUnselectedColor": "#232629", "AppInstallHighlightedColor": "#3C3C3C", "AppInstallSelectedColor": "#4C4C4C", - "AppInstallOverlayBackgroundColor": "#2E3135", "ComboBoxForegroundColor": "#F7F7F7", "ComboBoxBackgroundColor": "#1E3747", "LabelboxForegroundColor": "#5BDCFF", @@ -102,7 +99,6 @@ "ScrollBarDraggingColor": "#5E81AC", "ProgressBarForegroundColor": "#6EFF72", "ProgressBarBackgroundColor": "Transparent", - "ProgressBarTextColor": "#232629", "ButtonInstallBackgroundColor": "#222222", "ButtonTweaksBackgroundColor": "#333333", "ButtonConfigBackgroundColor": "#444444", diff --git a/docs/content/userguide/getting-started/_index.md b/docs/content/userguide/getting-started/_index.md index f2a5ae4e..efb16343 100644 --- a/docs/content/userguide/getting-started/_index.md +++ b/docs/content/userguide/getting-started/_index.md @@ -138,7 +138,7 @@ For a better Windows experience with minimal risk: 1. Check multiple application boxes 2. All checked apps will install in sequence -3. Progress is shown in the bottom panel +3. Install and uninstall progress is shown in the window-level bottom panel, including the current package or package-manager batch and overall completion ### Applying Tweaks diff --git a/functions/private/Hide-WPFInstallAppBusy.ps1 b/functions/private/Hide-WPFInstallAppBusy.ps1 deleted file mode 100644 index 7cf7b7da..00000000 --- a/functions/private/Hide-WPFInstallAppBusy.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -function Hide-WPFInstallAppBusy { - <# - .SYNOPSIS - Hides the busy overlay in the install app area of the WPF form. - This is used to indicate that an install or uninstall has finished. - #> - Invoke-WPFUIThread -ScriptBlock { - $sync.InstallAppAreaOverlay.Visibility = [Windows.Visibility]::Collapsed - $sync.InstallAppAreaBorder.IsEnabled = $true - $sync.InstallAppAreaScrollViewer.Effect.Radius = 0 - } -} diff --git a/functions/private/Initialize-InstallAppArea.ps1 b/functions/private/Initialize-InstallAppArea.ps1 index 85bd50a7..bb21c138 100644 --- a/functions/private/Initialize-InstallAppArea.ps1 +++ b/functions/private/Initialize-InstallAppArea.ps1 @@ -5,8 +5,6 @@ This is used as the parent object for all category and app entries on the install tab Used to as part of the Install Tab UI generation - Also creates an overlay with a progress bar and text to indicate that an install or uninstall is in progress - .PARAMETER TargetElement The element to which the AppArea should be added @@ -19,22 +17,14 @@ $Border = New-Object Windows.Controls.Border $Border.VerticalAlignment = "Stretch" $Border.SetResourceReference([Windows.Controls.Control]::StyleProperty, "BorderStyle") - $sync.InstallAppAreaBorder = $Border - # Add a ScrollViewer, because the ItemsControl does not support scrolling by itself $scrollViewer = New-Object Windows.Controls.ScrollViewer $scrollViewer.VerticalScrollBarVisibility = 'Auto' $scrollViewer.HorizontalAlignment = 'Stretch' $scrollViewer.VerticalAlignment = 'Stretch' $scrollViewer.CanContentScroll = $true - $sync.InstallAppAreaScrollViewer = $scrollViewer $Border.Child = $scrollViewer - # Initialize the Blur Effect for the ScrollViewer, which will be used to indicate that an install/uninstall is in progress - $blurEffect = New-Object Windows.Media.Effects.BlurEffect - $blurEffect.Radius = 0 - $scrollViewer.Effect = $blurEffect - ## Create the ItemsControl, which will be the parent of all the app entries $itemsControl = New-Object Windows.Controls.ItemsControl $itemsControl.HorizontalAlignment = 'Stretch' @@ -52,61 +42,5 @@ # Add the Border containing the App Area to the target Grid $targetGrid.Children.Add($Border) | Out-Null - $overlay = New-Object Windows.Controls.Border - $overlay.CornerRadius = New-Object Windows.CornerRadius(10) - $overlay.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallOverlayBackgroundColor") - $overlay.Visibility = [Windows.Visibility]::Collapsed - - # Also add the overlay to the target Grid on top of the App Area - $targetGrid.Children.Add($overlay) | Out-Null - $sync.InstallAppAreaOverlay = $overlay - - $overlayText = New-Object Windows.Controls.TextBlock - $overlayText.Text = "Installing apps..." - $overlayText.HorizontalAlignment = 'Center' - $overlayText.VerticalAlignment = 'Center' - $overlayText.SetResourceReference([Windows.Controls.TextBlock]::ForegroundProperty, "MainForegroundColor") - $overlayText.Background = "Transparent" - $overlayText.SetResourceReference([Windows.Controls.TextBlock]::FontSizeProperty, "HeaderFontSize") - $overlayText.SetResourceReference([Windows.Controls.TextBlock]::FontFamilyProperty, "MainFontFamily") - $overlayText.SetResourceReference([Windows.Controls.TextBlock]::FontWeightProperty, "MainFontWeight") - $overlayText.SetResourceReference([Windows.Controls.TextBlock]::MarginProperty, "MainMargin") - $sync.InstallAppAreaOverlayText = $overlayText - - $progressbar = New-Object Windows.Controls.ProgressBar - $progressbar.Name = "ProgressBar" - $progressbar.Width = 250 - $progressbar.Height = 50 - $sync.ProgressBar = $progressbar - - # Add a TextBlock overlay for the progress bar text - $progressBarTextBlock = New-Object Windows.Controls.TextBlock - $progressBarTextBlock.Name = "progressBarTextBlock" - $progressBarTextBlock.FontWeight = [Windows.FontWeights]::Bold - $progressBarTextBlock.FontSize = 16 - $progressBarTextBlock.Width = $progressbar.Width - $progressBarTextBlock.Height = $progressbar.Height - $progressBarTextBlock.SetResourceReference([Windows.Controls.TextBlock]::ForegroundProperty, "ProgressBarTextColor") - $progressBarTextBlock.TextTrimming = "CharacterEllipsis" - $progressBarTextBlock.Background = "Transparent" - $sync.progressBarTextBlock = $progressBarTextBlock - - # Create a Grid to overlay the text on the progress bar - $progressGrid = New-Object Windows.Controls.Grid - $progressGrid.Width = $progressbar.Width - $progressGrid.Height = $progressbar.Height - $progressGrid.Margin = "0,10,0,10" - $progressGrid.Children.Add($progressbar) | Out-Null - $progressGrid.Children.Add($progressBarTextBlock) | Out-Null - - $overlayStackPanel = New-Object Windows.Controls.StackPanel - $overlayStackPanel.Orientation = "Vertical" - $overlayStackPanel.HorizontalAlignment = 'Center' - $overlayStackPanel.VerticalAlignment = 'Center' - $overlayStackPanel.Children.Add($overlayText) | Out-Null - $overlayStackPanel.Children.Add($progressGrid) | Out-Null - - $overlay.Child = $overlayStackPanel - return $itemsControl } diff --git a/functions/private/Initialize-WinUtilTabContent.ps1 b/functions/private/Initialize-WinUtilTabContent.ps1 index f223a105..3394a8f2 100644 --- a/functions/private/Initialize-WinUtilTabContent.ps1 +++ b/functions/private/Initialize-WinUtilTabContent.ps1 @@ -28,7 +28,7 @@ function Initialize-WinUtilTabContent { "AppX" { Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2 } - "Win11 Creator" { + "Win11ISO" { if ($sync.Form -and $sync.Form.Dispatcher) { $sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null } diff --git a/functions/private/Invoke-WinUtilISO.ps1 b/functions/private/Invoke-WinUtilISO.ps1 index d63c74bf..53dd31d8 100644 --- a/functions/private/Invoke-WinUtilISO.ps1 +++ b/functions/private/Invoke-WinUtilISO.ps1 @@ -47,7 +47,7 @@ function Invoke-WinUtilISOMountAndVerify { } Write-Win11ISOLog "Mounting ISO: $isoPath" - Set-WinUtilProgressBar -Label "Mounting ISO..." -Percent 10 + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Mounting ISO..." -Percent 10 try { Mount-DiskImage -ImagePath $isoPath @@ -59,7 +59,7 @@ function Invoke-WinUtilISOMountAndVerify { $driveLetter = (Get-DiskImage -ImagePath $isoPath | Get-Volume).DriveLetter + ":" Write-Win11ISOLog "Mounted at drive $driveLetter" - Set-WinUtilProgressBar -Label "Verifying ISO contents..." -Percent 30 + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Verifying ISO contents..." -Percent 30 $wimPath = Join-Path $driveLetter "sources\install.wim" $esdPath = Join-Path $driveLetter "sources\install.esd" @@ -70,13 +70,13 @@ function Invoke-WinUtilISOMountAndVerify { [System.Windows.MessageBox]::Show( "This does not appear to be a valid Windows ISO.`n`ninstall.wim / install.esd was not found.", "Invalid ISO", "OK", "Error") - Set-WinUtilProgressBar -Label "" -Percent 0 + Set-WinUtilTweaksProgressIndicator -Visible $false return } $activeWim = if (Test-Path $wimPath) { $wimPath } else { $esdPath } - Set-WinUtilProgressBar -Label "Reading image metadata..." -Percent 55 + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Reading image metadata..." -Percent 55 $imageInfo = Get-WindowsImage -ImagePath $activeWim | Select-Object ImageIndex, ImageName if (-not ($imageInfo | Where-Object { $_.ImageName -match "Windows 11" })) { @@ -85,7 +85,7 @@ function Invoke-WinUtilISOMountAndVerify { [System.Windows.MessageBox]::Show( "No Windows 11 edition was found in this ISO.`n`nOnly official Windows 11 ISOs are supported.", "Not a Windows 11 ISO", "OK", "Error") - Set-WinUtilProgressBar -Label "" -Percent 0 + Set-WinUtilTweaksProgressIndicator -Visible $false return } @@ -114,7 +114,7 @@ function Invoke-WinUtilISOMountAndVerify { $sync["Win11ISOImagePath"] = $isoPath $sync["WPFWin11ISOModifySection"].Visibility = "Visible" - Set-WinUtilProgressBar -Label "ISO verified" -Percent 100 + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "ISO verified" -Percent 100 Write-Win11ISOLog "ISO verified OK. Editions found: $($imageInfo.Count)" } catch { Write-Win11ISOLog "ERROR during mount/verify: $_" @@ -123,7 +123,7 @@ function Invoke-WinUtilISOMountAndVerify { "Error", "OK", "Error") } finally { Start-Sleep -Milliseconds 800 - Set-WinUtilProgressBar -Label "" -Percent 0 + Set-WinUtilTweaksProgressIndicator -Visible $false } } @@ -151,6 +151,7 @@ function Invoke-WinUtilISOModify { $sync["WPFWin11ISOModifyButton"].IsEnabled = $false $sync["Win11ISOModifying"] = $true + $sync["Win11ISOProcessRunning"] = $true $workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')" if (Test-Path $workDir) { @@ -203,9 +204,10 @@ function Invoke-WinUtilISOModify { function SetProgress($label, $pct) { $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = $label - $sync.progressBarTextBlock.ToolTip = $label - $sync.ProgressBar.Value = [Math]::Max($pct, 5) + $sync["WPFTweaksProgressBar"].Visibility = "Visible" + $sync["WPFTweaksProgressLabel"].Text = $label + $sync["WPFTweaksProgressLabel"].ToolTip = $label + $sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5) }) } @@ -452,10 +454,12 @@ function Invoke-WinUtilISOModify { } finally { Start-Sleep -Milliseconds 800 $sync["Win11ISOModifying"] = $false + $sync["Win11ISOProcessRunning"] = $false $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = "" - $sync.progressBarTextBlock.ToolTip = "" - $sync.ProgressBar.Value = 0 + $sync["WPFTweaksProgressBar"].Visibility = "Collapsed" + $sync["WPFTweaksProgressLabel"].Text = "" + $sync["WPFTweaksProgressLabel"].ToolTip = "" + $sync["WPFTweaksProgressValue"].Value = 0 $sync["WPFWin11ISOModifyButton"].IsEnabled = $true if ($sync["WPFWin11ISOOutputSection"].Visibility -ne "Visible") { $sync["WPFWin11ISOSelectSection"].Visibility = "Visible" @@ -514,6 +518,7 @@ function Invoke-WinUtilISOCleanAndReset { } $sync["WPFWin11ISOCleanResetButton"].IsEnabled = $false + $sync["Win11ISOProcessRunning"] = $true $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() $runspace.ApartmentState = "STA" @@ -538,9 +543,10 @@ function Invoke-WinUtilISOCleanAndReset { function SetProgress($label, $pct) { $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = $label - $sync.progressBarTextBlock.ToolTip = $label - $sync.ProgressBar.Value = [Math]::Max($pct, 5) + $sync["WPFTweaksProgressBar"].Visibility = "Visible" + $sync["WPFTweaksProgressLabel"].Text = $label + $sync["WPFTweaksProgressLabel"].ToolTip = $label + $sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5) }) } @@ -628,20 +634,24 @@ function Invoke-WinUtilISOCleanAndReset { $sync["WPFWin11ISOModifyButton"].IsEnabled = $true $sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true - $sync.progressBarTextBlock.Text = "" - $sync.progressBarTextBlock.ToolTip = "" - $sync.ProgressBar.Value = 0 + $sync["WPFTweaksProgressBar"].Visibility = "Collapsed" + $sync["WPFTweaksProgressLabel"].Text = "" + $sync["WPFTweaksProgressLabel"].ToolTip = "" + $sync["WPFTweaksProgressValue"].Value = 0 $sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin." }) } catch { Log "ERROR during Clean & Reset: $_" $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = "" - $sync.progressBarTextBlock.ToolTip = "" - $sync.ProgressBar.Value = 0 + $sync["WPFTweaksProgressBar"].Visibility = "Collapsed" + $sync["WPFTweaksProgressLabel"].Text = "" + $sync["WPFTweaksProgressLabel"].ToolTip = "" + $sync["WPFTweaksProgressValue"].Value = 0 $sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true }) + } finally { + $sync["Win11ISOProcessRunning"] = $false } }) @@ -706,6 +716,7 @@ function Invoke-WinUtilISOExport { } $sync["WPFWin11ISOChooseISOButton"].IsEnabled = $false + $sync["Win11ISOProcessRunning"] = $true $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() $runspace.ApartmentState = "STA" @@ -726,9 +737,10 @@ function Invoke-WinUtilISOExport { function SetProgress($label, $pct) { $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = $label - $sync.progressBarTextBlock.ToolTip = $label - $sync.ProgressBar.Value = [Math]::Max($pct, 5) + $sync["WPFTweaksProgressBar"].Visibility = "Visible" + $sync["WPFTweaksProgressLabel"].Text = $label + $sync["WPFTweaksProgressLabel"].ToolTip = $label + $sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5) }) } @@ -788,10 +800,12 @@ function Invoke-WinUtilISOExport { }) } finally { Start-Sleep -Milliseconds 800 + $sync["Win11ISOProcessRunning"] = $false $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = "" - $sync.progressBarTextBlock.ToolTip = "" - $sync.ProgressBar.Value = 0 + $sync["WPFTweaksProgressBar"].Visibility = "Collapsed" + $sync["WPFTweaksProgressLabel"].Text = "" + $sync["WPFTweaksProgressLabel"].ToolTip = "" + $sync["WPFTweaksProgressValue"].Value = 0 $sync["WPFWin11ISOChooseISOButton"].IsEnabled = $true }) } diff --git a/functions/private/Invoke-WinUtilISOUSB.ps1 b/functions/private/Invoke-WinUtilISOUSB.ps1 index 6ebc157d..3c9dee06 100644 --- a/functions/private/Invoke-WinUtilISOUSB.ps1 +++ b/functions/private/Invoke-WinUtilISOUSB.ps1 @@ -61,6 +61,7 @@ function Invoke-WinUtilISOWriteUSB { } $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false + $sync["Win11ISOProcessRunning"] = $true Write-Win11ISOLog "Starting USB write to Disk $diskNum..." $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() @@ -86,9 +87,10 @@ function Invoke-WinUtilISOWriteUSB { function SetProgress($label, $pct) { $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = $label - $sync.progressBarTextBlock.ToolTip = $label - $sync.ProgressBar.Value = [Math]::Max($pct, 5) + $sync["WPFTweaksProgressBar"].Visibility = "Visible" + $sync["WPFTweaksProgressLabel"].Text = $label + $sync["WPFTweaksProgressLabel"].ToolTip = $label + $sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5) }) } @@ -255,10 +257,12 @@ function Invoke-WinUtilISOWriteUSB { }) } finally { Start-Sleep -Milliseconds 800 + $sync["Win11ISOProcessRunning"] = $false $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ - $sync.progressBarTextBlock.Text = "" - $sync.progressBarTextBlock.ToolTip = "" - $sync.ProgressBar.Value = 0 + $sync["WPFTweaksProgressBar"].Visibility = "Collapsed" + $sync["WPFTweaksProgressLabel"].Text = "" + $sync["WPFTweaksProgressLabel"].ToolTip = "" + $sync["WPFTweaksProgressValue"].Value = 0 $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true }) } diff --git a/functions/private/Set-WinUtilProgressbar.ps1 b/functions/private/Set-WinUtilProgressbar.ps1 deleted file mode 100644 index 47097b27..00000000 --- a/functions/private/Set-WinUtilProgressbar.ps1 +++ /dev/null @@ -1,26 +0,0 @@ -function Set-WinUtilProgressbar{ - <# - .SYNOPSIS - This function is used to Update the Progress Bar displayed in the winutil GUI. - It will be automatically hidden if the user clicks something and no process is running - .PARAMETER Label - The Text to be overlaid onto the Progress Bar - .PARAMETER PERCENT - The percentage of the Progress Bar that should be filled (0-100) - #> - param( - [string]$Label, - [ValidateRange(0,100)] - [int]$Percent - ) - - $progressLabel = $Label - - Invoke-WPFUIThread -ScriptBlock {$sync.progressBarTextBlock.Text = $progressLabel} - Invoke-WPFUIThread -ScriptBlock {$sync.progressBarTextBlock.ToolTip = $progressLabel} - if ($Percent -lt 5 ) { - $Percent = 5 # Ensure the progress bar is not empty, as it looks weird - } - Invoke-WPFUIThread -ScriptBlock { $sync.ProgressBar.Value = $Percent} - -} diff --git a/functions/private/Set-WinUtilTweaksProgressIndicator.ps1 b/functions/private/Set-WinUtilTweaksProgressIndicator.ps1 index 5a470768..9d17742d 100644 --- a/functions/private/Set-WinUtilTweaksProgressIndicator.ps1 +++ b/functions/private/Set-WinUtilTweaksProgressIndicator.ps1 @@ -2,8 +2,8 @@ function Set-WinUtilTweaksProgressIndicator { <# .SYNOPSIS Shows, updates, or hides the window-level progress indicator used by long-running - workflows such as Tweaks, Undo, and AppX management. It lives outside the TabControl, - so unlike the Install tab's progress bar it stays visible no matter which tab is active. + workflows such as app management, Tweaks, AppX management, and Win11 Creator. + It lives outside the TabControl, so it stays visible no matter which tab is active. .PARAMETER Visible Whether the indicator should be shown or hidden. .PARAMETER Label diff --git a/functions/private/Show-WPFInstallAppBusy.ps1 b/functions/private/Show-WPFInstallAppBusy.ps1 deleted file mode 100644 index 20211f79..00000000 --- a/functions/private/Show-WPFInstallAppBusy.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -function Show-WPFInstallAppBusy { - <# - .SYNOPSIS - Displays a busy overlay in the install app area of the WPF form. - This is used to indicate that an install or uninstall is in progress. - Dynamically updates the size of the overlay based on the app area on each invocation. - .PARAMETER text - The text to display in the busy overlay. Defaults to "Installing apps...". - #> - param ( - $text = "Installing apps..." - ) - $overlayText = $text - - Invoke-WPFUIThread -ScriptBlock { - $sync.InstallAppAreaOverlay.Visibility = [Windows.Visibility]::Visible - $sync.InstallAppAreaOverlay.Width = $($sync.InstallAppAreaScrollViewer.ActualWidth * 0.4) - $sync.InstallAppAreaOverlay.Height = $($sync.InstallAppAreaScrollViewer.ActualWidth * 0.4) - $sync.InstallAppAreaOverlayText.Text = $overlayText - $sync.InstallAppAreaBorder.IsEnabled = $false - $sync.InstallAppAreaScrollViewer.Effect.Radius = 5 - } -} diff --git a/functions/public/Invoke-WPFButton.ps1 b/functions/public/Invoke-WPFButton.ps1 index 4e247718..684d31c4 100644 --- a/functions/public/Invoke-WPFButton.ps1 +++ b/functions/public/Invoke-WPFButton.ps1 @@ -14,8 +14,7 @@ function Invoke-WPFButton { # Use this to get the name of the button #[System.Windows.MessageBox]::Show("$Button","Chris Titus Tech's Windows Utility","OK","Info") - if (-not $sync.ProcessRunning) { - Set-WinUtilProgressBar -label "" -percent 0 + if (-not $sync.ProcessRunning -and -not $sync.Win11ISOProcessRunning) { Set-WinUtilTweaksProgressIndicator -Visible $false } diff --git a/functions/public/Invoke-WPFInstall.ps1 b/functions/public/Invoke-WPFInstall.ps1 index d27ba2fc..f4b93386 100644 --- a/functions/public/Invoke-WPFInstall.ps1 +++ b/functions/public/Invoke-WPFInstall.ps1 @@ -31,33 +31,81 @@ function Invoke-WPFInstall { $packagesWinget = $packagesSorted['Winget'] $packagesChoco = $packagesSorted['Choco'] + $totalPackages = @($packagesWinget).Count + @($packagesChoco).Count + $completedPackages = 0 + $hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher Write-WinUtilLog -Component "Install" -Message "Install package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)" try { $sync.ProcessRunning = $true + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Preparing app install (0/$totalPackages)" -Percent 0 + Invoke-WPFUIThread -ScriptBlock { + if ($null -ne $sync.ItemsControl) { + $sync.ItemsControl.IsEnabled = $false + } + } + } + if($packagesWinget.Count -gt 0 -and $packagesWinget -ne "0") { - Show-WPFInstallAppBusy -text "Installing apps..." Install-WinUtilWinget - Install-WinUtilProgramWinget -Action Install -Programs $packagesWinget + foreach ($program in $packagesWinget) { + $position = $completedPackages + 1 + $startPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installing $program ($position/$totalPackages)" -Percent $startPercent + } + + Install-WinUtilProgramWinget -Action Install -Programs @($program) + $completedPackages++ + $completedPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installed $program ($completedPackages/$totalPackages)" -Percent $completedPercent + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) } + } + } } if($packagesChoco.Count -gt 0) { + $position = $completedPackages + 1 + $startPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installing Chocolatey packages ($position/$totalPackages)" -Percent $startPercent + } + Install-WinUtilChoco Install-WinUtilProgramChoco -Action Install -Programs $packagesChoco + $completedPackages += @($packagesChoco).Count + $completedPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Installed Chocolatey packages ($completedPackages/$totalPackages)" -Percent $completedPercent + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) } + } } - Hide-WPFInstallAppBusy Write-Host "===========================================" Write-Host "-- Installs have finished ---" Write-Host "===========================================" Write-WinUtilLog -Component "Install" -Message "Install workflow completed." - Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App install finished" -Percent 100 + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } + } } catch { - Hide-WPFInstallAppBusy Write-Host "===========================================" Write-Host "Error: $_" Write-Host "===========================================" Write-WinUtilLog -Level "ERROR" -Component "Install" -Message "Install workflow failed: $($_.Exception.Message)" - Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" } + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App install failed" -Percent 100 + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" } + } } finally { + if ($hasUI) { + Invoke-WPFUIThread -ScriptBlock { + if ($null -ne $sync.ItemsControl) { + $sync.ItemsControl.IsEnabled = $true + } + } + } $sync.ProcessRunning = $False } } diff --git a/functions/public/Invoke-WPFUnInstall.ps1 b/functions/public/Invoke-WPFUnInstall.ps1 index a51af86d..cdf86f3b 100644 --- a/functions/public/Invoke-WPFUnInstall.ps1 +++ b/functions/public/Invoke-WPFUnInstall.ps1 @@ -42,11 +42,21 @@ function Invoke-WPFUnInstall { $packagesWinget = $packagesSorted['Winget'] $packagesChoco = $packagesSorted['Choco'] + $totalPackages = @($packagesWinget).Count + @($packagesChoco).Count + $completedPackages = 0 + $hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher Write-WinUtilLog -Component "Uninstall" -Message "Uninstall package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)" try { $sync.ProcessRunning = $true - Show-WPFInstallAppBusy -text "Uninstalling apps..." + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Preparing app uninstall (0/$totalPackages)" -Percent 0 + Invoke-WPFUIThread -ScriptBlock { + if ($null -ne $sync.ItemsControl) { + $sync.ItemsControl.IsEnabled = $false + } + } + } if ($packagesWinget -contains "Microsoft.Edge") { New-Item -Path "$Env:SystemRoot\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\MicrosoftEdge.exe" -Force @@ -54,25 +64,62 @@ function Invoke-WPFUnInstall { # Uninstall all selected programs in new window if($packagesWinget.Count -gt 0) { - Install-WinUtilProgramWinget -Action Uninstall -Programs $packagesWinget + foreach ($program in $packagesWinget) { + $position = $completedPackages + 1 + $startPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalling $program ($position/$totalPackages)" -Percent $startPercent + } + + Install-WinUtilProgramWinget -Action Uninstall -Programs @($program) + $completedPackages++ + $completedPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalled $program ($completedPackages/$totalPackages)" -Percent $completedPercent + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) } + } + } } if($packagesChoco.Count -gt 0) { + $position = $completedPackages + 1 + $startPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalling Chocolatey packages ($position/$totalPackages)" -Percent $startPercent + } + Install-WinUtilProgramChoco -Action Uninstall -Programs $packagesChoco + $completedPackages += @($packagesChoco).Count + $completedPercent = [int](($completedPackages / $totalPackages) * 100) + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Uninstalled Chocolatey packages ($completedPackages/$totalPackages)" -Percent $completedPercent + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($completedPercent / 100) } + } } - Hide-WPFInstallAppBusy Write-Host "===========================================" Write-Host "-- Uninstalls have finished ---" Write-Host "===========================================" Write-WinUtilLog -Component "Uninstall" -Message "Uninstall workflow completed." - Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App uninstall finished" -Percent 100 + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } + } } catch { - Hide-WPFInstallAppBusy Write-Host "===========================================" Write-Host "Error: $_" Write-Host "===========================================" Write-WinUtilLog -Level "ERROR" -Component "Uninstall" -Message "Uninstall workflow failed: $($_.Exception.Message)" - Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" } + if ($hasUI) { + Set-WinUtilTweaksProgressIndicator -Visible $true -Label "App uninstall failed" -Percent 100 + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Error" -overlay "warning" } + } } finally { + if ($hasUI) { + Invoke-WPFUIThread -ScriptBlock { + if ($null -ne $sync.ItemsControl) { + $sync.ItemsControl.IsEnabled = $true + } + } + } $sync.ProcessRunning = $False } diff --git a/functions/public/Invoke-WPFtweaksbutton.ps1 b/functions/public/Invoke-WPFtweaksbutton.ps1 index 598941a5..d803b02a 100644 --- a/functions/public/Invoke-WPFtweaksbutton.ps1 +++ b/functions/public/Invoke-WPFtweaksbutton.ps1 @@ -39,14 +39,12 @@ function Invoke-WPFtweaksbutton { Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" } } - Set-WinUtilProgressBar -Label "Creating restore point" -Percent 0 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Creating restore point" -Percent 0 Write-WinUtilLog -Component "Tweaks" -Message "Creating restore point before applying selected tweaks." Invoke-WinUtilTweaks $restorePointTweak $completedSteps = 1 if ($tweaksToRun.Count -eq 0 -and $dnsProvider -eq "Default") { - Set-WinUtilProgressBar -Label "Tweaks finished" -Percent 100 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100 $sync.ProcessRunning = $false Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } @@ -77,14 +75,12 @@ function Invoke-WPFtweaksbutton { } for ($i = 0; $i -lt $tweaks.Count; $i++) { - Set-WinUtilProgressBar -Label "Applying $($tweaks[$i])" -Percent ($completedSteps / $totalSteps * 100) Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Applying $($tweaks[$i]) ($($completedSteps + 1)/$totalSteps)" -Percent ($completedSteps / $totalSteps * 100) Invoke-WinUtilTweaks $tweaks[$i] $completedSteps++ $progress = $completedSteps / $totalSteps Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value $progress } } - Set-WinUtilProgressBar -Label "Tweaks finished" -Percent 100 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100 $sync.ProcessRunning = $false Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } diff --git a/functions/public/Invoke-WPFundoall.ps1 b/functions/public/Invoke-WPFundoall.ps1 index be128136..47903009 100644 --- a/functions/public/Invoke-WPFundoall.ps1 +++ b/functions/public/Invoke-WPFundoall.ps1 @@ -33,13 +33,11 @@ function Invoke-WPFundoall { for ($i = 0; $i -lt $tweaks.Count; $i++) { - Set-WinUtilProgressBar -Label "Undoing $($tweaks[$i])" -Percent ($i / $tweaks.Count * 100) Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Undoing $($tweaks[$i]) ($($i + 1)/$($tweaks.Count))" -Percent ($i / $tweaks.Count * 100) Invoke-WinUtiltweaks $tweaks[$i] -undo $true Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($i/$tweaks.Count) } } - Set-WinUtilProgressBar -Label "Undo Tweaks Finished" -Percent 100 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Undo Tweaks Finished" -Percent 100 $sync.ProcessRunning = $false Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } diff --git a/pester/appx.Tests.ps1 b/pester/appx.Tests.ps1 index a732c225..e055a10f 100644 --- a/pester/appx.Tests.ps1 +++ b/pester/appx.Tests.ps1 @@ -46,9 +46,6 @@ BeforeAll { function Invoke-WPFUIThread { param([scriptblock]$ScriptBlock) } - function Set-WinUtilProgressBar { - param($Label, $Percent) - } function Set-WinUtilTweaksProgressIndicator { param($Visible, $Label, $Percent) } @@ -235,7 +232,6 @@ Describe "Get installed AppX selection" { WPFAppxMissing = [pscustomobject]@{ IsChecked = $false } }) - Mock Set-WinUtilProgressBar { } Mock Set-WinUtilTweaksProgressIndicator { } Mock Get-WinUtilInstalledAPPX { @("Example.Package") } Mock Invoke-WPFAppxInstall { } diff --git a/pester/install-workflow.Tests.ps1 b/pester/install-workflow.Tests.ps1 index e7d6751e..f2de1eae 100644 --- a/pester/install-workflow.Tests.ps1 +++ b/pester/install-workflow.Tests.ps1 @@ -18,10 +18,9 @@ BeforeAll { function Get-WinUtilSelectedPackages { param($PackageList, [string]$Preference) } - function Show-WPFInstallAppBusy { - param($text) + function Set-WinUtilTweaksProgressIndicator { + param($Visible, $Label, $Percent) } - function Hide-WPFInstallAppBusy { } function Install-WinUtilWinget { } function Install-WinUtilChoco { } function Install-WinUtilProgramWinget { @@ -74,6 +73,9 @@ BeforeAll { preferences = [pscustomobject]@{ packagemanager = "Winget" } + Form = [pscustomobject]@{ + Dispatcher = [pscustomobject]@{} + } configs = @{ applicationsHashtable = $applications } @@ -187,8 +189,7 @@ Describe "Invoke-WPFInstall runspace body" { Mock Get-WinUtilSelectedPackages { New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc") } - Mock Show-WPFInstallAppBusy { } - Mock Hide-WPFInstallAppBusy { } + Mock Set-WinUtilTweaksProgressIndicator { } Mock Install-WinUtilWinget { } Mock Install-WinUtilChoco { } Mock Install-WinUtilProgramWinget { } @@ -212,8 +213,17 @@ Describe "Invoke-WPFInstall runspace body" { Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter { @($PackageList).Count -eq 1 -and $Preference -eq "Winget" } - Should -Invoke -CommandName Show-WPFInstallAppBusy -Times 1 -Exactly -ParameterFilter { - $text -eq "Installing apps..." + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "Preparing app install (0/2)" -and $Percent -eq 0 + } + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "Installed Git.Git (1/2)" -and $Percent -eq 50 + } + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "Installed Chocolatey packages (2/2)" -and $Percent -eq 100 + } + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "App install finished" -and $Percent -eq 100 } Should -Invoke -CommandName Install-WinUtilWinget -Times 1 -Exactly Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter { @@ -223,21 +233,34 @@ Describe "Invoke-WPFInstall runspace body" { Should -Invoke -CommandName Install-WinUtilProgramChoco -Times 1 -Exactly -ParameterFilter { $Action -eq "Install" -and @($Programs)[0] -eq "vlc" } - Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*' + } + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*' + } Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*' } $script:sync.ProcessRunning | Should -BeFalse } - It "hides the busy overlay, sets taskbar error state, and clears ProcessRunning on failure" { + It "shows failure progress, sets taskbar error state, and clears ProcessRunning on failure" { Mock Install-WinUtilProgramWinget { throw "winget failed" } Invoke-WPFInstall & $script:capturedInstallScriptBlock -PackagesToInstall @($script:package) -ManagerPreference "Winget" - Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "App install failed" -and $Percent -eq 100 + } + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*' + } + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*' + } Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*' } @@ -345,8 +368,7 @@ Describe "Invoke-WPFUnInstall runspace body" { Mock Get-WinUtilSelectedPackages { New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc") } - Mock Show-WPFInstallAppBusy { } - Mock Hide-WPFInstallAppBusy { } + Mock Set-WinUtilTweaksProgressIndicator { } Mock Install-WinUtilProgramWinget { } Mock Install-WinUtilProgramChoco { } Mock Invoke-WPFUIThread { } @@ -369,8 +391,17 @@ Describe "Invoke-WPFUnInstall runspace body" { Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter { @($PackageList).Count -eq 1 -and $Preference -eq "Winget" } - Should -Invoke -CommandName Show-WPFInstallAppBusy -Times 1 -Exactly -ParameterFilter { - $text -eq "Uninstalling apps..." + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "Preparing app uninstall (0/2)" -and $Percent -eq 0 + } + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "Uninstalled Git.Git (1/2)" -and $Percent -eq 50 + } + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "Uninstalled Chocolatey packages (2/2)" -and $Percent -eq 100 + } + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "App uninstall finished" -and $Percent -eq 100 } Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter { $Action -eq "Uninstall" -and @($Programs)[0] -eq "Git.Git" @@ -378,21 +409,34 @@ Describe "Invoke-WPFUnInstall runspace body" { Should -Invoke -CommandName Install-WinUtilProgramChoco -Times 1 -Exactly -ParameterFilter { $Action -eq "Uninstall" -and @($Programs)[0] -eq "vlc" } - Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*' + } + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*' + } Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*' } $script:sync.ProcessRunning | Should -BeFalse } - It "hides the busy overlay, sets taskbar error state, and clears ProcessRunning on failure" { + It "shows failure progress, sets taskbar error state, and clears ProcessRunning on failure" { Mock Install-WinUtilProgramWinget { throw "winget failed" } Invoke-WPFUnInstall -PackagesToUninstall @($script:package) & $script:capturedUninstallScriptBlock -PackagesToUninstall @($script:package) -ManagerPreference "Winget" - Should -Invoke -CommandName Hide-WPFInstallAppBusy -Times 1 -Exactly + Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { + $Visible -eq $true -and $Label -eq "App uninstall failed" -and $Percent -eq 100 + } + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $false*' + } + Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { + $ScriptBlock.ToString() -like '*$sync.ItemsControl.IsEnabled = $true*' + } Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter { $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*' } diff --git a/pester/lazy-tabs.Tests.ps1 b/pester/lazy-tabs.Tests.ps1 index 9e9f67f4..cb9695e0 100644 --- a/pester/lazy-tabs.Tests.ps1 +++ b/pester/lazy-tabs.Tests.ps1 @@ -69,6 +69,23 @@ Describe "Initialize-WinUtilTabContent" { $targetGridName -eq "appxpanel" -and $columncount -eq 2 } } + + It "checks for existing Win11ISO work when the tab is initialized" { + Add-Type -AssemblyName WindowsBase + $dispatcher = [pscustomobject]@{} + $dispatcher | Add-Member -MemberType ScriptMethod -Name BeginInvoke -Value { + param($priority, $action) + $action.Invoke() + } + $script:sync.Form = [pscustomobject]@{ Dispatcher = $dispatcher } + Mock Invoke-WinUtilISOCheckExistingWork { } + + Initialize-WinUtilTabContent -TabName "Win11ISO" + Initialize-WinUtilTabContent -TabName "Win11ISO" + + Should -Invoke -CommandName Invoke-WinUtilISOCheckExistingWork -Times 1 -Exactly + $script:sync.InitializedTabs["Win11ISO"] | Should -BeTrue + } } Describe "Startup lazy tab wiring" { diff --git a/pester/tweaks.Tests.ps1 b/pester/tweaks.Tests.ps1 index 3b14b1ba..2f076df0 100644 --- a/pester/tweaks.Tests.ps1 +++ b/pester/tweaks.Tests.ps1 @@ -31,9 +31,6 @@ BeforeAll { function Invoke-WPFUIThread { param([scriptblock]$ScriptBlock) } - function Set-WinUtilProgressBar { - param($Label, $Percent) - } function Set-WinUtilTweaksProgressIndicator { param($Visible, $Label, $Percent) } @@ -187,7 +184,6 @@ Describe "Invoke-WPFtweaksbutton" { Mock Invoke-WPFRunspace { [pscustomobject]@{ MockHandle = $true } } Mock Invoke-WinUtilTweaks { } Mock Invoke-WPFUIThread { } - Mock Set-WinUtilProgressBar { } Mock Write-WinUtilLog { } Mock Write-Host { } } diff --git a/pester/ui-state.Tests.ps1 b/pester/ui-state.Tests.ps1 index 5f8fb62b..3eb93d56 100644 --- a/pester/ui-state.Tests.ps1 +++ b/pester/ui-state.Tests.ps1 @@ -65,10 +65,6 @@ namespace System.Windows.Controls . (Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1") . (Join-Path $script:repoRoot "functions\public\Invoke-WPFToggleAllCategories.ps1") - function Set-WinUtilProgressBar { - param($Label, $Percent) - } - function Set-WinUtilTweaksProgressIndicator { param($Visible, $Label, $Percent) } @@ -303,7 +299,6 @@ Describe "Invoke-WPFToggleAllCategories" { Describe "Invoke-WPFButton progress cleanup" { BeforeEach { New-WinUtilUiStateTestContext - Mock Set-WinUtilProgressBar { } Mock Set-WinUtilTweaksProgressIndicator { } } @@ -317,9 +312,6 @@ Describe "Invoke-WPFButton progress cleanup" { Invoke-WPFButton -Button "WPFNoOp" - Should -Invoke Set-WinUtilProgressBar -Times 1 -Exactly -ParameterFilter { - $Label -eq "" -and $Percent -eq 0 - } Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter { $Visible -eq $false } @@ -330,7 +322,15 @@ Describe "Invoke-WPFButton progress cleanup" { Invoke-WPFButton -Button "WPFNoOp" - Should -Not -Invoke Set-WinUtilProgressBar + Should -Not -Invoke Set-WinUtilTweaksProgressIndicator + } + + It "leaves progress visible while a Win11 ISO process is running" { + $script:sync.ProcessRunning = $false + $script:sync.Win11ISOProcessRunning = $true + + Invoke-WPFButton -Button "WPFNoOp" + Should -Not -Invoke Set-WinUtilTweaksProgressIndicator } } diff --git a/pester/win11creator.Tests.ps1 b/pester/win11creator.Tests.ps1 index 6769e3ef..326b66ed 100644 --- a/pester/win11creator.Tests.ps1 +++ b/pester/win11creator.Tests.ps1 @@ -6,6 +6,7 @@ Describe "Win11 Creator setup media" { BeforeAll { $script:repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $script:isoWorkflowPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISO.ps1" + $script:isoUsbWorkflowPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISOUSB.ps1" $script:isoScriptPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISOScript.ps1" $script:autoUnattendPath = Join-Path $script:repoRoot "tools\autounattend.xml" @@ -36,6 +37,9 @@ Describe "Win11 Creator setup media" { } $script:modifyFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOModify" + $script:cleanAndResetFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOCleanAndReset" + $script:exportFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOExport" + $script:writeUsbFunction = Get-WinUtilFunctionText -Path $script:isoUsbWorkflowPath -FunctionName "Invoke-WinUtilISOWriteUSB" $script:editionIdFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Get-WinUtilEditionIdFromName" $script:addDriversFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Add-DriversToImage" $script:answerFileChildElementFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Get-WinUtilISOScriptChildElement" @@ -81,6 +85,18 @@ Describe "Win11 Creator setup media" { $script:modifyFunction | Should -Not -Match ([regex]::Escape("Reusing existing temp directory")) } + It "tracks every background ISO workflow with the shared busy state" { + foreach ($functionText in @( + $script:modifyFunction, + $script:cleanAndResetFunction, + $script:exportFunction, + $script:writeUsbFunction + )) { + $functionText | Should -Match ([regex]::Escape('$sync["Win11ISOProcessRunning"] = $true')) + $functionText | Should -Match ([regex]::Escape('$sync["Win11ISOProcessRunning"] = $false')) + } + } + It "mounts the copied image file that was verified from the ISO" { foreach ($expectedText in @( '$sourceImageFileName = Split-Path $wimPath -Leaf', diff --git a/pester/xaml.Tests.ps1 b/pester/xaml.Tests.ps1 index c6801bf9..cc6d60d9 100644 --- a/pester/xaml.Tests.ps1 +++ b/pester/xaml.Tests.ps1 @@ -432,20 +432,15 @@ Describe "XAML and sync wiring" { "InitializedTabs", "RenderedAssetCache", "ToggleStatusCache", - "InstallAppAreaBorder", - "InstallAppAreaScrollViewer", - "InstallAppAreaOverlay", - "InstallAppAreaOverlayText", "InstallAppRenderQueue", "InstallAppEntriesRendered", - "ProgressBar", - "progressBarTextBlock", "FontScaleFactor", "Win11ISOImageInfo", "Win11ISODriveLetter", "Win11ISOWimPath", "Win11ISOImagePath", "Win11ISOModifying", + "Win11ISOProcessRunning", "Win11ISOWorkDir", "Win11ISOContentsDir", "Win11ISOUSBDisks" diff --git a/scripts/start.ps1 b/scripts/start.ps1 index ed1d4164..95a28f38 100644 --- a/scripts/start.ps1 +++ b/scripts/start.ps1 @@ -62,6 +62,7 @@ $sync.configs = @{} $sync.Buttons = [System.Collections.Generic.List[PSObject]]::new() $sync.preferences = @{} $sync.ProcessRunning = $false +$sync.Win11ISOProcessRunning = $false $sync.selectedAppx = [System.Collections.Generic.List[string]]::new() $sync.selectedApps = [System.Collections.Generic.List[string]]::new() $sync.selectedTweaks = [System.Collections.Generic.List[string]]::new() diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index 127e8e41..bb6eeecc 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -1823,7 +1823,7 @@ - +