Compare commits

...
4 Commits
Author SHA1 Message Date
Chris TitusandGitHub 70f1c50bdd chore: Update generated dev docs (#4846) 2026-07-15 23:37:16 -05:00
2e5df82979 Update sponsors in README (#4820)
Co-authored-by: ChrisTitusTech <7896101+ChrisTitusTech@users.noreply.github.com>
2026-07-15 23:37:06 -05:00
Chris TitusandGitHub 29595f90e2 Fix progress, update profiles, and documentation links (#4845)
* Improve O&O ShutUp10++ download flow

* Refine Windows Update profile behavior and docs

* Fix documentation link regressions

* Restore updates when applying recommended profile
2026-07-15 23:36:46 -05:00
Chris TitusandGitHub 9ed06e793a 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
2026-07-15 22:35:58 -05:00
36 changed files with 977 additions and 443 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ See https://github.com/ChrisTitusTech/winutil/blob/main/.github/CONTRIBUTING.md
These are the sponsors that help keep this project alive with monthly contributions. These are the sponsors that help keep this project alive with monthly contributions.
<!-- sponsors --><a href="https://github.com/ysaito8015"><img src="https:&#x2F;&#x2F;github.com&#x2F;ysaito8015.png" width="60px" alt="User avatar: Yusuke Saito" /></a><a href="https://github.com/dwelfusius"><img src="https:&#x2F;&#x2F;github.com&#x2F;dwelfusius.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/mews-se"><img src="https:&#x2F;&#x2F;github.com&#x2F;mews-se.png" width="60px" alt="User avatar: Martin Stockzell" /></a><a href="https://github.com/jdiegmueller"><img src="https:&#x2F;&#x2F;github.com&#x2F;jdiegmueller.png" width="60px" alt="User avatar: Jason A. Diegmueller" /></a><a href="https://github.com/robertsandrock"><img src="https:&#x2F;&#x2F;github.com&#x2F;robertsandrock.png" width="60px" alt="User avatar: RMS" /></a><a href="https://github.com/paulsheets"><img src="https:&#x2F;&#x2F;github.com&#x2F;paulsheets.png" width="60px" alt="User avatar: Paul" /></a><a href="https://github.com/djones369"><img src="https:&#x2F;&#x2F;github.com&#x2F;djones369.png" width="60px" alt="User avatar: Dave J (WhamGeek)" /></a><a href="https://github.com/anthonymendez"><img src="https:&#x2F;&#x2F;github.com&#x2F;anthonymendez.png" width="60px" alt="User avatar: Anthony Mendez" /></a><a href="https://github.com/FatBastard0"><img src="https:&#x2F;&#x2F;github.com&#x2F;FatBastard0.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/DursleyGuy"><img src="https:&#x2F;&#x2F;github.com&#x2F;DursleyGuy.png" width="60px" alt="User avatar: DursleyGuy" /></a><a href="https://github.com/DwayneTheRockLobster1"><img src="https:&#x2F;&#x2F;github.com&#x2F;DwayneTheRockLobster1.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/KieraKujisawa"><img src="https:&#x2F;&#x2F;github.com&#x2F;KieraKujisawa.png" width="60px" alt="User avatar: Kiera Meredith" /></a><a href="https://github.com/andrewpayne68"><img src="https:&#x2F;&#x2F;github.com&#x2F;andrewpayne68.png" width="60px" alt="User avatar: Andrew P" /></a><a href="https://github.com/johanwildeboer"><img src="https:&#x2F;&#x2F;github.com&#x2F;johanwildeboer.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/lukas346"><img src="https:&#x2F;&#x2F;github.com&#x2F;lukas346.png" width="60px" alt="User avatar: Wook" /></a><a href="https://github.com/tsv31"><img src="https:&#x2F;&#x2F;github.com&#x2F;tsv31.png" width="60px" alt="User avatar: Sorin" /></a><!-- sponsors --> <!-- sponsors --><a href="https://github.com/ysaito8015"><img src="https:&#x2F;&#x2F;github.com&#x2F;ysaito8015.png" width="60px" alt="User avatar: Yusuke Saito" /></a><a href="https://github.com/dwelfusius"><img src="https:&#x2F;&#x2F;github.com&#x2F;dwelfusius.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/mews-se"><img src="https:&#x2F;&#x2F;github.com&#x2F;mews-se.png" width="60px" alt="User avatar: Martin Stockzell" /></a><a href="https://github.com/jdiegmueller"><img src="https:&#x2F;&#x2F;github.com&#x2F;jdiegmueller.png" width="60px" alt="User avatar: Jason A. Diegmueller" /></a><a href="https://github.com/robertsandrock"><img src="https:&#x2F;&#x2F;github.com&#x2F;robertsandrock.png" width="60px" alt="User avatar: RMS" /></a><a href="https://github.com/paulsheets"><img src="https:&#x2F;&#x2F;github.com&#x2F;paulsheets.png" width="60px" alt="User avatar: Paul" /></a><a href="https://github.com/djones369"><img src="https:&#x2F;&#x2F;github.com&#x2F;djones369.png" width="60px" alt="User avatar: Dave J (WhamGeek)" /></a><a href="https://github.com/anthonymendez"><img src="https:&#x2F;&#x2F;github.com&#x2F;anthonymendez.png" width="60px" alt="User avatar: Anthony Mendez" /></a><a href="https://github.com/FatBastard0"><img src="https:&#x2F;&#x2F;github.com&#x2F;FatBastard0.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/DursleyGuy"><img src="https:&#x2F;&#x2F;github.com&#x2F;DursleyGuy.png" width="60px" alt="User avatar: DursleyGuy" /></a><a href="https://github.com/DwayneTheRockLobster1"><img src="https:&#x2F;&#x2F;github.com&#x2F;DwayneTheRockLobster1.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/KieraKujisawa"><img src="https:&#x2F;&#x2F;github.com&#x2F;KieraKujisawa.png" width="60px" alt="User avatar: Kiera Meredith" /></a><a href="https://github.com/andrewpayne68"><img src="https:&#x2F;&#x2F;github.com&#x2F;andrewpayne68.png" width="60px" alt="User avatar: Andrew P" /></a><a href="https://github.com/johanwildeboer"><img src="https:&#x2F;&#x2F;github.com&#x2F;johanwildeboer.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/lukas346"><img src="https:&#x2F;&#x2F;github.com&#x2F;lukas346.png" width="60px" alt="User avatar: Wook" /></a><a href="https://github.com/tsv31"><img src="https:&#x2F;&#x2F;github.com&#x2F;tsv31.png" width="60px" alt="User avatar: Sorin" /></a><a href="https://github.com/seanh1995"><img src="https:&#x2F;&#x2F;github.com&#x2F;seanh1995.png" width="60px" alt="User avatar: Sean (ANGRYxScotsman)" /></a><!-- sponsors -->
--- ---
-4
View File
@@ -46,7 +46,6 @@
"AppInstallUnselectedColor": "#F7F7F7", "AppInstallUnselectedColor": "#F7F7F7",
"AppInstallHighlightedColor": "#CFCFCF", "AppInstallHighlightedColor": "#CFCFCF",
"AppInstallSelectedColor": "#C2C2C2", "AppInstallSelectedColor": "#C2C2C2",
"AppInstallOverlayBackgroundColor": "#6A6D72",
"ComboBoxForegroundColor": "#232629", "ComboBoxForegroundColor": "#232629",
"ComboBoxBackgroundColor": "#F7F7F7", "ComboBoxBackgroundColor": "#F7F7F7",
"LabelboxForegroundColor": "#232629", "LabelboxForegroundColor": "#232629",
@@ -60,7 +59,6 @@
"ScrollBarDraggingColor": "#6A6D72", "ScrollBarDraggingColor": "#6A6D72",
"ProgressBarForegroundColor": "#2E77FF", "ProgressBarForegroundColor": "#2E77FF",
"ProgressBarBackgroundColor": "Transparent", "ProgressBarBackgroundColor": "Transparent",
"ProgressBarTextColor": "#232629",
"ButtonInstallBackgroundColor": "#F7F7F7", "ButtonInstallBackgroundColor": "#F7F7F7",
"ButtonTweaksBackgroundColor": "#F7F7F7", "ButtonTweaksBackgroundColor": "#F7F7F7",
"ButtonConfigBackgroundColor": "#F7F7F7", "ButtonConfigBackgroundColor": "#F7F7F7",
@@ -88,7 +86,6 @@
"AppInstallUnselectedColor": "#232629", "AppInstallUnselectedColor": "#232629",
"AppInstallHighlightedColor": "#3C3C3C", "AppInstallHighlightedColor": "#3C3C3C",
"AppInstallSelectedColor": "#4C4C4C", "AppInstallSelectedColor": "#4C4C4C",
"AppInstallOverlayBackgroundColor": "#2E3135",
"ComboBoxForegroundColor": "#F7F7F7", "ComboBoxForegroundColor": "#F7F7F7",
"ComboBoxBackgroundColor": "#1E3747", "ComboBoxBackgroundColor": "#1E3747",
"LabelboxForegroundColor": "#5BDCFF", "LabelboxForegroundColor": "#5BDCFF",
@@ -102,7 +99,6 @@
"ScrollBarDraggingColor": "#5E81AC", "ScrollBarDraggingColor": "#5E81AC",
"ProgressBarForegroundColor": "#6EFF72", "ProgressBarForegroundColor": "#6EFF72",
"ProgressBarBackgroundColor": "Transparent", "ProgressBarBackgroundColor": "Transparent",
"ProgressBarTextColor": "#232629",
"ButtonInstallBackgroundColor": "#222222", "ButtonInstallBackgroundColor": "#222222",
"ButtonTweaksBackgroundColor": "#333333", "ButtonTweaksBackgroundColor": "#333333",
"ButtonConfigBackgroundColor": "#444444", "ButtonConfigBackgroundColor": "#444444",
@@ -5,15 +5,53 @@ description: ""
```powershell {filename="functions/public/Invoke-WPFOOSU.ps1",linenos=inline,linenostart=1} ```powershell {filename="functions/public/Invoke-WPFOOSU.ps1",linenos=inline,linenostart=1}
function Invoke-WPFOOSU { function Invoke-WPFOOSU {
try { if ($sync.ProcessRunning) {
$ProgressPreference = 'SilentlyContinue' Show-WinUtilMessage -Message "Another process is currently running." -Title "WinUtil" -Button "OK" -Icon "Warning"
return
}
Invoke-WebRequest -Uri https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe -OutFile "$winutildir\ooshutup10.exe" $downloadPath = Join-Path $sync.winutildir "ooshutup10.exe"
Start-Process -FilePath "$winutildir\ooshutup10.exe" $sync.ProcessRunning = $true
$ProgressPreference = 'Continue' Invoke-WPFRunspace -ParameterList @(,("downloadPath", $downloadPath)) -ScriptBlock {
} catch { param($downloadPath)
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
try {
Write-WinUtilLog -Component "OOSU" -Message "Downloading O&O ShutUp10++."
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ (0%)" -Percent 0
}
Save-WinUtilFile -Uri "https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe" -DestinationPath $downloadPath -ProgressCallback {
param($percent)
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ ($percent%)" -Percent $percent
}
}
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Launching O&O ShutUp10++" -Percent 100
}
Start-Process -FilePath $downloadPath
Write-WinUtilLog -Component "OOSU" -Message "O&O ShutUp10++ launched."
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ launched" -Percent 100
}
}
catch {
Write-WinUtilLog -Level "ERROR" -Component "OOSU" -Message "O&O ShutUp10++ download failed: $($_.Exception.Message)"
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ download failed" -Percent 100
}
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
}
finally {
$sync.ProcessRunning = $false
}
} }
} }
``` ```
@@ -138,7 +138,7 @@ For a better Windows experience with minimal risk:
1. Check multiple application boxes 1. Check multiple application boxes
2. All checked apps will install in sequence 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 ### Applying Tweaks
+14 -12
View File
@@ -9,28 +9,30 @@ WinUtil provides three update modes so you can choose how aggressively Windows U
Changing modes adjusts system-wide Windows Update behavior. After switching modes, give Windows a moment to apply the policy and plan for a restart if the new state does not appear immediately. Changing modes adjusts system-wide Windows Update behavior. After switching modes, give Windows a moment to apply the policy and plan for a restart if the new state does not appear immediately.
{{< image src="images/updates-tab-new" alt="Updates tab in WinUtil" >}} - **Recommended**: Prioritizes stability while still receiving security updates
- **Windows Default**: Restores standard Windows Update behavior
- **Disable Updates**: Blocks Windows Update and should only be used with extreme caution
- **Default (Out of the Box) Settings**: Restores standard Windows Update behavior ### Windows Default
- **Security (Recommended) Settings**: Prioritizes stability while still receiving security updates
- **Disable ALL Updates**: Turns off Windows Update entirely and should only be used with extreme caution
### Default (Out of Box) Settings - **What it does**: Removes Windows Update policies managed by WinUtil, restores update service startup settings, and re-enables update scheduled tasks.
- **What it does**: Restores the default Windows Update configuration.
- **Best for**: Systems where you want Windows to manage updates normally. - **Best for**: Systems where you want Windows to manage updates normally.
- **Notes**: This removes custom update settings previously applied by WinUtil. If update errors continue, use the reset option in the **Config** tab to restore Microsoft Update services to their default state. - **Notes**: Only values managed by WinUtil are removed; other Windows Update policies are left in place. If update errors continue, use the reset option in the **Config** tab to repair Microsoft Update components.
### Security (Recommended) Settings ### Recommended
- **What it does**: Applies a more conservative update strategy designed for most users. - **What it does**: Applies a more conservative update strategy designed for most users.
- **Feature updates**: Delayed by **365 days** to reduce the chance of disruption from major Windows changes. - **Feature updates**: Delayed by **365 days** to reduce the chance of disruption from major Windows changes.
- **Security updates**: Delayed by **4 days** to allow time for early issues to surface while still keeping the system protected. - **Quality updates**: Delayed by **4 days** to allow time for early issues to surface while still keeping the system protected.
- **Drivers**: Excluded from Windows quality updates.
- **Restarts**: Scheduled updates do not automatically restart Windows while a user is signed in. A restart explicitly scheduled by a user still takes precedence.
- **Availability**: Update deferral policies apply to Windows Pro, Enterprise, and Education editions.
- **Why use it**: This mode offers the best balance between security and stability, which is why it is the recommended option for most PCs. - **Why use it**: This mode offers the best balance between security and stability, which is why it is the recommended option for most PCs.
### Disable ALL Updates (NOT RECOMMENDED!) ### Disable Updates (NOT RECOMMENDED!)
- **What it does**: Disables all Windows updates. - **What it does**: Disables automatic update policy, stops and disables update services, disables update scheduled tasks, and clears downloaded update files.
- **Best for**: Highly controlled or special-purpose systems where updates must remain off temporarily. - **Best for**: Highly controlled or special-purpose systems where updates must remain off temporarily.
- **Warning**: This leaves the system without security patches and significantly increases security risk. - **Warning**: This leaves the system without security patches and significantly increases security risk.
- **Notes**: Windows servicing can restore update components in some circumstances. Use **Restore Defaults** when you are ready to receive updates again.
- **Recommendation**: Avoid this mode unless you fully understand the tradeoffs and have a specific reason to use it. - **Recommendation**: Avoid this mode unless you fully understand the tradeoffs and have a specific reason to use it.
@@ -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
}
}
@@ -5,8 +5,6 @@
This is used as the parent object for all category and app entries on the install tab 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 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 .PARAMETER TargetElement
The element to which the AppArea should be added The element to which the AppArea should be added
@@ -19,22 +17,14 @@
$Border = New-Object Windows.Controls.Border $Border = New-Object Windows.Controls.Border
$Border.VerticalAlignment = "Stretch" $Border.VerticalAlignment = "Stretch"
$Border.SetResourceReference([Windows.Controls.Control]::StyleProperty, "BorderStyle") $Border.SetResourceReference([Windows.Controls.Control]::StyleProperty, "BorderStyle")
$sync.InstallAppAreaBorder = $Border
# Add a ScrollViewer, because the ItemsControl does not support scrolling by itself # Add a ScrollViewer, because the ItemsControl does not support scrolling by itself
$scrollViewer = New-Object Windows.Controls.ScrollViewer $scrollViewer = New-Object Windows.Controls.ScrollViewer
$scrollViewer.VerticalScrollBarVisibility = 'Auto' $scrollViewer.VerticalScrollBarVisibility = 'Auto'
$scrollViewer.HorizontalAlignment = 'Stretch' $scrollViewer.HorizontalAlignment = 'Stretch'
$scrollViewer.VerticalAlignment = 'Stretch' $scrollViewer.VerticalAlignment = 'Stretch'
$scrollViewer.CanContentScroll = $true $scrollViewer.CanContentScroll = $true
$sync.InstallAppAreaScrollViewer = $scrollViewer
$Border.Child = $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 ## Create the ItemsControl, which will be the parent of all the app entries
$itemsControl = New-Object Windows.Controls.ItemsControl $itemsControl = New-Object Windows.Controls.ItemsControl
$itemsControl.HorizontalAlignment = 'Stretch' $itemsControl.HorizontalAlignment = 'Stretch'
@@ -52,61 +42,5 @@
# Add the Border containing the App Area to the target Grid # Add the Border containing the App Area to the target Grid
$targetGrid.Children.Add($Border) | Out-Null $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 return $itemsControl
} }
@@ -28,7 +28,7 @@ function Initialize-WinUtilTabContent {
"AppX" { "AppX" {
Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2 Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2
} }
"Win11 Creator" { "Win11ISO" {
if ($sync.Form -and $sync.Form.Dispatcher) { if ($sync.Form -and $sync.Form.Dispatcher) {
$sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null $sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null
} }
+42 -28
View File
@@ -47,7 +47,7 @@ function Invoke-WinUtilISOMountAndVerify {
} }
Write-Win11ISOLog "Mounting ISO: $isoPath" Write-Win11ISOLog "Mounting ISO: $isoPath"
Set-WinUtilProgressBar -Label "Mounting ISO..." -Percent 10 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Mounting ISO..." -Percent 10
try { try {
Mount-DiskImage -ImagePath $isoPath Mount-DiskImage -ImagePath $isoPath
@@ -59,7 +59,7 @@ function Invoke-WinUtilISOMountAndVerify {
$driveLetter = (Get-DiskImage -ImagePath $isoPath | Get-Volume).DriveLetter + ":" $driveLetter = (Get-DiskImage -ImagePath $isoPath | Get-Volume).DriveLetter + ":"
Write-Win11ISOLog "Mounted at drive $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" $wimPath = Join-Path $driveLetter "sources\install.wim"
$esdPath = Join-Path $driveLetter "sources\install.esd" $esdPath = Join-Path $driveLetter "sources\install.esd"
@@ -70,13 +70,13 @@ function Invoke-WinUtilISOMountAndVerify {
[System.Windows.MessageBox]::Show( [System.Windows.MessageBox]::Show(
"This does not appear to be a valid Windows ISO.`n`ninstall.wim / install.esd was not found.", "This does not appear to be a valid Windows ISO.`n`ninstall.wim / install.esd was not found.",
"Invalid ISO", "OK", "Error") "Invalid ISO", "OK", "Error")
Set-WinUtilProgressBar -Label "" -Percent 0 Set-WinUtilTweaksProgressIndicator -Visible $false
return return
} }
$activeWim = if (Test-Path $wimPath) { $wimPath } else { $esdPath } $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 $imageInfo = Get-WindowsImage -ImagePath $activeWim | Select-Object ImageIndex, ImageName
if (-not ($imageInfo | Where-Object { $_.ImageName -match "Windows 11" })) { if (-not ($imageInfo | Where-Object { $_.ImageName -match "Windows 11" })) {
@@ -85,7 +85,7 @@ function Invoke-WinUtilISOMountAndVerify {
[System.Windows.MessageBox]::Show( [System.Windows.MessageBox]::Show(
"No Windows 11 edition was found in this ISO.`n`nOnly official Windows 11 ISOs are supported.", "No Windows 11 edition was found in this ISO.`n`nOnly official Windows 11 ISOs are supported.",
"Not a Windows 11 ISO", "OK", "Error") "Not a Windows 11 ISO", "OK", "Error")
Set-WinUtilProgressBar -Label "" -Percent 0 Set-WinUtilTweaksProgressIndicator -Visible $false
return return
} }
@@ -114,7 +114,7 @@ function Invoke-WinUtilISOMountAndVerify {
$sync["Win11ISOImagePath"] = $isoPath $sync["Win11ISOImagePath"] = $isoPath
$sync["WPFWin11ISOModifySection"].Visibility = "Visible" $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)" Write-Win11ISOLog "ISO verified OK. Editions found: $($imageInfo.Count)"
} catch { } catch {
Write-Win11ISOLog "ERROR during mount/verify: $_" Write-Win11ISOLog "ERROR during mount/verify: $_"
@@ -123,7 +123,7 @@ function Invoke-WinUtilISOMountAndVerify {
"Error", "OK", "Error") "Error", "OK", "Error")
} finally { } finally {
Start-Sleep -Milliseconds 800 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["WPFWin11ISOModifyButton"].IsEnabled = $false
$sync["Win11ISOModifying"] = $true $sync["Win11ISOModifying"] = $true
$sync["Win11ISOProcessRunning"] = $true
$workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')" $workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
if (Test-Path $workDir) { if (Test-Path $workDir) {
@@ -203,9 +204,10 @@ function Invoke-WinUtilISOModify {
function SetProgress($label, $pct) { function SetProgress($label, $pct) {
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = $label $sync["WPFTweaksProgressBar"].Visibility = "Visible"
$sync.progressBarTextBlock.ToolTip = $label $sync["WPFTweaksProgressLabel"].Text = $label
$sync.ProgressBar.Value = [Math]::Max($pct, 5) $sync["WPFTweaksProgressLabel"].ToolTip = $label
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
}) })
} }
@@ -452,10 +454,12 @@ function Invoke-WinUtilISOModify {
} finally { } finally {
Start-Sleep -Milliseconds 800 Start-Sleep -Milliseconds 800
$sync["Win11ISOModifying"] = $false $sync["Win11ISOModifying"] = $false
$sync["Win11ISOProcessRunning"] = $false
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = "" $sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
$sync.progressBarTextBlock.ToolTip = "" $sync["WPFTweaksProgressLabel"].Text = ""
$sync.ProgressBar.Value = 0 $sync["WPFTweaksProgressLabel"].ToolTip = ""
$sync["WPFTweaksProgressValue"].Value = 0
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true $sync["WPFWin11ISOModifyButton"].IsEnabled = $true
if ($sync["WPFWin11ISOOutputSection"].Visibility -ne "Visible") { if ($sync["WPFWin11ISOOutputSection"].Visibility -ne "Visible") {
$sync["WPFWin11ISOSelectSection"].Visibility = "Visible" $sync["WPFWin11ISOSelectSection"].Visibility = "Visible"
@@ -514,6 +518,7 @@ function Invoke-WinUtilISOCleanAndReset {
} }
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $false $sync["WPFWin11ISOCleanResetButton"].IsEnabled = $false
$sync["Win11ISOProcessRunning"] = $true
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA" $runspace.ApartmentState = "STA"
@@ -538,9 +543,10 @@ function Invoke-WinUtilISOCleanAndReset {
function SetProgress($label, $pct) { function SetProgress($label, $pct) {
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = $label $sync["WPFTweaksProgressBar"].Visibility = "Visible"
$sync.progressBarTextBlock.ToolTip = $label $sync["WPFTweaksProgressLabel"].Text = $label
$sync.ProgressBar.Value = [Math]::Max($pct, 5) $sync["WPFTweaksProgressLabel"].ToolTip = $label
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
}) })
} }
@@ -628,20 +634,24 @@ function Invoke-WinUtilISOCleanAndReset {
$sync["WPFWin11ISOModifyButton"].IsEnabled = $true $sync["WPFWin11ISOModifyButton"].IsEnabled = $true
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true $sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
$sync.progressBarTextBlock.Text = "" $sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
$sync.progressBarTextBlock.ToolTip = "" $sync["WPFTweaksProgressLabel"].Text = ""
$sync.ProgressBar.Value = 0 $sync["WPFTweaksProgressLabel"].ToolTip = ""
$sync["WPFTweaksProgressValue"].Value = 0
$sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin." $sync["WPFWin11ISOStatusLog"].Text = "Ready. Please select a Windows 11 ISO to begin."
}) })
} catch { } catch {
Log "ERROR during Clean & Reset: $_" Log "ERROR during Clean & Reset: $_"
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = "" $sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
$sync.progressBarTextBlock.ToolTip = "" $sync["WPFTweaksProgressLabel"].Text = ""
$sync.ProgressBar.Value = 0 $sync["WPFTweaksProgressLabel"].ToolTip = ""
$sync["WPFTweaksProgressValue"].Value = 0
$sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true $sync["WPFWin11ISOCleanResetButton"].IsEnabled = $true
}) })
} finally {
$sync["Win11ISOProcessRunning"] = $false
} }
}) })
@@ -706,6 +716,7 @@ function Invoke-WinUtilISOExport {
} }
$sync["WPFWin11ISOChooseISOButton"].IsEnabled = $false $sync["WPFWin11ISOChooseISOButton"].IsEnabled = $false
$sync["Win11ISOProcessRunning"] = $true
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$runspace.ApartmentState = "STA" $runspace.ApartmentState = "STA"
@@ -726,9 +737,10 @@ function Invoke-WinUtilISOExport {
function SetProgress($label, $pct) { function SetProgress($label, $pct) {
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = $label $sync["WPFTweaksProgressBar"].Visibility = "Visible"
$sync.progressBarTextBlock.ToolTip = $label $sync["WPFTweaksProgressLabel"].Text = $label
$sync.ProgressBar.Value = [Math]::Max($pct, 5) $sync["WPFTweaksProgressLabel"].ToolTip = $label
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
}) })
} }
@@ -788,10 +800,12 @@ function Invoke-WinUtilISOExport {
}) })
} finally { } finally {
Start-Sleep -Milliseconds 800 Start-Sleep -Milliseconds 800
$sync["Win11ISOProcessRunning"] = $false
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = "" $sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
$sync.progressBarTextBlock.ToolTip = "" $sync["WPFTweaksProgressLabel"].Text = ""
$sync.ProgressBar.Value = 0 $sync["WPFTweaksProgressLabel"].ToolTip = ""
$sync["WPFTweaksProgressValue"].Value = 0
$sync["WPFWin11ISOChooseISOButton"].IsEnabled = $true $sync["WPFWin11ISOChooseISOButton"].IsEnabled = $true
}) })
} }
+10 -6
View File
@@ -61,6 +61,7 @@ function Invoke-WinUtilISOWriteUSB {
} }
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $false
$sync["Win11ISOProcessRunning"] = $true
Write-Win11ISOLog "Starting USB write to Disk $diskNum..." Write-Win11ISOLog "Starting USB write to Disk $diskNum..."
$runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace() $runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
@@ -86,9 +87,10 @@ function Invoke-WinUtilISOWriteUSB {
function SetProgress($label, $pct) { function SetProgress($label, $pct) {
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = $label $sync["WPFTweaksProgressBar"].Visibility = "Visible"
$sync.progressBarTextBlock.ToolTip = $label $sync["WPFTweaksProgressLabel"].Text = $label
$sync.ProgressBar.Value = [Math]::Max($pct, 5) $sync["WPFTweaksProgressLabel"].ToolTip = $label
$sync["WPFTweaksProgressValue"].Value = [Math]::Max($pct, 5)
}) })
} }
@@ -255,10 +257,12 @@ function Invoke-WinUtilISOWriteUSB {
}) })
} finally { } finally {
Start-Sleep -Milliseconds 800 Start-Sleep -Milliseconds 800
$sync["Win11ISOProcessRunning"] = $false
$sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{
$sync.progressBarTextBlock.Text = "" $sync["WPFTweaksProgressBar"].Visibility = "Collapsed"
$sync.progressBarTextBlock.ToolTip = "" $sync["WPFTweaksProgressLabel"].Text = ""
$sync.ProgressBar.Value = 0 $sync["WPFTweaksProgressLabel"].ToolTip = ""
$sync["WPFTweaksProgressValue"].Value = 0
$sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true $sync["WPFWin11ISOWriteUSBButton"].IsEnabled = $true
}) })
} }
+59
View File
@@ -0,0 +1,59 @@
function Save-WinUtilFile {
<#
.SYNOPSIS
Downloads a file and reports transfer progress.
#>
param(
[Parameter(Mandatory)]
[uri]$Uri,
[Parameter(Mandatory)]
[string]$DestinationPath,
[Parameter(Mandatory)]
[scriptblock]$ProgressCallback
)
$response = $null
$responseStream = $null
$outputStream = $null
try {
$request = [System.Net.WebRequest]::Create($Uri)
$response = $request.GetResponse()
$totalBytes = $response.ContentLength
$responseStream = $response.GetResponseStream()
$outputStream = [System.IO.File]::Create($DestinationPath)
$buffer = New-Object byte[] 81920
$downloadedBytes = 0L
$lastPercent = -1
while (($bytesRead = $responseStream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$outputStream.Write($buffer, 0, $bytesRead)
$downloadedBytes += $bytesRead
if ($totalBytes -gt 0) {
$percent = [Math]::Min(100, [int](($downloadedBytes / $totalBytes) * 100))
if ($percent -ne $lastPercent) {
& $ProgressCallback $percent
$lastPercent = $percent
}
}
}
if ($lastPercent -ne 100) {
& $ProgressCallback 100
}
}
finally {
if ($null -ne $outputStream) {
$outputStream.Dispose()
}
if ($null -ne $responseStream) {
$responseStream.Dispose()
}
if ($null -ne $response) {
$response.Dispose()
}
}
}
@@ -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}
}
@@ -2,8 +2,8 @@ function Set-WinUtilTweaksProgressIndicator {
<# <#
.SYNOPSIS .SYNOPSIS
Shows, updates, or hides the window-level progress indicator used by long-running 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, workflows such as app management, Tweaks, AppX management, and Win11 Creator.
so unlike the Install tab's progress bar it stays visible no matter which tab is active. It lives outside the TabControl, so it stays visible no matter which tab is active.
.PARAMETER Visible .PARAMETER Visible
Whether the indicator should be shown or hidden. Whether the indicator should be shown or hidden.
.PARAMETER Label .PARAMETER Label
@@ -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
}
}
+1 -2
View File
@@ -14,8 +14,7 @@ function Invoke-WPFButton {
# Use this to get the name of the button # Use this to get the name of the button
#[System.Windows.MessageBox]::Show("$Button","Chris Titus Tech's Windows Utility","OK","Info") #[System.Windows.MessageBox]::Show("$Button","Chris Titus Tech's Windows Utility","OK","Info")
if (-not $sync.ProcessRunning) { if (-not $sync.ProcessRunning -and -not $sync.Win11ISOProcessRunning) {
Set-WinUtilProgressBar -label "" -percent 0
Set-WinUtilTweaksProgressIndicator -Visible $false Set-WinUtilTweaksProgressIndicator -Visible $false
} }
+54 -6
View File
@@ -31,33 +31,81 @@ function Invoke-WPFInstall {
$packagesWinget = $packagesSorted['Winget'] $packagesWinget = $packagesSorted['Winget']
$packagesChoco = $packagesSorted['Choco'] $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)" Write-WinUtilLog -Component "Install" -Message "Install package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)"
try { try {
$sync.ProcessRunning = $true $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") { if($packagesWinget.Count -gt 0 -and $packagesWinget -ne "0") {
Show-WPFInstallAppBusy -text "Installing apps..."
Install-WinUtilWinget 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) { 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-WinUtilChoco
Install-WinUtilProgramChoco -Action Install -Programs $packagesChoco 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 "==========================================="
Write-Host "-- Installs have finished ---" Write-Host "-- Installs have finished ---"
Write-Host "===========================================" Write-Host "==========================================="
Write-WinUtilLog -Component "Install" -Message "Install workflow completed." 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 { } catch {
Hide-WPFInstallAppBusy
Write-Host "===========================================" Write-Host "==========================================="
Write-Host "Error: $_" Write-Host "Error: $_"
Write-Host "===========================================" Write-Host "==========================================="
Write-WinUtilLog -Level "ERROR" -Component "Install" -Message "Install workflow failed: $($_.Exception.Message)" 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 { } finally {
if ($hasUI) {
Invoke-WPFUIThread -ScriptBlock {
if ($null -ne $sync.ItemsControl) {
$sync.ItemsControl.IsEnabled = $true
}
}
}
$sync.ProcessRunning = $False $sync.ProcessRunning = $False
} }
} }
+45 -7
View File
@@ -1,12 +1,50 @@
function Invoke-WPFOOSU { function Invoke-WPFOOSU {
try { if ($sync.ProcessRunning) {
$ProgressPreference = 'SilentlyContinue' Show-WinUtilMessage -Message "Another process is currently running." -Title "WinUtil" -Button "OK" -Icon "Warning"
return
}
Invoke-WebRequest -Uri https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe -OutFile "$winutildir\ooshutup10.exe" $downloadPath = Join-Path $sync.winutildir "ooshutup10.exe"
Start-Process -FilePath "$winutildir\ooshutup10.exe" $sync.ProcessRunning = $true
$ProgressPreference = 'Continue' Invoke-WPFRunspace -ParameterList @(,("downloadPath", $downloadPath)) -ScriptBlock {
} catch { param($downloadPath)
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
$hasUI = $null -ne $sync.Form -and $null -ne $sync.Form.Dispatcher
try {
Write-WinUtilLog -Component "OOSU" -Message "Downloading O&O ShutUp10++."
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ (0%)" -Percent 0
}
Save-WinUtilFile -Uri "https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe" -DestinationPath $downloadPath -ProgressCallback {
param($percent)
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Downloading O&O ShutUp10++ ($percent%)" -Percent $percent
}
}
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Launching O&O ShutUp10++" -Percent 100
}
Start-Process -FilePath $downloadPath
Write-WinUtilLog -Component "OOSU" -Message "O&O ShutUp10++ launched."
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ launched" -Percent 100
}
}
catch {
Write-WinUtilLog -Level "ERROR" -Component "OOSU" -Message "O&O ShutUp10++ download failed: $($_.Exception.Message)"
if ($hasUI) {
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "O&O ShutUp10++ download failed" -Percent 100
}
Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection."
}
finally {
$sync.ProcessRunning = $false
}
} }
} }
+6 -1
View File
@@ -404,11 +404,16 @@ function Invoke-WPFUIElements {
$textBlock.ToolTip = $entryInfo.Link $textBlock.ToolTip = $entryInfo.Link
$textBlock.Style = $HoverTextBlockStyle $textBlock.Style = $HoverTextBlockStyle
$textBlock.UseLayoutRounding = $true $textBlock.UseLayoutRounding = $true
$textBlock.VerticalAlignment = "Center" $textBlock.VerticalAlignment = "Center"
$textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize")
$textBlock.Tag = $checkBox $textBlock.Tag = $checkBox
$textBlock.Add_MouseUp({
[System.Object]$Sender = $args[0]
Start-Process $Sender.ToolTip -ErrorAction Stop
})
$updateLinkMargin = { $updateLinkMargin = {
[System.Object]$Sender = $args[0] [System.Object]$Sender = $args[0]
$linkedCheckBox = $Sender.Tag $linkedCheckBox = $Sender.Tag
+53 -6
View File
@@ -42,11 +42,21 @@ function Invoke-WPFUnInstall {
$packagesWinget = $packagesSorted['Winget'] $packagesWinget = $packagesSorted['Winget']
$packagesChoco = $packagesSorted['Choco'] $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)" Write-WinUtilLog -Component "Uninstall" -Message "Uninstall package manager split: winget=$(@($packagesWinget).Count), choco=$(@($packagesChoco).Count)"
try { try {
$sync.ProcessRunning = $true $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") { if ($packagesWinget -contains "Microsoft.Edge") {
New-Item -Path "$Env:SystemRoot\SystemApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\MicrosoftEdge.exe" -Force 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 # Uninstall all selected programs in new window
if($packagesWinget.Count -gt 0) { 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) { 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 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 "==========================================="
Write-Host "-- Uninstalls have finished ---" Write-Host "-- Uninstalls have finished ---"
Write-Host "===========================================" Write-Host "==========================================="
Write-WinUtilLog -Component "Uninstall" -Message "Uninstall workflow completed." 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 { } catch {
Hide-WPFInstallAppBusy
Write-Host "===========================================" Write-Host "==========================================="
Write-Host "Error: $_" Write-Host "Error: $_"
Write-Host "===========================================" Write-Host "==========================================="
Write-WinUtilLog -Level "ERROR" -Component "Uninstall" -Message "Uninstall workflow failed: $($_.Exception.Message)" 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 { } finally {
if ($hasUI) {
Invoke-WPFUIThread -ScriptBlock {
if ($null -ne $sync.ItemsControl) {
$sync.ItemsControl.IsEnabled = $true
}
}
}
$sync.ProcessRunning = $False $sync.ProcessRunning = $False
} }
+43 -22
View File
@@ -5,22 +5,51 @@ function Invoke-WPFUpdatesdefault {
Resets Windows Update settings to default Resets Windows Update settings to default
#> #>
$ErrorActionPreference = 'SilentlyContinue'
Write-WinUtilLog -Component "Updates" -Message "Resetting Windows Update settings to default." Write-WinUtilLog -Component "Updates" -Message "Resetting Windows Update settings to default."
Write-Host "Removing Windows Update policy settings..." -ForegroundColor Green Write-Host "Removing Windows Update settings managed by WinUtil..." -ForegroundColor Green
Write-WinUtilLog -Component "Updates" -Message "Removing Windows Update policy registry paths." Write-WinUtilLog -Component "Updates" -Message "Removing Windows Update registry values managed by WinUtil."
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Recurse -Force $registryValues = @(
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization" -Recurse -Force @{
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Recurse -Force Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Recurse -Force Names = @("NoAutoUpdate", "AUOptions", "NoAutoRebootWithLoggedOnUsers", "AUPowerManagement")
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Recurse -Force },
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Recurse -Force @{
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
Names = @("ExcludeWUDriversInQualityUpdate", "DeferFeatureUpdates", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdates", "DeferQualityUpdatesPeriodInDays")
},
@{
Path = "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings"
Names = @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")
},
@{
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata"
Names = @("PreventDeviceMetadataFromNetwork")
},
@{
Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching"
Names = @("DontPromptForWindowsUpdate", "DontSearchWindowsUpdate", "DriverUpdateWizardWuSearchEnabled")
},
@{
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config"
Names = @("DODownloadMode")
}
)
Write-Host "Showing Windows Updates in settings..." foreach ($registryEntry in $registryValues) {
Write-WinUtilLog -Component "Updates" -Message "Showing Windows Update settings page." foreach ($valueName in $registryEntry.Names) {
Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer -Name SettingsPageVisibility Remove-ItemProperty -Path $registryEntry.Path -Name $valueName -ErrorAction SilentlyContinue
}
}
$explorerPolicyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer"
$settingsPageVisibility = (Get-ItemProperty -Path $explorerPolicyPath -Name "SettingsPageVisibility" -ErrorAction SilentlyContinue).SettingsPageVisibility
if ($settingsPageVisibility -eq "hide:windowsupdate") {
Write-Host "Removing WinUtil's legacy Windows Update page restriction..."
Write-WinUtilLog -Component "Updates" -Message "Removing the legacy Windows Update settings page restriction."
Remove-ItemProperty -Path $explorerPolicyPath -Name "SettingsPageVisibility" -ErrorAction SilentlyContinue
}
Write-Host "Reenabling Windows Update Services..." -ForegroundColor Green Write-Host "Reenabling Windows Update Services..." -ForegroundColor Green
Write-WinUtilLog -Component "Updates" -Message "Restoring Windows Update service startup types." Write-WinUtilLog -Component "Updates" -Message "Restoring Windows Update service startup types."
@@ -35,12 +64,8 @@ function Invoke-WPFUpdatesdefault {
Write-Host "Restored UsoSvc to Automatic." Write-Host "Restored UsoSvc to Automatic."
Write-WinUtilLog -Component "Updates" -Message "Starting UsoSvc service and restoring startup type to Automatic." Write-WinUtilLog -Component "Updates" -Message "Starting UsoSvc service and restoring startup type to Automatic."
Start-Service -Name UsoSvc
Set-Service -Name UsoSvc -StartupType Automatic Set-Service -Name UsoSvc -StartupType Automatic
Start-Service -Name UsoSvc
Write-Host "Restored WaaSMedicSvc to Manual."
Write-WinUtilLog -Component "Updates" -Message "Restoring WaaSMedicSvc service to Manual."
Set-Service -Name WaaSMedicSvc -StartupType Manual
Write-Host "Enabling update related scheduled tasks..." -ForegroundColor Green Write-Host "Enabling update related scheduled tasks..." -ForegroundColor Green
Write-WinUtilLog -Component "Updates" -Message "Enabling update related scheduled tasks." Write-WinUtilLog -Component "Updates" -Message "Enabling update related scheduled tasks."
@@ -54,13 +79,9 @@ function Invoke-WPFUpdatesdefault {
'\Microsoft\WindowsUpdate\*' '\Microsoft\WindowsUpdate\*'
foreach ($Task in $Tasks) { foreach ($Task in $Tasks) {
Get-ScheduledTask -TaskPath $Task | Enable-ScheduledTask -ErrorAction SilentlyContinue Get-ScheduledTask -TaskPath $Task -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue
} }
Write-Host "Windows Local Policies Reset to Default."
Write-WinUtilLog -Component "Updates" -Message "Resetting local security policy to defaults with secedit."
secedit /configure /cfg "$Env:SystemRoot\inf\defltbase.inf" /db defltbase.sdb
Write-Host "===================================================" -ForegroundColor Green Write-Host "===================================================" -ForegroundColor Green
Write-Host "--- Windows Update Settings Reset to Default ---" -ForegroundColor Green Write-Host "--- Windows Update Settings Reset to Default ---" -ForegroundColor Green
Write-Host "===================================================" -ForegroundColor Green Write-Host "===================================================" -ForegroundColor Green
+20 -20
View File
@@ -8,7 +8,17 @@ function Invoke-WPFUpdatesdisable {
Disabling Windows Update is not recommended. This is only for advanced users who know what they are doing. Disabling Windows Update is not recommended. This is only for advanced users who know what they are doing.
#> #>
$ErrorActionPreference = 'SilentlyContinue' $confirmation = Show-WinUtilMessage `
-Message "Disabling Windows Update stops update services, disables scheduled tasks, and clears downloaded update files. Security updates will not be installed until defaults are restored. Continue?" `
-Title "Disable Windows Update?" `
-Button "YesNo" `
-Icon "Warning"
if ($confirmation -ne "Yes") {
Write-WinUtilLog -Component "Updates" -Message "Windows Update disable workflow cancelled."
return
}
Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update settings." Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update settings."
Write-Host "Configuring registry settings..." -ForegroundColor Yellow Write-Host "Configuring registry settings..." -ForegroundColor Yellow
@@ -21,24 +31,14 @@ function Invoke-WPFUpdatesdisable {
New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Force New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -Type DWord -Value 0 Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -Type DWord -Value 0
Write-Host "Hiding Windows Updates from settings..." foreach ($serviceName in @("BITS", "wuauserv", "UsoSvc")) {
Write-WinUtilLog -Component "Updates" -Message "Hiding Windows Update settings page." Write-Host "Stopping and disabling $serviceName service."
Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer -Name SettingsPageVisibility -Value hide:windowsupdate Write-WinUtilLog -Component "Updates" -Message "Stopping and disabling $serviceName service."
Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue
Set-Service -Name $serviceName -StartupType Disabled
}
Write-Host "Disabled BITS Service." Remove-Item -Path "C:\Windows\SoftwareDistribution\*" -Recurse -Force -ErrorAction SilentlyContinue
Write-WinUtilLog -Component "Updates" -Message "Disabling BITS service."
Set-Service -Name BITS -StartupType Disabled
Write-Host "Disabled wuauserv Service."
Write-WinUtilLog -Component "Updates" -Message "Disabling wuauserv service."
Set-Service -Name wuauserv -StartupType Disabled
Write-Host "Disabled UsoSvc Service."
Write-WinUtilLog -Component "Updates" -Message "Stopping and disabling UsoSvc service."
Stop-Service -Name UsoSvc -Force
Set-Service -Name UsoSvc -StartupType Disabled
Remove-Item "C:\Windows\SoftwareDistribution\*" -Recurse -Force
Write-Host "Cleared SoftwareDistribution folder." Write-Host "Cleared SoftwareDistribution folder."
Write-WinUtilLog -Component "Updates" -Message "Cleared SoftwareDistribution folder." Write-WinUtilLog -Component "Updates" -Message "Cleared SoftwareDistribution folder."
@@ -54,11 +54,11 @@ function Invoke-WPFUpdatesdisable {
'\Microsoft\WindowsUpdate\*' '\Microsoft\WindowsUpdate\*'
foreach ($Task in $Tasks) { foreach ($Task in $Tasks) {
Get-ScheduledTask -TaskPath $Task | Disable-ScheduledTask -ErrorAction SilentlyContinue Get-ScheduledTask -TaskPath $Task -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue
} }
Write-Host "=================================" -ForegroundColor Green Write-Host "=================================" -ForegroundColor Green
Write-Host "--- Updates Are Disabled ---" -ForegroundColor Green Write-Host "--- Windows Update Is Disabled ---" -ForegroundColor Green
Write-Host "=================================" -ForegroundColor Green Write-Host "=================================" -ForegroundColor Green
Write-Host "Note: You must restart your system in order for all changes to take effect." -ForegroundColor Yellow Write-Host "Note: You must restart your system in order for all changes to take effect." -ForegroundColor Yellow
+47 -16
View File
@@ -6,10 +6,9 @@ function Invoke-WPFUpdatessecurity {
.DESCRIPTION .DESCRIPTION
1. Disables driver offering through Windows Update 1. Disables driver offering through Windows Update
2. Disables Windows Update automatic restart 2. Defers feature updates for 365 days
3. Sets Windows Update to Semi-Annual Channel (Targeted) 3. Defers quality updates for 4 days
4. Defers feature updates for 365 days 4. Prevents automatic restarts while a user is signed in
5. Defers quality updates for 4 days
#> #>
@@ -17,6 +16,32 @@ function Invoke-WPFUpdatessecurity {
Write-WinUtilLog -Component "Updates" -Message "Applying recommended Windows Update settings." Write-WinUtilLog -Component "Updates" -Message "Applying recommended Windows Update settings."
Write-WinUtilLog -Component "Updates" -Message "Disabling driver offering through Windows Update." Write-WinUtilLog -Component "Updates" -Message "Disabling driver offering through Windows Update."
$windowsUpdatePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
$automaticUpdatePolicyPath = Join-Path $windowsUpdatePolicyPath "AU"
Write-Host "Restoring Windows Update availability..."
Write-WinUtilLog -Component "Updates" -Message "Restoring Windows Update services and scheduled tasks before applying recommended settings."
Remove-ItemProperty -Path $automaticUpdatePolicyPath -Name "NoAutoUpdate" -ErrorAction SilentlyContinue
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -Name "DODownloadMode" -ErrorAction SilentlyContinue
Set-Service -Name BITS -StartupType Manual
Set-Service -Name wuauserv -StartupType Manual
Set-Service -Name UsoSvc -StartupType Automatic
Start-Service -Name UsoSvc
$Tasks =
'\Microsoft\Windows\InstallService\*',
'\Microsoft\Windows\UpdateOrchestrator\*',
'\Microsoft\Windows\UpdateAssistant\*',
'\Microsoft\Windows\WaaSMedic\*',
'\Microsoft\Windows\WindowsUpdate\*',
'\Microsoft\WindowsUpdate\*'
foreach ($Task in $Tasks) {
Get-ScheduledTask -TaskPath $Task -ErrorAction SilentlyContinue | Enable-ScheduledTask -ErrorAction SilentlyContinue
}
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Force New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Name "PreventDeviceMetadataFromNetwork" -Type DWord -Value 1 Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Name "PreventDeviceMetadataFromNetwork" -Type DWord -Value 1
@@ -26,24 +51,30 @@ function Invoke-WPFUpdatessecurity {
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Name "DontSearchWindowsUpdate" -Type DWord -Value 1 Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Name "DontSearchWindowsUpdate" -Type DWord -Value 1
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Name "DriverUpdateWizardWuSearchEnabled" -Type DWord -Value 0 Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Name "DriverUpdateWizardWuSearchEnabled" -Type DWord -Value 0
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Force New-Item -Path $windowsUpdatePolicyPath -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name "ExcludeWUDriversInQualityUpdate" -Type DWord -Value 1 Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "ExcludeWUDriversInQualityUpdate" -Type DWord -Value 1
Write-Host "Setting cumulative updates back by 1 year and security updates by 4 days..." Write-Host "Deferring feature updates by 365 days and quality updates by 4 days..."
Write-WinUtilLog -Component "Updates" -Message "Deferring feature updates by 365 days and quality updates by 4 days." Write-WinUtilLog -Component "Updates" -Message "Deferring feature updates by 365 days and quality updates by 4 days."
New-Item -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Force Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferFeatureUpdates" -Type DWord -Value 1
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferFeatureUpdatesPeriodInDays" -Type DWord -Value 365
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferQualityUpdates" -Type DWord -Value 1
Set-ItemProperty -Path $windowsUpdatePolicyPath -Name "DeferQualityUpdatesPeriodInDays" -Type DWord -Value 4
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "BranchReadinessLevel" -Type DWord -Value 20 $legacySettingsPath = "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings"
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferFeatureUpdatesPeriodInDays" -Type DWord -Value 365 foreach ($legacyValue in @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")) {
Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferQualityUpdatesPeriodInDays" -Type DWord -Value 4 Remove-ItemProperty -Path $legacySettingsPath -Name $legacyValue -ErrorAction SilentlyContinue
}
Write-Host "Disabling Windows Update automatic restart..." Write-Host "Preventing automatic restarts while users are signed in..."
Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update automatic restart while users are logged in." Write-WinUtilLog -Component "Updates" -Message "Configuring scheduled automatic updates without restarting while users are signed in."
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Force New-Item -Path $automaticUpdatePolicyPath -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoRebootWithLoggedOnUsers" -Type DWord -Value 1 # NoAutoRebootWithLoggedOnUsers only applies when automatic updates use option 4.
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUPowerManagement" -Type DWord -Value 0 Set-ItemProperty -Path $automaticUpdatePolicyPath -Name "AUOptions" -Type DWord -Value 4
Set-ItemProperty -Path $automaticUpdatePolicyPath -Name "NoAutoRebootWithLoggedOnUsers" -Type DWord -Value 1
Set-ItemProperty -Path $automaticUpdatePolicyPath -Name "AUPowerManagement" -Type DWord -Value 0
Write-Host "=================================" Write-Host "================================="
Write-Host "-- Updates Set to Recommended ---" Write-Host "-- Updates Set to Recommended ---"
@@ -39,14 +39,12 @@ function Invoke-WPFtweaksbutton {
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" } 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 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Creating restore point" -Percent 0
Write-WinUtilLog -Component "Tweaks" -Message "Creating restore point before applying selected tweaks." Write-WinUtilLog -Component "Tweaks" -Message "Creating restore point before applying selected tweaks."
Invoke-WinUtilTweaks $restorePointTweak Invoke-WinUtilTweaks $restorePointTweak
$completedSteps = 1 $completedSteps = 1
if ($tweaksToRun.Count -eq 0 -and $dnsProvider -eq "Default") { 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 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100
$sync.ProcessRunning = $false $sync.ProcessRunning = $false
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
@@ -77,14 +75,12 @@ function Invoke-WPFtweaksbutton {
} }
for ($i = 0; $i -lt $tweaks.Count; $i++) { 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) Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Applying $($tweaks[$i]) ($($completedSteps + 1)/$totalSteps)" -Percent ($completedSteps / $totalSteps * 100)
Invoke-WinUtilTweaks $tweaks[$i] Invoke-WinUtilTweaks $tweaks[$i]
$completedSteps++ $completedSteps++
$progress = $completedSteps / $totalSteps $progress = $completedSteps / $totalSteps
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value $progress } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value $progress }
} }
Set-WinUtilProgressBar -Label "Tweaks finished" -Percent 100
Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Tweaks finished" -Percent 100
$sync.ProcessRunning = $false $sync.ProcessRunning = $false
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
-2
View File
@@ -33,13 +33,11 @@ function Invoke-WPFundoall {
for ($i = 0; $i -lt $tweaks.Count; $i++) { 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) Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Undoing $($tweaks[$i]) ($($i + 1)/$($tweaks.Count))" -Percent ($i / $tweaks.Count * 100)
Invoke-WinUtiltweaks $tweaks[$i] -undo $true Invoke-WinUtiltweaks $tweaks[$i] -undo $true
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($i/$tweaks.Count) } 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 Set-WinUtilTweaksProgressIndicator -Visible $true -Label "Undo Tweaks Finished" -Percent 100
$sync.ProcessRunning = $false $sync.ProcessRunning = $false
Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" } Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "None" -overlay "checkmark" }
-4
View File
@@ -46,9 +46,6 @@ BeforeAll {
function Invoke-WPFUIThread { function Invoke-WPFUIThread {
param([scriptblock]$ScriptBlock) param([scriptblock]$ScriptBlock)
} }
function Set-WinUtilProgressBar {
param($Label, $Percent)
}
function Set-WinUtilTweaksProgressIndicator { function Set-WinUtilTweaksProgressIndicator {
param($Visible, $Label, $Percent) param($Visible, $Label, $Percent)
} }
@@ -235,7 +232,6 @@ Describe "Get installed AppX selection" {
WPFAppxMissing = [pscustomobject]@{ IsChecked = $false } WPFAppxMissing = [pscustomobject]@{ IsChecked = $false }
}) })
Mock Set-WinUtilProgressBar { }
Mock Set-WinUtilTweaksProgressIndicator { } Mock Set-WinUtilTweaksProgressIndicator { }
Mock Get-WinUtilInstalledAPPX { @("Example.Package") } Mock Get-WinUtilInstalledAPPX { @("Example.Package") }
Mock Invoke-WPFAppxInstall { } Mock Invoke-WPFAppxInstall { }
+61 -17
View File
@@ -18,10 +18,9 @@ BeforeAll {
function Get-WinUtilSelectedPackages { function Get-WinUtilSelectedPackages {
param($PackageList, [string]$Preference) param($PackageList, [string]$Preference)
} }
function Show-WPFInstallAppBusy { function Set-WinUtilTweaksProgressIndicator {
param($text) param($Visible, $Label, $Percent)
} }
function Hide-WPFInstallAppBusy { }
function Install-WinUtilWinget { } function Install-WinUtilWinget { }
function Install-WinUtilChoco { } function Install-WinUtilChoco { }
function Install-WinUtilProgramWinget { function Install-WinUtilProgramWinget {
@@ -74,6 +73,9 @@ BeforeAll {
preferences = [pscustomobject]@{ preferences = [pscustomobject]@{
packagemanager = "Winget" packagemanager = "Winget"
} }
Form = [pscustomobject]@{
Dispatcher = [pscustomobject]@{}
}
configs = @{ configs = @{
applicationsHashtable = $applications applicationsHashtable = $applications
} }
@@ -187,8 +189,7 @@ Describe "Invoke-WPFInstall runspace body" {
Mock Get-WinUtilSelectedPackages { Mock Get-WinUtilSelectedPackages {
New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc") New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc")
} }
Mock Show-WPFInstallAppBusy { } Mock Set-WinUtilTweaksProgressIndicator { }
Mock Hide-WPFInstallAppBusy { }
Mock Install-WinUtilWinget { } Mock Install-WinUtilWinget { }
Mock Install-WinUtilChoco { } Mock Install-WinUtilChoco { }
Mock Install-WinUtilProgramWinget { } Mock Install-WinUtilProgramWinget { }
@@ -212,8 +213,17 @@ Describe "Invoke-WPFInstall runspace body" {
Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter {
@($PackageList).Count -eq 1 -and $Preference -eq "Winget" @($PackageList).Count -eq 1 -and $Preference -eq "Winget"
} }
Should -Invoke -CommandName Show-WPFInstallAppBusy -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$text -eq "Installing apps..." $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-WinUtilWinget -Times 1 -Exactly
Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter { 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 { Should -Invoke -CommandName Install-WinUtilProgramChoco -Times 1 -Exactly -ParameterFilter {
$Action -eq "Install" -and @($Programs)[0] -eq "vlc" $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 { Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*' $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
} }
$script:sync.ProcessRunning | Should -BeFalse $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" } Mock Install-WinUtilProgramWinget { throw "winget failed" }
Invoke-WPFInstall Invoke-WPFInstall
& $script:capturedInstallScriptBlock -PackagesToInstall @($script:package) -ManagerPreference "Winget" & $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 { Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*' $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
} }
@@ -345,8 +368,7 @@ Describe "Invoke-WPFUnInstall runspace body" {
Mock Get-WinUtilSelectedPackages { Mock Get-WinUtilSelectedPackages {
New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc") New-WinUtilPackageSplit -Winget @("Git.Git") -Choco @("vlc")
} }
Mock Show-WPFInstallAppBusy { } Mock Set-WinUtilTweaksProgressIndicator { }
Mock Hide-WPFInstallAppBusy { }
Mock Install-WinUtilProgramWinget { } Mock Install-WinUtilProgramWinget { }
Mock Install-WinUtilProgramChoco { } Mock Install-WinUtilProgramChoco { }
Mock Invoke-WPFUIThread { } Mock Invoke-WPFUIThread { }
@@ -369,8 +391,17 @@ Describe "Invoke-WPFUnInstall runspace body" {
Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Get-WinUtilSelectedPackages -Times 1 -Exactly -ParameterFilter {
@($PackageList).Count -eq 1 -and $Preference -eq "Winget" @($PackageList).Count -eq 1 -and $Preference -eq "Winget"
} }
Should -Invoke -CommandName Show-WPFInstallAppBusy -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$text -eq "Uninstalling apps..." $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 { Should -Invoke -CommandName Install-WinUtilProgramWinget -Times 1 -Exactly -ParameterFilter {
$Action -eq "Uninstall" -and @($Programs)[0] -eq "Git.Git" $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 { Should -Invoke -CommandName Install-WinUtilProgramChoco -Times 1 -Exactly -ParameterFilter {
$Action -eq "Uninstall" -and @($Programs)[0] -eq "vlc" $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 { Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*' $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "None" -overlay "checkmark"*'
} }
$script:sync.ProcessRunning | Should -BeFalse $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" } Mock Install-WinUtilProgramWinget { throw "winget failed" }
Invoke-WPFUnInstall -PackagesToUninstall @($script:package) Invoke-WPFUnInstall -PackagesToUninstall @($script:package)
& $script:capturedUninstallScriptBlock -PackagesToUninstall @($script:package) -ManagerPreference "Winget" & $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 { Should -Invoke -CommandName Invoke-WPFUIThread -Times 1 -Exactly -ParameterFilter {
$ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*' $ScriptBlock.ToString() -like '*Set-WinUtilTaskbaritem -state "Error" -overlay "warning"*'
} }
+25
View File
@@ -69,6 +69,23 @@ Describe "Initialize-WinUtilTabContent" {
$targetGridName -eq "appxpanel" -and $columncount -eq 2 $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" { Describe "Startup lazy tab wiring" {
@@ -96,4 +113,12 @@ Describe "Startup lazy tab wiring" {
$rendererScript | Should -Match '\$sync\.Buttons\.Add\(\$button\.Name\)' $rendererScript | Should -Match '\$sync\.Buttons\.Add\(\$button\.Name\)'
$mainScript | Should -Match '\$sync\.Buttons -notcontains \$psitem' $mainScript | Should -Match '\$sync\.Buttons -notcontains \$psitem'
} }
It "binds generated documentation links when lazy panels are rendered" {
$rendererScript = Get-Content -Path (Join-Path $script:repoRoot "functions\public\Invoke-WPFUIElements.ps1") -Raw
$mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw
$rendererScript | Should -Match '(?s)if \(\$entryInfo\.Link\).*\$textBlock\.Add_MouseUp\(\{.*Start-Process \$Sender\.ToolTip -ErrorAction Stop'
$mainScript | Should -Not -Match '\.Name\.EndsWith\("Link"\)'
}
} }
+139
View File
@@ -0,0 +1,139 @@
#===========================================================================
# Tests - O&O ShutUp10++ Download Workflow
#===========================================================================
BeforeAll {
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
. (Join-Path $script:repoRoot "functions\private\Save-WinUtilFile.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFOOSU.ps1")
function Invoke-WPFRunspace {
param($ArgumentList, $ParameterList, [scriptblock]$ScriptBlock)
}
function Set-WinUtilTweaksProgressIndicator {
param($Visible, $Label, $Percent)
}
function Show-WinUtilMessage {
param($Message, $Title, $Button, $Icon)
}
function Write-WinUtilLog {
param($Message, $Level, $Component)
}
function script:New-WinUtilOOSUTestContext {
param([bool]$ProcessRunning = $false)
$script:sync = [Hashtable]::Synchronized(@{
ProcessRunning = $ProcessRunning
winutildir = $TestDrive
Form = [pscustomobject]@{
Dispatcher = [pscustomobject]@{}
}
})
}
}
Describe "Save-WinUtilFile" {
It "copies a download and reports its percentage" {
$sourcePath = Join-Path $TestDrive "source.bin"
$destinationPath = Join-Path $TestDrive "destination.bin"
$sourceBytes = [byte[]](0..255)
[System.IO.File]::WriteAllBytes($sourcePath, $sourceBytes)
$reportedProgress = [System.Collections.Generic.List[int]]::new()
Save-WinUtilFile -Uri ([uri]$sourcePath) -DestinationPath $destinationPath -ProgressCallback {
param($percent)
$reportedProgress.Add($percent)
}
[System.IO.File]::ReadAllBytes($destinationPath) | Should -Be $sourceBytes
$reportedProgress[-1] | Should -Be 100
}
}
Describe "Invoke-WPFOOSU" {
BeforeEach {
New-WinUtilOOSUTestContext
$script:capturedScriptBlock = $null
$script:capturedParameterList = $null
Mock Invoke-WPFRunspace {
$script:capturedScriptBlock = $ScriptBlock
$script:capturedParameterList = $ParameterList
[pscustomobject]@{ MockHandle = $true }
}
Mock Set-WinUtilTweaksProgressIndicator { }
Mock Show-WinUtilMessage { }
Mock Write-WinUtilLog { }
Mock Start-Process { }
Mock Write-Error { }
}
AfterEach {
Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedScriptBlock -Scope Script -ErrorAction SilentlyContinue
Remove-Variable -Name capturedParameterList -Scope Script -ErrorAction SilentlyContinue
}
It "queues the download in a background runspace" {
Invoke-WPFOOSU
$script:sync.ProcessRunning | Should -BeTrue
Should -Invoke Invoke-WPFRunspace -Times 1 -Exactly
$script:capturedParameterList[0][0] | Should -Be "downloadPath"
$script:capturedParameterList[0][1] | Should -Be (Join-Path $TestDrive "ooshutup10.exe")
}
It "does not start while another process is running" {
New-WinUtilOOSUTestContext -ProcessRunning $true
Invoke-WPFOOSU
Should -Invoke Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Message -eq "Another process is currently running." -and
$Title -eq "WinUtil" -and
$Button -eq "OK" -and
$Icon -eq "Warning"
}
Should -Not -Invoke Invoke-WPFRunspace
}
It "maps download progress to the window indicator and launches O&O ShutUp10++" {
Mock Save-WinUtilFile {
& $ProgressCallback 35
& $ProgressCallback 100
}
Invoke-WPFOOSU
& $script:capturedScriptBlock -downloadPath $script:capturedParameterList[0][1]
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$Visible -eq $true -and $Label -eq "Downloading O&O ShutUp10++ (0%)" -and $Percent -eq 0
}
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$Visible -eq $true -and $Label -eq "Downloading O&O ShutUp10++ (35%)" -and $Percent -eq 35
}
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$Visible -eq $true -and $Label -eq "O&O ShutUp10++ launched" -and $Percent -eq 100
}
Should -Invoke Start-Process -Times 1 -Exactly -ParameterFilter {
$FilePath -eq (Join-Path $TestDrive "ooshutup10.exe")
}
$script:sync.ProcessRunning | Should -BeFalse
}
It "shows failure progress and clears the running state when the download fails" {
Mock Save-WinUtilFile { throw "download failed" }
Invoke-WPFOOSU
& $script:capturedScriptBlock -downloadPath $script:capturedParameterList[0][1]
Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$Visible -eq $true -and $Label -eq "O&O ShutUp10++ download failed" -and $Percent -eq 100
}
Should -Not -Invoke Start-Process
Should -Invoke Write-Error -Times 1 -Exactly
$script:sync.ProcessRunning | Should -BeFalse
}
}
-4
View File
@@ -31,9 +31,6 @@ BeforeAll {
function Invoke-WPFUIThread { function Invoke-WPFUIThread {
param([scriptblock]$ScriptBlock) param([scriptblock]$ScriptBlock)
} }
function Set-WinUtilProgressBar {
param($Label, $Percent)
}
function Set-WinUtilTweaksProgressIndicator { function Set-WinUtilTweaksProgressIndicator {
param($Visible, $Label, $Percent) param($Visible, $Label, $Percent)
} }
@@ -187,7 +184,6 @@ Describe "Invoke-WPFtweaksbutton" {
Mock Invoke-WPFRunspace { [pscustomobject]@{ MockHandle = $true } } Mock Invoke-WPFRunspace { [pscustomobject]@{ MockHandle = $true } }
Mock Invoke-WinUtilTweaks { } Mock Invoke-WinUtilTweaks { }
Mock Invoke-WPFUIThread { } Mock Invoke-WPFUIThread { }
Mock Set-WinUtilProgressBar { }
Mock Write-WinUtilLog { } Mock Write-WinUtilLog { }
Mock Write-Host { } Mock Write-Host { }
} }
+9 -9
View File
@@ -65,10 +65,6 @@ namespace System.Windows.Controls
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1") . (Join-Path $script:repoRoot "functions\public\Invoke-WPFButton.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFToggleAllCategories.ps1") . (Join-Path $script:repoRoot "functions\public\Invoke-WPFToggleAllCategories.ps1")
function Set-WinUtilProgressBar {
param($Label, $Percent)
}
function Set-WinUtilTweaksProgressIndicator { function Set-WinUtilTweaksProgressIndicator {
param($Visible, $Label, $Percent) param($Visible, $Label, $Percent)
} }
@@ -303,7 +299,6 @@ Describe "Invoke-WPFToggleAllCategories" {
Describe "Invoke-WPFButton progress cleanup" { Describe "Invoke-WPFButton progress cleanup" {
BeforeEach { BeforeEach {
New-WinUtilUiStateTestContext New-WinUtilUiStateTestContext
Mock Set-WinUtilProgressBar { }
Mock Set-WinUtilTweaksProgressIndicator { } Mock Set-WinUtilTweaksProgressIndicator { }
} }
@@ -317,9 +312,6 @@ Describe "Invoke-WPFButton progress cleanup" {
Invoke-WPFButton -Button "WPFNoOp" 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 { Should -Invoke Set-WinUtilTweaksProgressIndicator -Times 1 -Exactly -ParameterFilter {
$Visible -eq $false $Visible -eq $false
} }
@@ -330,7 +322,15 @@ Describe "Invoke-WPFButton progress cleanup" {
Invoke-WPFButton -Button "WPFNoOp" 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 Should -Not -Invoke Set-WinUtilTweaksProgressIndicator
} }
} }
+136 -36
View File
@@ -11,8 +11,11 @@ BeforeAll {
function Write-WinUtilLog { function Write-WinUtilLog {
param($Message, $Level, $Component) param($Message, $Level, $Component)
} }
function Show-WinUtilMessage {
param($Message, $Title, $Button, $Icon)
}
function Get-ScheduledTask { function Get-ScheduledTask {
param($TaskPath) param($TaskPath, $ErrorAction)
} }
function Disable-ScheduledTask { function Disable-ScheduledTask {
param( param(
@@ -51,6 +54,7 @@ Describe "Invoke-WPFUpdatesdisable" {
BeforeEach { BeforeEach {
Mock Write-Host { } Mock Write-Host { }
Mock Write-WinUtilLog { } Mock Write-WinUtilLog { }
Mock Show-WinUtilMessage { "Yes" }
Mock New-Item { } Mock New-Item { }
Mock Set-ItemProperty { } Mock Set-ItemProperty { }
Mock Set-Service { } Mock Set-Service { }
@@ -88,25 +92,23 @@ Describe "Invoke-WPFUpdatesdisable" {
$Type -eq "DWord" -and $Type -eq "DWord" -and
$Value -eq 0 $Value -eq 0
} }
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and
$Name -eq "SettingsPageVisibility" -and
$Value -eq "hide:windowsupdate"
}
} }
It "disables update services and clears the SoftwareDistribution folder" { It "disables update services and clears the SoftwareDistribution folder" {
Invoke-WPFUpdatesdisable Invoke-WPFUpdatesdisable
foreach ($expectedServiceName in @("BITS", "wuauserv", "UsoSvc")) {
$expected = $expectedServiceName
Should -Invoke -CommandName Stop-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq $expected -and $Force -eq $true
}
}
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "BITS" -and $StartupType -eq "Disabled" $Name -eq "BITS" -and $StartupType -eq "Disabled"
} }
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "wuauserv" -and $StartupType -eq "Disabled" $Name -eq "wuauserv" -and $StartupType -eq "Disabled"
} }
Should -Invoke -CommandName Stop-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "UsoSvc" -and $Force -eq $true
}
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "UsoSvc" -and $StartupType -eq "Disabled" $Name -eq "UsoSvc" -and $StartupType -eq "Disabled"
} }
@@ -128,6 +130,21 @@ Describe "Invoke-WPFUpdatesdisable" {
} }
Should -Invoke -CommandName Disable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly Should -Invoke -CommandName Disable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly
} }
It "requires confirmation before disabling updates" {
Mock Show-WinUtilMessage { "No" }
Invoke-WPFUpdatesdisable
Should -Invoke Show-WinUtilMessage -Times 1 -Exactly -ParameterFilter {
$Title -eq "Disable Windows Update?" -and
$Button -eq "YesNo" -and
$Icon -eq "Warning"
}
Should -Not -Invoke Set-ItemProperty
Should -Not -Invoke Set-Service
Should -Not -Invoke Remove-Item
}
} }
Describe "Invoke-WPFUpdatesdefault" { Describe "Invoke-WPFUpdatesdefault" {
@@ -136,6 +153,11 @@ Describe "Invoke-WPFUpdatesdefault" {
Mock Write-WinUtilLog { } Mock Write-WinUtilLog { }
Mock Remove-Item { } Mock Remove-Item { }
Mock Remove-ItemProperty { } Mock Remove-ItemProperty { }
Mock Get-ItemProperty {
[pscustomobject]@{
SettingsPageVisibility = "hide:windowsupdate"
}
}
Mock Set-Service { } Mock Set-Service { }
Mock Start-Service { } Mock Start-Service { }
Mock Get-ScheduledTask { Mock Get-ScheduledTask {
@@ -147,30 +169,49 @@ Describe "Invoke-WPFUpdatesdefault" {
Mock secedit { } Mock secedit { }
} }
It "removes update policy registry paths and shows the Windows Update settings page" { It "removes only registry values managed by WinUtil" {
Invoke-WPFUpdatesdefault Invoke-WPFUpdatesdefault
$expectedPaths = @( $expectedRegistryValues = @(
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", "NoAutoUpdate", "AUOptions", "NoAutoRebootWithLoggedOnUsers", "AUPowerManagement"),
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization", @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "ExcludeWUDriversInQualityUpdate", "DeferFeatureUpdates", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdates", "DeferQualityUpdatesPeriodInDays"),
"HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings", @("HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings", "BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays"),
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata", @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata", "PreventDeviceMetadataFromNetwork"),
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching", @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching", "DontPromptForWindowsUpdate", "DontSearchWindowsUpdate", "DriverUpdateWizardWuSearchEnabled"),
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" @("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config", "DODownloadMode")
) )
foreach ($expectedRegistryPath in $expectedPaths) { foreach ($expectedEntry in $expectedRegistryValues) {
$expected = $expectedRegistryPath $expectedPath = $expectedEntry[0]
Should -Invoke -CommandName Remove-Item -Times 1 -Exactly -ParameterFilter { foreach ($expectedName in $expectedEntry[1..($expectedEntry.Count - 1)]) {
$Path -eq $expected -and $Recurse -eq $true -and $Force -eq $true $valueName = $expectedName
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq $expectedPath -and $Name -eq $valueName
}
} }
} }
Should -Not -Invoke Remove-Item
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and $Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and
$Name -eq "SettingsPageVisibility" $Name -eq "SettingsPageVisibility"
} }
} }
It "preserves unrelated Settings page visibility policy" {
Mock Get-ItemProperty {
[pscustomobject]@{
SettingsPageVisibility = "hide:privacy"
}
}
Invoke-WPFUpdatesdefault
Should -Not -Invoke Remove-ItemProperty -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and
$Name -eq "SettingsPageVisibility"
}
}
It "restores update service startup types" { It "restores update service startup types" {
Invoke-WPFUpdatesdefault Invoke-WPFUpdatesdefault
@@ -186,12 +227,12 @@ Describe "Invoke-WPFUpdatesdefault" {
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "UsoSvc" -and $StartupType -eq "Automatic" $Name -eq "UsoSvc" -and $StartupType -eq "Automatic"
} }
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { Should -Not -Invoke Set-Service -ParameterFilter {
$Name -eq "WaaSMedicSvc" -and $StartupType -eq "Manual" $Name -eq "WaaSMedicSvc"
} }
} }
It "enables update scheduled task paths and resets local policy defaults" { It "enables update scheduled task paths without resetting unrelated local security policy" {
Invoke-WPFUpdatesdefault Invoke-WPFUpdatesdefault
foreach ($expectedTaskPath in $script:updateTaskPaths) { foreach ($expectedTaskPath in $script:updateTaskPaths) {
@@ -201,13 +242,7 @@ Describe "Invoke-WPFUpdatesdefault" {
} }
} }
Should -Invoke -CommandName Enable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly Should -Invoke -CommandName Enable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly
Should -Invoke -CommandName secedit -Times 1 -Exactly -ParameterFilter { Should -Not -Invoke secedit
$Arguments[0] -eq "/configure" -and
$Arguments[1] -eq "/cfg" -and
$Arguments[2] -like "*\inf\defltbase.inf" -and
$Arguments[3] -eq "/db" -and
$Arguments[4] -eq "defltbase.sdb"
}
} }
} }
@@ -217,6 +252,47 @@ Describe "Invoke-WPFUpdatessecurity" {
Mock Write-WinUtilLog { } Mock Write-WinUtilLog { }
Mock New-Item { } Mock New-Item { }
Mock Set-ItemProperty { } Mock Set-ItemProperty { }
Mock Remove-ItemProperty { }
Mock Set-Service { }
Mock Start-Service { }
Mock Get-ScheduledTask {
[pscustomobject]@{
TaskPath = $TaskPath
}
}
Mock Enable-ScheduledTask { }
}
It "restores update availability before applying recommended settings" {
Invoke-WPFUpdatessecurity
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and
$Name -eq "NoAutoUpdate"
}
Should -Invoke -CommandName Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" -and
$Name -eq "DODownloadMode"
}
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "BITS" -and $StartupType -eq "Manual"
}
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "wuauserv" -and $StartupType -eq "Manual"
}
Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "UsoSvc" -and $StartupType -eq "Automatic"
}
Should -Invoke -CommandName Start-Service -Times 1 -Exactly -ParameterFilter {
$Name -eq "UsoSvc"
}
foreach ($expectedTaskPath in $script:updateTaskPaths) {
$expected = $expectedTaskPath
Should -Invoke -CommandName Get-ScheduledTask -Times 1 -Exactly -ParameterFilter {
$TaskPath -eq $expected
}
}
Should -Invoke -CommandName Enable-ScheduledTask -Times $script:updateTaskPaths.Count -Exactly
} }
It "disables driver metadata and Windows Update driver search" { It "disables driver metadata and Windows Update driver search" {
@@ -261,23 +337,35 @@ Describe "Invoke-WPFUpdatessecurity" {
Invoke-WPFUpdatessecurity Invoke-WPFUpdatessecurity
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and $Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
$Name -eq "BranchReadinessLevel" -and $Name -eq "DeferFeatureUpdates" -and
$Type -eq "DWord" -and $Type -eq "DWord" -and
$Value -eq 20 $Value -eq 1
} }
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and $Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
$Name -eq "DeferFeatureUpdatesPeriodInDays" -and $Name -eq "DeferFeatureUpdatesPeriodInDays" -and
$Type -eq "DWord" -and $Type -eq "DWord" -and
$Value -eq 365 $Value -eq 365
} }
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and $Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
$Name -eq "DeferQualityUpdates" -and
$Type -eq "DWord" -and
$Value -eq 1
}
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and
$Name -eq "DeferQualityUpdatesPeriodInDays" -and $Name -eq "DeferQualityUpdatesPeriodInDays" -and
$Type -eq "DWord" -and $Type -eq "DWord" -and
$Value -eq 4 $Value -eq 4
} }
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and
$Name -eq "AUOptions" -and
$Type -eq "DWord" -and
$Value -eq 4
}
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter { Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and $Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and
$Name -eq "NoAutoRebootWithLoggedOnUsers" -and $Name -eq "NoAutoRebootWithLoggedOnUsers" -and
@@ -291,4 +379,16 @@ Describe "Invoke-WPFUpdatessecurity" {
$Value -eq 0 $Value -eq 0
} }
} }
It "removes legacy WinUtil deferral values from the unsupported UX settings path" {
Invoke-WPFUpdatessecurity
foreach ($expectedValueName in @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")) {
$expected = $expectedValueName
Should -Invoke Remove-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
$Name -eq $expected
}
}
}
} }
+16
View File
@@ -6,6 +6,7 @@ Describe "Win11 Creator setup media" {
BeforeAll { BeforeAll {
$script:repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") $script:repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
$script:isoWorkflowPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISO.ps1" $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:isoScriptPath = Join-Path $script:repoRoot "functions\private\Invoke-WinUtilISOScript.ps1"
$script:autoUnattendPath = Join-Path $script:repoRoot "tools\autounattend.xml" $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: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:editionIdFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Get-WinUtilEditionIdFromName"
$script:addDriversFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Add-DriversToImage" $script:addDriversFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Add-DriversToImage"
$script:answerFileChildElementFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Get-WinUtilISOScriptChildElement" $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")) $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" { It "mounts the copied image file that was verified from the ISO" {
foreach ($expectedText in @( foreach ($expectedText in @(
'$sourceImageFileName = Split-Path $wimPath -Leaf', '$sourceImageFileName = Split-Path $wimPath -Leaf',
+18 -7
View File
@@ -152,7 +152,6 @@ Describe "XAML document" {
"appspanel", "appspanel",
"tweakspanel", "tweakspanel",
"featurespanel", "featurespanel",
"updatespanel",
"appxpanel", "appxpanel",
"WPFstandard", "WPFstandard",
"WPFminimal", "WPFminimal",
@@ -180,6 +179,23 @@ Describe "XAML document" {
} }
} }
It "presents the three Updates profiles with accurate action labels" {
$updatesTab = $script:xaml.SelectSingleNode('//*[local-name()="TabItem"][@Name="WPFTab4"]')
$profileGrid = $updatesTab.SelectSingleNode('.//*[local-name()="UniformGrid"]')
$expectedButtons = @{
WPFUpdatessecurity = "Apply Recommended"
WPFUpdatesdefault = "Restore Defaults"
WPFUpdatesdisable = "Disable Updates"
}
$profileGrid.GetAttribute("Columns") | Should -Be "3"
foreach ($buttonName in $expectedButtons.Keys) {
$button = $updatesTab.SelectSingleNode(".//*[local-name()='Button'][@Name='$buttonName']")
$button.GetAttribute("Content") | Should -Be $expectedButtons[$buttonName]
}
$updatesTab.SelectSingleNode('.//*[@Name="updatespanel"]') | Should -BeNullOrEmpty
}
It "contains Win11 Creator controls used by the ISO workflow" { It "contains Win11 Creator controls used by the ISO workflow" {
$xamlNames = @(Get-WinUtilXamlRuntimeNamedControls | ForEach-Object { $_.Name }) $xamlNames = @(Get-WinUtilXamlRuntimeNamedControls | ForEach-Object { $_.Name })
$requiredControls = @( $requiredControls = @(
@@ -432,20 +448,15 @@ Describe "XAML and sync wiring" {
"InitializedTabs", "InitializedTabs",
"RenderedAssetCache", "RenderedAssetCache",
"ToggleStatusCache", "ToggleStatusCache",
"InstallAppAreaBorder",
"InstallAppAreaScrollViewer",
"InstallAppAreaOverlay",
"InstallAppAreaOverlayText",
"InstallAppRenderQueue", "InstallAppRenderQueue",
"InstallAppEntriesRendered", "InstallAppEntriesRendered",
"ProgressBar",
"progressBarTextBlock",
"FontScaleFactor", "FontScaleFactor",
"Win11ISOImageInfo", "Win11ISOImageInfo",
"Win11ISODriveLetter", "Win11ISODriveLetter",
"Win11ISOWimPath", "Win11ISOWimPath",
"Win11ISOImagePath", "Win11ISOImagePath",
"Win11ISOModifying", "Win11ISOModifying",
"Win11ISOProcessRunning",
"Win11ISOWorkDir", "Win11ISOWorkDir",
"Win11ISOContentsDir", "Win11ISOContentsDir",
"Win11ISOUSBDisks" "Win11ISOUSBDisks"
-12
View File
@@ -128,9 +128,6 @@ Invoke-WinutilThemeChange -theme $sync.preferences.theme
$sync.InitializedTabs = @{} $sync.InitializedTabs = @{}
Initialize-WinUtilTabContent -TabName "Install" Initialize-WinUtilTabContent -TabName "Install"
# Future implementation: Add Windows Version to updates panel
#Invoke-WPFUIElements -configVariable $sync.configs.updates -targetGridName "updatespanel" -columncount 1
#=========================================================================== #===========================================================================
# Store Form Objects In PowerShell # Store Form Objects In PowerShell
#=========================================================================== #===========================================================================
@@ -171,15 +168,6 @@ $sync.keys | ForEach-Object {
} }
} }
if ($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "TextBlock") {
if ($sync["$psitem"].Name.EndsWith("Link")) {
$sync["$psitem"].Add_MouseUp({
[System.Object]$Sender = $args[0]
Start-Process $Sender.ToolTip -ErrorAction Stop
})
}
}
} }
} }
+1
View File
@@ -62,6 +62,7 @@ $sync.configs = @{}
$sync.Buttons = [System.Collections.Generic.List[PSObject]]::new() $sync.Buttons = [System.Collections.Generic.List[PSObject]]::new()
$sync.preferences = @{} $sync.preferences = @{}
$sync.ProcessRunning = $false $sync.ProcessRunning = $false
$sync.Win11ISOProcessRunning = $false
$sync.selectedAppx = [System.Collections.Generic.List[string]]::new() $sync.selectedAppx = [System.Collections.Generic.List[string]]::new()
$sync.selectedApps = [System.Collections.Generic.List[string]]::new() $sync.selectedApps = [System.Collections.Generic.List[string]]::new()
$sync.selectedTweaks = [System.Collections.Generic.List[string]]::new() $sync.selectedTweaks = [System.Collections.Generic.List[string]]::new()
+128 -79
View File
@@ -1366,99 +1366,148 @@
</TabItem> </TabItem>
<TabItem Header="Updates" Visibility="Collapsed" Name="WPFTab4"> <TabItem Header="Updates" Visibility="Collapsed" Name="WPFTab4">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="{DynamicResource TabContentMargin}"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Margin="{DynamicResource TabContentMargin}">
<Grid Background="Transparent" MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ScrollViewer}}"> <Grid Background="Transparent" MaxWidth="1250" HorizontalAlignment="Center">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Row for the 3 columns --> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <!-- Row for Windows Version --> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Three columns container --> <StackPanel Grid.Row="0" Margin="10,10,10,14">
<Grid Grid.Row="0"> <TextBlock Text="Windows Update Profiles"
<Grid.ColumnDefinitions> FontSize="24"
<ColumnDefinition Width="*"/> FontWeight="Bold"
<ColumnDefinition Width="*"/> Foreground="{DynamicResource MainForegroundColor}"/>
<ColumnDefinition Width="*"/> <TextBlock Text="Choose how Windows receives updates. Each profile replaces the Windows Update settings managed by WinUtil."
</Grid.ColumnDefinitions> Margin="0,6,0,0"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource MainForegroundColor}"/>
</StackPanel>
<!-- Default Settings --> <UniformGrid Grid.Row="1" Columns="3">
<Border Grid.Column="0" Style="{StaticResource BorderStyle}"> <Border Style="{StaticResource BorderStyle}"
<StackPanel> BorderBrush="{DynamicResource ProgressBarForegroundColor}"
<Button Name="WPFUpdatesdefault" BorderThickness="2"
FontSize="{DynamicResource ConfigTabButtonFontSize}" Padding="16"
Content="Default Settings" MinHeight="300">
Margin="10,5" <Grid>
Padding="10"/> <Grid.RowDefinitions>
<TextBlock Margin="10" <RowDefinition Height="Auto"/>
TextWrapping="Wrap" <RowDefinition Height="*"/>
Foreground="{DynamicResource MainForegroundColor}"> <RowDefinition Height="Auto"/>
<Run FontWeight="Bold">Default Windows Update Configuration</Run> </Grid.RowDefinitions>
<LineBreak/> <StackPanel Grid.Row="0" Margin="0,0,0,14">
- No modifications to Windows defaults <TextBlock Text="Recommended"
<LineBreak/> FontSize="20"
- Removes any custom update settings FontWeight="Bold"
<LineBreak/><LineBreak/> Foreground="{DynamicResource MainForegroundColor}"/>
<Run FontStyle="Italic" FontSize="11">Note: This resets your Windows Update settings to default out of the box settings. It removes ANY policy or customization that has been done to Windows Update.</Run> <TextBlock Text="Balanced security and stability"
</TextBlock> Margin="0,4,0,0"
</StackPanel> FontSize="13"
</Border> Foreground="{DynamicResource MainForegroundColor}"/>
</StackPanel>
<!-- Security Settings --> <StackPanel Grid.Row="1">
<Border Grid.Column="1" Style="{StaticResource BorderStyle}"> <TextBlock Text="- Defers feature updates for 365 days" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<StackPanel> <TextBlock Text="- Defers quality updates for 4 days" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="- Excludes drivers from quality updates" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="- Prevents automatic restarts while a user is signed in" TextWrapping="Wrap" Margin="0,0,0,12" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="Available on Windows Pro, Enterprise, and Education editions."
FontSize="11"
FontStyle="Italic"
TextWrapping="Wrap"
Foreground="{DynamicResource MainForegroundColor}"/>
</StackPanel>
<Button Name="WPFUpdatessecurity" <Button Name="WPFUpdatessecurity"
Grid.Row="2"
Content="Apply Recommended"
FontSize="{DynamicResource ConfigTabButtonFontSize}" FontSize="{DynamicResource ConfigTabButtonFontSize}"
Content="Security Settings" Margin="0,16,0,0"
Margin="10,5"
Padding="10"/> Padding="10"/>
<TextBlock Margin="10" </Grid>
TextWrapping="Wrap"
Foreground="{DynamicResource MainForegroundColor}">
<Run FontWeight="Bold">Balanced Security Configuration</Run>
<LineBreak/>
- Feature updates delayed by 365 days
<LineBreak/>
- Security updates installed after 4 days
<LineBreak/>
- Prevents Windows Update from installing drivers
<LineBreak/><LineBreak/>
<Run FontWeight="SemiBold">Feature Updates:</Run> New features and potential bugs
<LineBreak/>
<Run FontWeight="SemiBold">Security Updates:</Run> Critical security patches
<LineBreak/><LineBreak/>
<Run FontStyle="Italic" FontSize="11">Note: This only applies to Pro systems that can use group policy.</Run>
</TextBlock>
</StackPanel>
</Border> </Border>
<!-- Disable Updates --> <Border Style="{StaticResource BorderStyle}" Padding="16" MinHeight="300">
<Border Grid.Column="2" Style="{StaticResource BorderStyle}"> <Grid>
<StackPanel> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="0,0,0,14">
<TextBlock Text="Windows Default"
FontSize="20"
FontWeight="Bold"
Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="Return control to Windows"
Margin="0,4,0,0"
FontSize="13"
Foreground="{DynamicResource MainForegroundColor}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="- Removes Windows Update policies applied by WinUtil" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="- Restores update service startup settings" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="- Re-enables update scheduled tasks" TextWrapping="Wrap" Margin="0,0,0,12" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="Use this to undo the Recommended or Disable profile."
FontSize="11"
FontStyle="Italic"
TextWrapping="Wrap"
Foreground="{DynamicResource MainForegroundColor}"/>
</StackPanel>
<Button Name="WPFUpdatesdefault"
Grid.Row="2"
Content="Restore Defaults"
FontSize="{DynamicResource ConfigTabButtonFontSize}"
Margin="0,16,0,0"
Padding="10"/>
</Grid>
</Border>
<Border Style="{StaticResource BorderStyle}" Padding="16" MinHeight="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Margin="0,0,0,14">
<TextBlock Text="Disable Updates"
FontSize="20"
FontWeight="Bold"
Foreground="Red"/>
<TextBlock Text="Advanced use only"
Margin="0,4,0,0"
FontSize="13"
FontWeight="SemiBold"
Foreground="Red"/>
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="- Disables automatic update policy" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="- Stops update services and scheduled tasks" TextWrapping="Wrap" Margin="0,0,0,7" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="- Clears downloaded update files" TextWrapping="Wrap" Margin="0,0,0,12" Foreground="{DynamicResource MainForegroundColor}"/>
<TextBlock Text="Security updates will not be installed while this profile is active."
FontSize="11"
FontStyle="Italic"
TextWrapping="Wrap"
Foreground="Red"/>
</StackPanel>
<Button Name="WPFUpdatesdisable" <Button Name="WPFUpdatesdisable"
Grid.Row="2"
Content="Disable Updates"
FontSize="{DynamicResource ConfigTabButtonFontSize}" FontSize="{DynamicResource ConfigTabButtonFontSize}"
Content="Disable All Updates"
Foreground="Red" Foreground="Red"
Margin="10,5" Margin="0,16,0,0"
Padding="10"/> Padding="10"/>
<TextBlock Margin="10" </Grid>
TextWrapping="Wrap"
Foreground="{DynamicResource MainForegroundColor}">
<Run FontWeight="Bold" Foreground="Red">!! Not Recommended !!</Run>
<LineBreak/>
- Disables ALL Windows Updates
<LineBreak/>
- Increases security risks
<LineBreak/>
- Only use for isolated systems
<LineBreak/><LineBreak/>
<Run FontStyle="Italic" FontSize="11">Warning: Your system will be vulnerable without security updates.</Run>
</TextBlock>
</StackPanel>
</Border> </Border>
</Grid> </UniformGrid>
<!-- Future Implementation: Add Windows Version to updates panel --> <Border Grid.Row="2" Style="{StaticResource BorderStyle}" Margin="8,14,8,8" Padding="12">
<Grid Name="updatespanel" Grid.Row="1" Background="Transparent"> <TextBlock Text="Changes apply system-wide. Restart Windows after switching profiles. Use Restore Defaults to undo WinUtil update policies."
</Grid> TextWrapping="Wrap"
HorizontalAlignment="Center"
Foreground="{DynamicResource MainForegroundColor}"/>
</Border>
</Grid> </Grid>
</ScrollViewer> </ScrollViewer>
</TabItem> </TabItem>
@@ -1823,7 +1872,7 @@
</TabItem> </TabItem>
</TabControl> </TabControl>
<!-- Tweaks/Undo progress indicator - visible regardless of active tab --> <!-- Window-level progress indicator - visible regardless of active tab -->
<Border Name="WPFTweaksProgressBar" Grid.Row="3" Background="{DynamicResource MainBackgroundColor}" Visibility="Collapsed" Padding="10,6"> <Border Name="WPFTweaksProgressBar" Grid.Row="3" Background="{DynamicResource MainBackgroundColor}" Visibility="Collapsed" Padding="10,6">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock Name="WPFTweaksProgressLabel" Text="" Foreground="{DynamicResource MainForegroundColor}" FontSize="13" Background="Transparent" Margin="0,0,0,4"/> <TextBlock Name="WPFTweaksProgressLabel" Text="" Foreground="{DynamicResource MainForegroundColor}" FontSize="13" Background="Transparent" Margin="0,0,0,4"/>