diff --git a/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/xboxremoval.md b/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/xboxremoval.md new file mode 100644 index 00000000..05330b4e --- /dev/null +++ b/docs/content/dev/tweaks/z--Advanced-Tweaks---CAUTION/xboxremoval.md @@ -0,0 +1,8 @@ +--- +title: "Xbox and Gaming Components - Removal" +description: "Xbox package management has moved to AppX Removal." +--- + +The former Xbox Removal tweak has been replaced by the AppX Removal tool. + +Open **Tweaks**, select **AppX Removal**, and choose the Xbox or gaming packages you want to remove or reinstall. See the [AppX Packages guide](/userguide/tweaks/#appx-packages) for details. diff --git a/docs/content/userguide/updates/_index.md b/docs/content/userguide/updates/_index.md index 34d24690..d8f6e02e 100644 --- a/docs/content/userguide/updates/_index.md +++ b/docs/content/userguide/updates/_index.md @@ -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. -{{< 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 -- **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 +### Windows Default -### Default (Out of Box) Settings - -- **What it does**: Restores the default Windows Update configuration. +- **What it does**: Removes Windows Update policies managed by WinUtil, restores update service startup settings, and re-enables update scheduled tasks. - **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. - **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. -### 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. - **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. diff --git a/functions/private/Save-WinUtilFile.ps1 b/functions/private/Save-WinUtilFile.ps1 new file mode 100644 index 00000000..726a008c --- /dev/null +++ b/functions/private/Save-WinUtilFile.ps1 @@ -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() + } + } +} diff --git a/functions/public/Invoke-WPFOOSU.ps1 b/functions/public/Invoke-WPFOOSU.ps1 index 7b21bbd9..993e32a7 100644 --- a/functions/public/Invoke-WPFOOSU.ps1 +++ b/functions/public/Invoke-WPFOOSU.ps1 @@ -1,12 +1,50 @@ function Invoke-WPFOOSU { - try { - $ProgressPreference = 'SilentlyContinue' + if ($sync.ProcessRunning) { + 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" - Start-Process -FilePath "$winutildir\ooshutup10.exe" + $downloadPath = Join-Path $sync.winutildir "ooshutup10.exe" + $sync.ProcessRunning = $true - $ProgressPreference = 'Continue' - } catch { - Write-Error "Couldn't download O&O ShutUp10. Please make sure you have an active Internet connection." + Invoke-WPFRunspace -ParameterList @(,("downloadPath", $downloadPath)) -ScriptBlock { + param($downloadPath) + + $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 + } } } diff --git a/functions/public/Invoke-WPFUIElements.ps1 b/functions/public/Invoke-WPFUIElements.ps1 index 9c7f00ab..82b5c14a 100644 --- a/functions/public/Invoke-WPFUIElements.ps1 +++ b/functions/public/Invoke-WPFUIElements.ps1 @@ -404,11 +404,16 @@ function Invoke-WPFUIElements { $textBlock.ToolTip = $entryInfo.Link $textBlock.Style = $HoverTextBlockStyle $textBlock.UseLayoutRounding = $true - + $textBlock.VerticalAlignment = "Center" $textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $textBlock.Tag = $checkBox + $textBlock.Add_MouseUp({ + [System.Object]$Sender = $args[0] + Start-Process $Sender.ToolTip -ErrorAction Stop + }) + $updateLinkMargin = { [System.Object]$Sender = $args[0] $linkedCheckBox = $Sender.Tag diff --git a/functions/public/Invoke-WPFUpdatesdefault.ps1 b/functions/public/Invoke-WPFUpdatesdefault.ps1 index dcd88982..8b250caa 100644 --- a/functions/public/Invoke-WPFUpdatesdefault.ps1 +++ b/functions/public/Invoke-WPFUpdatesdefault.ps1 @@ -5,22 +5,51 @@ function Invoke-WPFUpdatesdefault { Resets Windows Update settings to default #> - $ErrorActionPreference = 'SilentlyContinue' Write-WinUtilLog -Component "Updates" -Message "Resetting Windows Update settings to default." - Write-Host "Removing Windows Update policy settings..." -ForegroundColor Green - Write-WinUtilLog -Component "Updates" -Message "Removing Windows Update policy registry paths." + Write-Host "Removing Windows Update settings managed by WinUtil..." -ForegroundColor Green + 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 - Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization" -Recurse -Force - Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Recurse -Force - Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata" -Recurse -Force - Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching" -Recurse -Force - Remove-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Recurse -Force + $registryValues = @( + @{ + Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" + Names = @("NoAutoUpdate", "AUOptions", "NoAutoRebootWithLoggedOnUsers", "AUPowerManagement") + }, + @{ + 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..." - Write-WinUtilLog -Component "Updates" -Message "Showing Windows Update settings page." - Remove-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer -Name SettingsPageVisibility + foreach ($registryEntry in $registryValues) { + foreach ($valueName in $registryEntry.Names) { + 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-WinUtilLog -Component "Updates" -Message "Restoring Windows Update service startup types." @@ -35,12 +64,8 @@ function Invoke-WPFUpdatesdefault { Write-Host "Restored UsoSvc 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 - - Write-Host "Restored WaaSMedicSvc to Manual." - Write-WinUtilLog -Component "Updates" -Message "Restoring WaaSMedicSvc service to Manual." - Set-Service -Name WaaSMedicSvc -StartupType Manual + Start-Service -Name UsoSvc Write-Host "Enabling update related scheduled tasks..." -ForegroundColor Green Write-WinUtilLog -Component "Updates" -Message "Enabling update related scheduled tasks." @@ -54,13 +79,9 @@ function Invoke-WPFUpdatesdefault { '\Microsoft\WindowsUpdate\*' 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 "--- Windows Update Settings Reset to Default ---" -ForegroundColor Green Write-Host "===================================================" -ForegroundColor Green diff --git a/functions/public/Invoke-WPFUpdatesdisable.ps1 b/functions/public/Invoke-WPFUpdatesdisable.ps1 index d020d9b6..547bb9f0 100644 --- a/functions/public/Invoke-WPFUpdatesdisable.ps1 +++ b/functions/public/Invoke-WPFUpdatesdisable.ps1 @@ -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. #> - $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-Host "Configuring registry settings..." -ForegroundColor Yellow @@ -21,24 +31,14 @@ function Invoke-WPFUpdatesdisable { 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 - Write-Host "Hiding Windows Updates from settings..." - Write-WinUtilLog -Component "Updates" -Message "Hiding Windows Update settings page." - Set-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer -Name SettingsPageVisibility -Value hide:windowsupdate + foreach ($serviceName in @("BITS", "wuauserv", "UsoSvc")) { + Write-Host "Stopping and disabling $serviceName service." + 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." - 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 + Remove-Item -Path "C:\Windows\SoftwareDistribution\*" -Recurse -Force -ErrorAction SilentlyContinue Write-Host "Cleared SoftwareDistribution folder." Write-WinUtilLog -Component "Updates" -Message "Cleared SoftwareDistribution folder." @@ -54,11 +54,11 @@ function Invoke-WPFUpdatesdisable { '\Microsoft\WindowsUpdate\*' 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 "--- Updates Are Disabled ---" -ForegroundColor Green + Write-Host "--- Windows Update Is Disabled ---" -ForegroundColor Green Write-Host "=================================" -ForegroundColor Green Write-Host "Note: You must restart your system in order for all changes to take effect." -ForegroundColor Yellow diff --git a/functions/public/Invoke-WPFUpdatessecurity.ps1 b/functions/public/Invoke-WPFUpdatessecurity.ps1 index 5c5e97c6..09eaf202 100644 --- a/functions/public/Invoke-WPFUpdatessecurity.ps1 +++ b/functions/public/Invoke-WPFUpdatessecurity.ps1 @@ -6,10 +6,9 @@ function Invoke-WPFUpdatessecurity { .DESCRIPTION 1. Disables driver offering through Windows Update - 2. Disables Windows Update automatic restart - 3. Sets Windows Update to Semi-Annual Channel (Targeted) - 4. Defers feature updates for 365 days - 5. Defers quality updates for 4 days + 2. Defers feature updates for 365 days + 3. Defers quality updates for 4 days + 4. Prevents automatic restarts while a user is signed in #> @@ -17,6 +16,32 @@ function Invoke-WPFUpdatessecurity { Write-WinUtilLog -Component "Updates" -Message "Applying recommended Windows Update settings." 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 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 "DriverUpdateWizardWuSearchEnabled" -Type DWord -Value 0 - New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Force - Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -Name "ExcludeWUDriversInQualityUpdate" -Type DWord -Value 1 + New-Item -Path $windowsUpdatePolicyPath -Force + 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." - 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 - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferFeatureUpdatesPeriodInDays" -Type DWord -Value 365 - Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -Name "DeferQualityUpdatesPeriodInDays" -Type DWord -Value 4 + $legacySettingsPath = "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" + foreach ($legacyValue in @("BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays")) { + Remove-ItemProperty -Path $legacySettingsPath -Name $legacyValue -ErrorAction SilentlyContinue + } - Write-Host "Disabling Windows Update automatic restart..." - Write-WinUtilLog -Component "Updates" -Message "Disabling Windows Update automatic restart while users are logged in." + Write-Host "Preventing automatic restarts while users are signed 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 - Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoRebootWithLoggedOnUsers" -Type DWord -Value 1 - Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUPowerManagement" -Type DWord -Value 0 + New-Item -Path $automaticUpdatePolicyPath -Force + # NoAutoRebootWithLoggedOnUsers only applies when automatic updates use option 4. + 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 "-- Updates Set to Recommended ---" diff --git a/pester/lazy-tabs.Tests.ps1 b/pester/lazy-tabs.Tests.ps1 index cb9695e0..1593fbd6 100644 --- a/pester/lazy-tabs.Tests.ps1 +++ b/pester/lazy-tabs.Tests.ps1 @@ -113,4 +113,12 @@ Describe "Startup lazy tab wiring" { $rendererScript | Should -Match '\$sync\.Buttons\.Add\(\$button\.Name\)' $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"\)' + } } diff --git a/pester/oosu.Tests.ps1 b/pester/oosu.Tests.ps1 new file mode 100644 index 00000000..f04b2a88 --- /dev/null +++ b/pester/oosu.Tests.ps1 @@ -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 + } +} diff --git a/pester/update-profiles.Tests.ps1 b/pester/update-profiles.Tests.ps1 index 161b49f0..826b797e 100644 --- a/pester/update-profiles.Tests.ps1 +++ b/pester/update-profiles.Tests.ps1 @@ -11,8 +11,11 @@ BeforeAll { function Write-WinUtilLog { param($Message, $Level, $Component) } + function Show-WinUtilMessage { + param($Message, $Title, $Button, $Icon) + } function Get-ScheduledTask { - param($TaskPath) + param($TaskPath, $ErrorAction) } function Disable-ScheduledTask { param( @@ -51,6 +54,7 @@ Describe "Invoke-WPFUpdatesdisable" { BeforeEach { Mock Write-Host { } Mock Write-WinUtilLog { } + Mock Show-WinUtilMessage { "Yes" } Mock New-Item { } Mock Set-ItemProperty { } Mock Set-Service { } @@ -88,25 +92,23 @@ Describe "Invoke-WPFUpdatesdisable" { $Type -eq "DWord" -and $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" { 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 { $Name -eq "BITS" -and $StartupType -eq "Disabled" } Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { $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 { $Name -eq "UsoSvc" -and $StartupType -eq "Disabled" } @@ -128,6 +130,21 @@ Describe "Invoke-WPFUpdatesdisable" { } 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" { @@ -136,6 +153,11 @@ Describe "Invoke-WPFUpdatesdefault" { Mock Write-WinUtilLog { } Mock Remove-Item { } Mock Remove-ItemProperty { } + Mock Get-ItemProperty { + [pscustomobject]@{ + SettingsPageVisibility = "hide:windowsupdate" + } + } Mock Set-Service { } Mock Start-Service { } Mock Get-ScheduledTask { @@ -147,30 +169,49 @@ Describe "Invoke-WPFUpdatesdefault" { 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 - $expectedPaths = @( - "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", - "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization", - "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings", - "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata", - "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching", - "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" + $expectedRegistryValues = @( + @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU", "NoAutoUpdate", "AUOptions", "NoAutoRebootWithLoggedOnUsers", "AUPowerManagement"), + @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate", "ExcludeWUDriversInQualityUpdate", "DeferFeatureUpdates", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdates", "DeferQualityUpdatesPeriodInDays"), + @("HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings", "BranchReadinessLevel", "DeferFeatureUpdatesPeriodInDays", "DeferQualityUpdatesPeriodInDays"), + @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\Device Metadata", "PreventDeviceMetadataFromNetwork"), + @("HKLM:\SOFTWARE\Policies\Microsoft\Windows\DriverSearching", "DontPromptForWindowsUpdate", "DontSearchWindowsUpdate", "DriverUpdateWizardWuSearchEnabled"), + @("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config", "DODownloadMode") ) - foreach ($expectedRegistryPath in $expectedPaths) { - $expected = $expectedRegistryPath - Should -Invoke -CommandName Remove-Item -Times 1 -Exactly -ParameterFilter { - $Path -eq $expected -and $Recurse -eq $true -and $Force -eq $true + foreach ($expectedEntry in $expectedRegistryValues) { + $expectedPath = $expectedEntry[0] + foreach ($expectedName in $expectedEntry[1..($expectedEntry.Count - 1)]) { + $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 { $Path -eq "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer" -and $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" { Invoke-WPFUpdatesdefault @@ -186,12 +227,12 @@ Describe "Invoke-WPFUpdatesdefault" { Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { $Name -eq "UsoSvc" -and $StartupType -eq "Automatic" } - Should -Invoke -CommandName Set-Service -Times 1 -Exactly -ParameterFilter { - $Name -eq "WaaSMedicSvc" -and $StartupType -eq "Manual" + Should -Not -Invoke Set-Service -ParameterFilter { + $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 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 secedit -Times 1 -Exactly -ParameterFilter { - $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" - } + Should -Not -Invoke secedit } } @@ -217,6 +252,47 @@ Describe "Invoke-WPFUpdatessecurity" { Mock Write-WinUtilLog { } Mock New-Item { } 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" { @@ -261,23 +337,35 @@ Describe "Invoke-WPFUpdatessecurity" { Invoke-WPFUpdatessecurity Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter { - $Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and - $Name -eq "BranchReadinessLevel" -and + $Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -and + $Name -eq "DeferFeatureUpdates" -and $Type -eq "DWord" -and - $Value -eq 20 + $Value -eq 1 } 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 $Type -eq "DWord" -and $Value -eq 365 } 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 $Type -eq "DWord" -and $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 { $Path -eq "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -and $Name -eq "NoAutoRebootWithLoggedOnUsers" -and @@ -291,4 +379,16 @@ Describe "Invoke-WPFUpdatessecurity" { $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 + } + } + } } diff --git a/pester/xaml.Tests.ps1 b/pester/xaml.Tests.ps1 index cc6d60d9..e601fd38 100644 --- a/pester/xaml.Tests.ps1 +++ b/pester/xaml.Tests.ps1 @@ -152,7 +152,6 @@ Describe "XAML document" { "appspanel", "tweakspanel", "featurespanel", - "updatespanel", "appxpanel", "WPFstandard", "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" { $xamlNames = @(Get-WinUtilXamlRuntimeNamedControls | ForEach-Object { $_.Name }) $requiredControls = @( diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 121bd9ff..23f42f52 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -128,9 +128,6 @@ Invoke-WinutilThemeChange -theme $sync.preferences.theme $sync.InitializedTabs = @{} 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 #=========================================================================== @@ -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 - }) - } - - } } } diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index bb6eeecc..4cbaf4e2 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -1366,99 +1366,148 @@ - + - - + + + - - - - - - - + + + + - - - -