diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml index b17cd74f..a805041f 100644 --- a/.github/workflows/unittests.yaml +++ b/.github/workflows/unittests.yaml @@ -9,14 +9,22 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - name: lint - uses: devblackops/github-action-psscriptanalyzer@master - with: - sendComment: false - settingsPath: lint/PSScriptAnalyser.ps1 - failOnErrors: false - failOnWarnings: false - failOnInfos: false + - name: Install PSScriptAnalyzer + run: | + Set-PSRepository -Name PSGallery -InstallationPolicy Trusted + Install-Module -Name PSScriptAnalyzer -RequiredVersion 1.20.0 -Scope CurrentUser -Force + Import-Module PSScriptAnalyzer -RequiredVersion 1.20.0 -Force + Get-Module PSScriptAnalyzer | Select-Object Name, Version, Path + shell: pwsh + - name: Run PSScriptAnalyzer + run: | + $results = Invoke-ScriptAnalyzer -Path . -Settings ./lint/PSScriptAnalyser.ps1 -Recurse + if ($results) { + $results | Format-Table -AutoSize | Out-String -Width 4096 | Write-Host + } else { + Write-Host "PSScriptAnalyzer found no diagnostics." + } + shell: pwsh test: runs-on: windows-latest diff --git a/AGENTS.md b/AGENTS.md index 9518ac1a..d54ded13 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,4 +188,5 @@ When the user corrects an agent approach, add or tighten one concrete rule here - Keep package install/uninstall process launches simple unless explicitly requested; do not add a separate stdout/stderr process logging helper for winget or Chocolatey. - When the active log file is owned by `Start-Transcript`, do not call `Add-Content` against that file; write to host output so the transcript captures the line in the same log file without recording a terminating-error diagnostic. - Log install/uninstall package names and package-manager IDs before queuing background runspace work; do not rely on runspace host output for the package identity. -- Keep performance tracing helpers under `tools/perf`; include them in generated output only through `Compile.ps1 -Trace`. +- For Win11 Creator, start each new ISO modification in a fresh `WinUtil_Win11ISO_*` temp directory; existing-work detection is only for resuming/exporting already modified media. +- For Script Analyzer cleanup, fix actionable source warnings first and do not globally suppress accepted convention warnings such as plural names, `ShouldProcess` on UI helpers, `$global:sync`, or compile-time cross-file false positives. diff --git a/Compile.ps1 b/Compile.ps1 index 430e396d..51630a1b 100644 --- a/Compile.ps1 +++ b/Compile.ps1 @@ -1,27 +1,9 @@ param ( - [switch]$Run, - [switch]$Trace + [switch]$Run ) $OFS = "`r`n" -function Get-WinUtilTraceStrippedContent { - param( - [Parameter(Mandatory = $true)] - [string]$Content - ) - - if ($Trace) { - return $Content - } - - $filteredLines = $Content -split "`r?`n" | Where-Object { - $_ -notmatch '^\s*(Start-WinUtilPerformanceTrace|Stop-WinUtilPerformanceTrace|Write-WinUtilPerformanceCheckpoint)\b' - } - - $filteredLines -join "`r`n" -} - # Variable to sync between runspaces $sync = [Hashtable]::Synchronized(@{}) $sync.configs = @{} @@ -29,17 +11,7 @@ $sync.configs = @{} $script = (Get-Content -Path scripts\start.ps1) -replace '#{replaceme}', (Get-Date -Format 'yy.MM.dd') $script += Get-ChildItem -Path functions -Recurse -File | ForEach-Object { - Get-WinUtilTraceStrippedContent -Content (Get-Content -Path $_.FullName -Raw) -} - -if ($Trace) { - $script += Get-ChildItem -Path tools\perf -Filter *.ps1 -File | Get-Content -Raw - - $script += @' -$sync.PerformanceTraceEnabled = $true -Start-WinUtilPerformanceTrace -Write-WinUtilPerformanceCheckpoint -Name "Config load start" -'@ + Get-Content -Path $_.FullName -Raw } Get-ChildItem config | ForEach-Object { @@ -57,13 +29,6 @@ Get-ChildItem config | ForEach-Object { $sync.configs[$_.BaseName] = $obj $script += "`$sync.configs.$($_.BaseName) = @'`r`n$json`r`n'@ | ConvertFrom-Json" - if ($Trace) { - $script += "`r`nWrite-WinUtilPerformanceCheckpoint -Name `"Config $($_.BaseName) loaded`"" - } -} - -if ($Trace) { - $script += "`r`nWrite-WinUtilPerformanceCheckpoint -Name `"Config load complete`"" } $xaml = Get-Content -Path xaml\inputXML.xaml -Raw @@ -72,7 +37,7 @@ $script += "`$inputXML = @'`r`n$xaml`r`n'@" $autounattendXml = Get-Content -Path tools\autounattend.xml -Raw $script += "`$WinUtilAutounattendXml = @'`r`n$autounattendXml`r`n'@" -$script += Get-WinUtilTraceStrippedContent -Content (Get-Content -Path scripts\main.ps1 -Raw) +$script += Get-Content -Path scripts\main.ps1 -Raw Set-Content -Path winutil.ps1 -Value $script diff --git a/functions/private/Find-AppsByNameOrDescription.ps1 b/functions/private/Find-AppsByNameOrDescription.ps1 index a4da2b56..ec405789 100644 --- a/functions/private/Find-AppsByNameOrDescription.ps1 +++ b/functions/private/Find-AppsByNameOrDescription.ps1 @@ -83,9 +83,9 @@ function Find-AppsByNameOrDescription { $categoryLabel.Visibility = [Windows.Visibility]::Visible # Search through apps in this category - $wrapPanel.Children | ForEach-Object { + foreach ($appControl in $wrapPanel.Children) { # Safely retrieve app entry from hashtable - $appTag = $_.Tag + $appTag = $appControl.Tag $appEntry = $null if (-not [string]::IsNullOrWhiteSpace($appTag) -and $sync.configs.applicationsHashtable.ContainsKey($appTag)) { @@ -99,16 +99,16 @@ function Find-AppsByNameOrDescription { if ($contentMatch -or $descriptionMatch) { # Show the App and mark that this category has a match - $_.Visibility = [Windows.Visibility]::Visible + $appControl.Visibility = [Windows.Visibility]::Visible $categoryHasMatch = $true } else { - $_.Visibility = [Windows.Visibility]::Collapsed + $appControl.Visibility = [Windows.Visibility]::Collapsed } } else { # Hide app if no entry found (data integrity issue) - $_.Visibility = [Windows.Visibility]::Collapsed + $appControl.Visibility = [Windows.Visibility]::Collapsed } } diff --git a/functions/private/Find-TweaksByNameOrDescription.ps1 b/functions/private/Find-TweaksByNameOrDescription.ps1 index 66af5753..9ca884b4 100644 --- a/functions/private/Find-TweaksByNameOrDescription.ps1 +++ b/functions/private/Find-TweaksByNameOrDescription.ps1 @@ -114,6 +114,7 @@ function Find-TweaksByNameOrDescription { } catch { # Silent catch - UI element may be disposed + $null = $_ } return @@ -303,5 +304,6 @@ function Find-TweaksByNameOrDescription { catch { # Silent catch - UI elements may be disposed or in unexpected state # Do not log to terminal as this function is called on every keystroke + $null = $_ } } diff --git a/functions/private/Get-WinUtilSelectedPackages.ps1 b/functions/private/Get-WinUtilSelectedPackages.ps1 index dae05524..780bb71b 100644 --- a/functions/private/Get-WinUtilSelectedPackages.ps1 +++ b/functions/private/Get-WinUtilSelectedPackages.ps1 @@ -3,7 +3,7 @@ function Get-WinUtilSelectedPackages { param( [Parameter(Mandatory = $true)] [object] $PackageList, - + [Parameter(Mandatory = $true)] [PackageManagers] $Preference ) @@ -14,12 +14,12 @@ function Get-WinUtilSelectedPackages { Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" } } - $packages = [System.Collections.Hashtable]::new() $packagesWinget = [System.Collections.ArrayList]::new() $packagesChoco = [System.Collections.ArrayList]::new() - - $packages[[PackageManagers]::Winget] = $packagesWinget - $packages[[PackageManagers]::Choco] = $packagesChoco + $packages = @{ + ([PackageManagers]::Winget) = $packagesWinget + ([PackageManagers]::Choco) = $packagesChoco + } function Add-PackageId { param( diff --git a/functions/private/Get-WinUtilVariables.ps1 b/functions/private/Get-WinUtilVariables.ps1 index 17d25393..8a416661 100644 --- a/functions/private/Get-WinUtilVariables.ps1 +++ b/functions/private/Get-WinUtilVariables.ps1 @@ -19,8 +19,9 @@ function Get-WinUtilVariables { if ($Type -contains $objType) { Write-Output $psitem } - } catch { - <#I am here so errors don't get outputted for a couple variables that don't have the .GetType() attribute#> + } + catch { + $null = $_ } } return $output diff --git a/functions/private/Initialize-WinUtilRunspacePool.ps1 b/functions/private/Initialize-WinUtilRunspacePool.ps1 index 8cf56c90..d9f5b248 100644 --- a/functions/private/Initialize-WinUtilRunspacePool.ps1 +++ b/functions/private/Initialize-WinUtilRunspacePool.ps1 @@ -34,6 +34,5 @@ function Initialize-WinUtilRunspacePool { ) $sync.runspace.Open() - Write-WinUtilPerformanceCheckpoint -Name "Runspace pool initialized" return $sync.runspace } diff --git a/functions/private/Initialize-WinUtilTabContent.ps1 b/functions/private/Initialize-WinUtilTabContent.ps1 index 0ac4093f..f223a105 100644 --- a/functions/private/Initialize-WinUtilTabContent.ps1 +++ b/functions/private/Initialize-WinUtilTabContent.ps1 @@ -16,28 +16,22 @@ function Initialize-WinUtilTabContent { "Install" { Invoke-WPFUIElements -configVariable $sync.configs.appnavigation -targetGridName "appscategory" -columncount 1 Initialize-WPFUI -targetGridName "appscategory" - Write-WinUtilPerformanceCheckpoint -Name "App navigation UI created" Initialize-WPFUI -targetGridName "appspanel" - Write-WinUtilPerformanceCheckpoint -Name "Install UI created" } "Tweaks" { Invoke-WPFUIElements -configVariable $sync.configs.tweaks -targetGridName "tweakspanel" -columncount 2 - Write-WinUtilPerformanceCheckpoint -Name "Tweaks UI created" } "Config" { Invoke-WPFUIElements -configVariable $sync.configs.feature -targetGridName "featurespanel" -columncount 2 - Write-WinUtilPerformanceCheckpoint -Name "Features UI created" } "AppX" { Invoke-WPFUIElements -configVariable $sync.configs.appx -targetGridName "appxpanel" -columncount 2 - Write-WinUtilPerformanceCheckpoint -Name "AppX UI created" } "Win11 Creator" { if ($sync.Form -and $sync.Form.Dispatcher) { $sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Invoke-WinUtilISOCheckExistingWork }) | Out-Null } - Write-WinUtilPerformanceCheckpoint -Name "Win11 ISO tab initialized" } } diff --git a/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 b/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 index 6c17dd6c..8c2365de 100644 --- a/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 +++ b/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 @@ -6,16 +6,13 @@ function Initialize-WinUtilTaskbarOverlayAssets { if ($IncludeLogo -and -not $sync["logorender"]) { $sync["logorender"] = (Invoke-WinUtilAssets -Type "Logo" -Size 90 -Render) - Write-WinUtilPerformanceCheckpoint -Name "Taskbar logo asset rendered" } if ($IncludeStatusAssets -and -not $sync["checkmarkrender"]) { $sync["checkmarkrender"] = (Invoke-WinUtilAssets -Type "checkmark" -Size 512 -Render) - Write-WinUtilPerformanceCheckpoint -Name "Taskbar checkmark asset rendered" } if ($IncludeStatusAssets -and -not $sync["warningrender"]) { $sync["warningrender"] = (Invoke-WinUtilAssets -Type "warning" -Size 512 -Render) - Write-WinUtilPerformanceCheckpoint -Name "Taskbar warning asset rendered" } } diff --git a/functions/private/Install-WinUtilChoco.ps1 b/functions/private/Install-WinUtilChoco.ps1 index dc2d4f15..1590aec8 100644 --- a/functions/private/Install-WinUtilChoco.ps1 +++ b/functions/private/Install-WinUtilChoco.ps1 @@ -11,5 +11,6 @@ function Install-WinUtilChoco { } Write-Host "Chocolatey is not installed. Installing now..." - Invoke-WebRequest -Uri https://community.chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression + $installScript = Invoke-WebRequest -Uri https://community.chocolatey.org/install.ps1 -UseBasicParsing + Invoke-Command -ScriptBlock ([scriptblock]::Create($installScript.Content)) } diff --git a/functions/private/Invoke-WinUtilCurrentSystem.ps1 b/functions/private/Invoke-WinUtilCurrentSystem.ps1 index 57b585d8..12239d55 100644 --- a/functions/private/Invoke-WinUtilCurrentSystem.ps1 +++ b/functions/private/Invoke-WinUtilCurrentSystem.ps1 @@ -55,8 +55,6 @@ Function Invoke-WinUtilCurrentSystem { $entry = $sync.configs.tweaks.$Config $registryKeys = $entry.registry $serviceKeys = $entry.service - $appxKeys = $entry.appx - $invokeScript = $entry.InvokeScript $entryType = $entry.Type if ($registryKeys -or $serviceKeys) { diff --git a/functions/private/Invoke-WinUtilISO.ps1 b/functions/private/Invoke-WinUtilISO.ps1 index e05a7d3f..d63c74bf 100644 --- a/functions/private/Invoke-WinUtilISO.ps1 +++ b/functions/private/Invoke-WinUtilISO.ps1 @@ -1,12 +1,13 @@ function Write-Win11ISOLog { param([string]$Message) $ts = (Get-Date).ToString("HH:mm:ss") + $logLine = "[$ts] $Message" $sync["WPFWin11ISOStatusLog"].Dispatcher.Invoke([action]{ $current = $sync["WPFWin11ISOStatusLog"].Text if ($current -eq "Ready. Please select a Windows 11 ISO to begin.") { - $sync["WPFWin11ISOStatusLog"].Text = "[$ts] $Message" + $sync["WPFWin11ISOStatusLog"].Text = $logLine } else { - $sync["WPFWin11ISOStatusLog"].Text += "`n[$ts] $Message" + $sync["WPFWin11ISOStatusLog"].Text += "`n$logLine" } $sync["WPFWin11ISOStatusLog"].CaretIndex = $sync["WPFWin11ISOStatusLog"].Text.Length $sync["WPFWin11ISOStatusLog"].ScrollToEnd() @@ -151,14 +152,9 @@ function Invoke-WinUtilISOModify { $sync["WPFWin11ISOModifyButton"].IsEnabled = $false $sync["Win11ISOModifying"] = $true - $existingWorkDir = Get-Item -Path (Join-Path $env:TEMP "WinUtil_Win11ISO*") | - Where-Object { $_.PSIsContainer } | Sort-Object LastWriteTime -Descending | Select-Object -First 1 - - $workDir = if ($existingWorkDir) { - Write-Win11ISOLog "Reusing existing temp directory: $($existingWorkDir.FullName)" - $existingWorkDir.FullName - } else { - 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) { + $workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format 'yyyyMMdd_HHmmss')_$(([guid]::NewGuid()).ToString('N').Substring(0, 8))" } $autounattendContent = if ($WinUtilAutounattendXml) { @@ -293,9 +289,11 @@ function Invoke-WinUtilISOModify { [scriptblock]$Logger ) + $metadataLogger = $Logger + function LogMeta([string]$Message) { - if ($Logger) { - $null = $Logger.Invoke($Message) + if ($metadataLogger) { + $null = $metadataLogger.Invoke($Message) } } @@ -368,8 +366,11 @@ function Invoke-WinUtilISOModify { Log "ISO contents copied." SetProgress "Mounting install.wim..." 25 - $localWim = Join-Path $isoContents "sources\install.wim" - if (-not (Test-Path $localWim)) { $localWim = Join-Path $isoContents "sources\install.esd" } + $sourceImageFileName = Split-Path $wimPath -Leaf + $localWim = Join-Path $isoContents "sources\$sourceImageFileName" + if (-not (Test-Path $localWim)) { + throw "Copied ISO image file not found: sources\$sourceImageFileName" + } Set-ItemProperty -Path $localWim -Name IsReadOnly -Value $false Log "Mounting install.wim (Index ${selectedWimIndex}: $selectedEditionName) at $mountDir..." @@ -590,10 +591,10 @@ function Invoke-WinUtilISOCleanAndReset { } foreach ($d in $allDirs) { - try { Remove-Item -Path $d.FullName -Force } catch {} + try { Remove-Item -Path $d.FullName -Force } catch { Log "WARNING: could not delete $($d.FullName): $_" } } - try { Remove-Item -Path $workDir -Recurse -Force } catch {} + try { Remove-Item -Path $workDir -Recurse -Force } catch { Log "WARNING: could not delete temp directory ${workDir}: $_" } if (Test-Path $workDir) { Log "WARNING: some items could not be deleted in $workDir" diff --git a/functions/private/Invoke-WinUtilISOScript.ps1 b/functions/private/Invoke-WinUtilISOScript.ps1 index cec6f0a7..fb2bb205 100644 --- a/functions/private/Invoke-WinUtilISOScript.ps1 +++ b/functions/private/Invoke-WinUtilISOScript.ps1 @@ -70,15 +70,11 @@ function Invoke-WinUtilISOScript { [int]$InstallImageIndex = 1, [scriptblock]$Log = { param($m) Write-Output $m } ) - - $adminSID = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544') - $adminGroup = $adminSID.Translate([System.Security.Principal.NTAccount]) - function Set-ISOScriptReg { - param ([string]$path, [string]$name, [string]$type, [string]$value) + param ([string]$Path, [string]$Name, [string]$Type, [string]$Value) try { - & reg add $path /v $name /t $type /d $value /f - & $Log "Set registry value: $path\$name" + & reg add $Path /v $Name /t $Type /d $Value /f + & $Log "Set registry value: $Path\$Name" } catch { & $Log "Error setting registry value: $_" } @@ -114,7 +110,7 @@ function Invoke-WinUtilISOScript { & $Logger "boot.wim driver injection complete." } catch { & $Logger "Warning: boot.wim driver injection failed: $_" - try { Dismount-WindowsImage -Path $mountDir -Discard } catch {} + try { Dismount-WindowsImage -Path $mountDir -Discard } catch { & $Logger "Warning: could not discard boot.wim mount: $_" } } finally { Remove-Item -Path $mountDir -Recurse -Force } @@ -330,44 +326,44 @@ Retail reg load HKLM\zSYSTEM "$ScratchDir\Windows\System32\config\SYSTEM" & $Log "Bypassing system requirements..." - Set-ISOScriptReg 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV1' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' 'SV2' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassCPUCheck' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassRAMCheck' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassSecureBootCheck' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassStorageCheck' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\LabConfig' 'BypassTPMCheck' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSYSTEM\Setup\MoSetup' 'AllowUpgradesWithUnsupportedTPMOrCPU' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' -Name 'SV1' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' -Name 'SV2' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' -Name 'SV1' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Control Panel\UnsupportedHardwareNotificationCache' -Name 'SV2' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\Setup\LabConfig' -Name 'BypassCPUCheck' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\Setup\LabConfig' -Name 'BypassRAMCheck' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\Setup\LabConfig' -Name 'BypassSecureBootCheck' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\Setup\LabConfig' -Name 'BypassStorageCheck' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\Setup\LabConfig' -Name 'BypassTPMCheck' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\Setup\MoSetup' -Name 'AllowUpgradesWithUnsupportedTPMOrCPU' -Type 'REG_DWORD' -Value '1' & $Log "Disabling sponsored apps..." - Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'OemPreInstalledAppsEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'PreInstalledAppsEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SilentInstalledAppsEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableWindowsConsumerFeatures' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'ContentDeliveryAllowed' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\PolicyManager\current\device\Start' 'ConfigureStartPins' 'REG_SZ' '{"pinnedList": [{}]}' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'FeatureManagementEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'PreInstalledAppsEverEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SoftLandingEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContentEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-310093Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338388Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338389Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-338393Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-353694Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SubscribedContent-353696Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' 'SystemPaneSuggestionsEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\PushToInstall' 'DisablePushToInstall' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\MRT' 'DontOfferThroughWUAU' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'OemPreInstalledAppsEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'PreInstalledAppsEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SilentInstalledAppsEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableWindowsConsumerFeatures' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'ContentDeliveryAllowed' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\PolicyManager\current\device\Start' -Name 'ConfigureStartPins' -Type 'REG_SZ' -Value '{"pinnedList": [{}]}' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'FeatureManagementEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'PreInstalledAppsEverEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SoftLandingEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContentEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-310093Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-338388Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-338389Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-338393Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-353694Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SubscribedContent-353696Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager' -Name 'SystemPaneSuggestionsEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\PushToInstall' -Name 'DisablePushToInstall' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\MRT' -Name 'DontOfferThroughWUAU' -Type 'REG_DWORD' -Value '1' Remove-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\Subscriptions' Remove-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager\SuggestedApps' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableConsumerAccountStateContent' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' 'DisableCloudOptimizedContent' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableConsumerAccountStateContent' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableCloudOptimizedContent' -Type 'REG_DWORD' -Value '1' & $Log "Enabling local accounts on OOBE..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' 'BypassNRO' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' -Name 'BypassNRO' -Type 'REG_DWORD' -Value '1' if ($AutoUnattendXml) { $preparedAutoUnattendXml = $AutoUnattendXml @@ -423,62 +419,62 @@ Retail } & $Log "Disabling reserved storage..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' 'ShippedWithReserves' 'REG_DWORD' '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' -Name 'ShippedWithReserves' -Type 'REG_DWORD' -Value '0' & $Log "Disabling BitLocker device encryption..." - Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Control\BitLocker' 'PreventDeviceEncryption' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\ControlSet001\Control\BitLocker' -Name 'PreventDeviceEncryption' -Type 'REG_DWORD' -Value '1' & $Log "Disabling Chat icon..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Chat' 'ChatIcon' 'REG_DWORD' '3' - Set-ISOScriptReg 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced' 'TaskbarMn' 'REG_DWORD' '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Chat' -Name 'ChatIcon' -Type 'REG_DWORD' -Value '3' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name 'TaskbarMn' -Type 'REG_DWORD' -Value '0' & $Log "Disabling OneDrive folder backup..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\OneDrive' 'DisableFileSyncNGSC' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\OneDrive' -Name 'DisableFileSyncNGSC' -Type 'REG_DWORD' -Value '1' & $Log "Disabling telemetry..." - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo' 'Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\Privacy' 'TailoredExperiencesWithDiagnosticDataEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' 'HasAccepted' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Input\TIPC' 'Enabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' 'RestrictImplicitInkCollection' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' 'RestrictImplicitTextCollection' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization\TrainedDataStore' 'HarvestContacts' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zNTUSER\Software\Microsoft\Personalization\Settings' 'AcceptedPrivacyPolicy' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\DataCollection' 'AllowTelemetry' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\dmwappushservice' 'Start' 'REG_DWORD' '4' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\AdvertisingInfo' -Name 'Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Windows\CurrentVersion\Privacy' -Name 'TailoredExperiencesWithDiagnosticDataEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' -Name 'HasAccepted' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Input\TIPC' -Name 'Enabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' -Name 'RestrictImplicitInkCollection' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization' -Name 'RestrictImplicitTextCollection' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\InputPersonalization\TrainedDataStore' -Name 'HarvestContacts' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zNTUSER\Software\Microsoft\Personalization\Settings' -Name 'AcceptedPrivacyPolicy' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name 'AllowTelemetry' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\ControlSet001\Services\dmwappushservice' -Name 'Start' -Type 'REG_DWORD' -Value '4' & $Log "Preventing installation of DevHome and Outlook..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate' 'workCompleted' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate' 'workCompleted' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\DevHomeUpdate' 'workCompleted' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate' -Name 'workCompleted' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate' -Name 'workCompleted' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\DevHomeUpdate' -Name 'workCompleted' -Type 'REG_DWORD' -Value '1' Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate' Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate' & $Log "Disabling Copilot..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' 'TurnOffWindowsCopilot' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' 'HubsSidebarEnabled' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' 'DisableSearchBoxSuggestions' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsCopilot' -Name 'TurnOffWindowsCopilot' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Edge' -Name 'HubsSidebarEnabled' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Explorer' -Name 'DisableSearchBoxSuggestions' -Type 'REG_DWORD' -Value '1' & $Log "Disabling Windows Update during OOBE (re-enabled on first logon via FirstLogon.ps1)..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'NoAutoUpdate' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'AUOptions' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' 'UseWUServer' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'DisableWindowsUpdateAccess' 'REG_DWORD' '1' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'WUServer' 'REG_SZ' 'http://localhost:8080' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' 'WUStatusServer' 'REG_SZ' 'http://localhost:8080' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\WindowsUpdate' 'workCompleted' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name 'NoAutoUpdate' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name 'AUOptions' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name 'UseWUServer' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Name 'DisableWindowsUpdateAccess' -Type 'REG_DWORD' -Value '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Name 'WUServer' -Type 'REG_SZ' -Value 'http://localhost:8080' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Name 'WUStatusServer' -Type 'REG_SZ' -Value 'http://localhost:8080' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\WindowsUpdate' -Name 'workCompleted' -Type 'REG_DWORD' -Value '1' Remove-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\WindowsUpdate' - Set-ISOScriptReg 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config' 'DODownloadMode' 'REG_DWORD' '0' - Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\BITS' 'Start' 'REG_DWORD' '4' - Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\wuauserv' 'Start' 'REG_DWORD' '4' - Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\UsoSvc' 'Start' 'REG_DWORD' '4' - Set-ISOScriptReg 'HKLM\zSYSTEM\ControlSet001\Services\WaaSMedicSvc' 'Start' 'REG_DWORD' '4' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config' -Name 'DODownloadMode' -Type 'REG_DWORD' -Value '0' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\ControlSet001\Services\BITS' -Name 'Start' -Type 'REG_DWORD' -Value '4' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\ControlSet001\Services\wuauserv' -Name 'Start' -Type 'REG_DWORD' -Value '4' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\ControlSet001\Services\UsoSvc' -Name 'Start' -Type 'REG_DWORD' -Value '4' + Set-ISOScriptReg -Path 'HKLM\zSYSTEM\ControlSet001\Services\WaaSMedicSvc' -Name 'Start' -Type 'REG_DWORD' -Value '4' & $Log "Preventing installation of Teams..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Teams' 'DisableInstallation' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Teams' -Name 'DisableInstallation' -Type 'REG_DWORD' -Value '1' & $Log "Preventing installation of new Outlook..." - Set-ISOScriptReg 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Mail' 'PreventRun' 'REG_DWORD' '1' + Set-ISOScriptReg -Path 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\Windows Mail' -Name 'PreventRun' -Type 'REG_DWORD' -Value '1' & $Log "Unloading offline registry hives..." reg unload HKLM\zCOMPONENTS diff --git a/functions/private/Invoke-WinUtilISOUSB.ps1 b/functions/private/Invoke-WinUtilISOUSB.ps1 index f64c205b..6ebc157d 100644 --- a/functions/private/Invoke-WinUtilISOUSB.ps1 +++ b/functions/private/Invoke-WinUtilISOUSB.ps1 @@ -180,7 +180,7 @@ function Invoke-WinUtilISOWriteUSB { Start-Sleep -Seconds 2 Update-Disk -Number $diskNum - try { Remove-PartitionAccessPath -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -AccessPath "$($winpePart.DriveLetter):" } catch {} + try { Remove-PartitionAccessPath -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -AccessPath "$($winpePart.DriveLetter):" } catch { Log "Warning: could not remove existing partition access path: $_" } $usbLetter = Get-FreeDriveLetter if (-not $usbLetter) { throw "No free drive letters (D-Z) available to assign to the USB data partition." } Set-Partition -DiskNumber $diskNum -PartitionNumber $winpePart.PartitionNumber -NewDriveLetter $usbLetter diff --git a/functions/private/Invoke-WinUtilSponsors.ps1 b/functions/private/Invoke-WinUtilSponsors.ps1 index 5ec3fe8f..f9ee9413 100644 --- a/functions/private/Invoke-WinUtilSponsors.ps1 +++ b/functions/private/Invoke-WinUtilSponsors.ps1 @@ -1,4 +1,4 @@ Function Invoke-WinUtilSponsors { - $sponsors = ([regex]::Matches(([regex]::Match((irm https://github.com/sponsors/ChrisTitusTech),'(?s)(?<=Current sponsors).*?(?=Past sponsors)')).Value,'(?<=alt="@)[^"]+')).Value | Where-Object {$_ -ne "ChrisTitusTech"} + $sponsors = ([regex]::Matches(([regex]::Match((Invoke-RestMethod https://github.com/sponsors/ChrisTitusTech),'(?s)(?<=Current sponsors).*?(?=Past sponsors)')).Value,'(?<=alt="@)[^"]+')).Value | Where-Object {$_ -ne "ChrisTitusTech"} return $sponsors } diff --git a/functions/private/Invoke-WinutilThemeChange.ps1 b/functions/private/Invoke-WinutilThemeChange.ps1 index 466358d5..aca44b32 100644 --- a/functions/private/Invoke-WinutilThemeChange.ps1 +++ b/functions/private/Invoke-WinutilThemeChange.ps1 @@ -91,34 +91,34 @@ function Invoke-WinutilThemeChange { # Retrieve all theme properties from the theme configuration $themeProperties = $sync.configs.themes.$currentTheme.PSObject.Properties - foreach ($_ in $themeProperties) { + foreach ($themeProperty in $themeProperties) { # Apply properties that deal with colors - if ($_.Name -like "*color*") { - Set-ThemeResourceProperty -Name $_.Name -Value $_.Value -Type "ColorBrush" + if ($themeProperty.Name -like "*color*") { + Set-ThemeResourceProperty -Name $themeProperty.Name -Value $themeProperty.Value -Type "ColorBrush" # For certain color properties, also set complementary values (e.g., BorderColor -> CBorderColor) This is required because e.g DropShadowEffect requires a and not a object - if ($_.Name -in @("BorderColor", "ButtonBackgroundMouseoverColor")) { - Set-ThemeResourceProperty -Name "C$($_.Name)" -Value $_.Value -Type "Color" + if ($themeProperty.Name -in @("BorderColor", "ButtonBackgroundMouseoverColor")) { + Set-ThemeResourceProperty -Name "C$($themeProperty.Name)" -Value $themeProperty.Value -Type "Color" } } # Apply corner radius properties - elseif ($_.Name -like "*Radius*") { - Set-ThemeResourceProperty -Name $_.Name -Value $_.Value -Type "CornerRadius" + elseif ($themeProperty.Name -like "*Radius*") { + Set-ThemeResourceProperty -Name $themeProperty.Name -Value $themeProperty.Value -Type "CornerRadius" } # Apply row height properties - elseif ($_.Name -like "*RowHeight*") { - Set-ThemeResourceProperty -Name $_.Name -Value $_.Value -Type "GridLength" + elseif ($themeProperty.Name -like "*RowHeight*") { + Set-ThemeResourceProperty -Name $themeProperty.Name -Value $themeProperty.Value -Type "GridLength" } # Apply thickness or margin properties - elseif (($_.Name -like "*Thickness*") -or ($_.Name -like "*margin")) { - Set-ThemeResourceProperty -Name $_.Name -Value $_.Value -Type "Thickness" + elseif (($themeProperty.Name -like "*Thickness*") -or ($themeProperty.Name -like "*margin")) { + Set-ThemeResourceProperty -Name $themeProperty.Name -Value $themeProperty.Value -Type "Thickness" } # Apply font family properties - elseif ($_.Name -like "*FontFamily*") { - Set-ThemeResourceProperty -Name $_.Name -Value $_.Value -Type "FontFamily" + elseif ($themeProperty.Name -like "*FontFamily*") { + Set-ThemeResourceProperty -Name $themeProperty.Name -Value $themeProperty.Value -Type "FontFamily" } # Apply any other properties as doubles (numerical values) else { - Set-ThemeResourceProperty -Name $_.Name -Value $_.Value -Type "Double" + Set-ThemeResourceProperty -Name $themeProperty.Name -Value $themeProperty.Value -Type "Double" } } } diff --git a/functions/private/Reset-WPFCheckBoxes.ps1 b/functions/private/Reset-WPFCheckBoxes.ps1 index 0f49f4d9..855bee3f 100644 --- a/functions/private/Reset-WPFCheckBoxes.ps1 +++ b/functions/private/Reset-WPFCheckBoxes.ps1 @@ -22,7 +22,11 @@ function Reset-WPFCheckBoxes { ) $CheckBoxesToCheck = $sync.selectedApps + $sync.selectedTweaks + $sync.selectedFeatures + $sync.selectedAppx - $CheckBoxes = ($sync.GetEnumerator()).where{ $_.Value -is [System.Windows.Controls.CheckBox] -and $_.Name -notlike "WPFToggle*" -and $_.Name -like "$checkboxfilterpattern"} + $CheckBoxes = foreach ($syncEntry in $sync.GetEnumerator()) { + if ($syncEntry.Value -is [System.Windows.Controls.CheckBox] -and $syncEntry.Name -notlike "WPFToggle*" -and $syncEntry.Name -like $checkboxfilterpattern) { + $syncEntry + } + } foreach ($CheckBox in $CheckBoxes) { $checkboxName = $CheckBox.Key diff --git a/functions/private/Set-WinUtilProgressbar.ps1 b/functions/private/Set-WinUtilProgressbar.ps1 index 9ea815e2..47097b27 100644 --- a/functions/private/Set-WinUtilProgressbar.ps1 +++ b/functions/private/Set-WinUtilProgressbar.ps1 @@ -14,11 +14,13 @@ function Set-WinUtilProgressbar{ [int]$Percent ) - Invoke-WPFUIThread -ScriptBlock {$sync.progressBarTextBlock.Text = $label} - Invoke-WPFUIThread -ScriptBlock {$sync.progressBarTextBlock.ToolTip = $label} - if ($percent -lt 5 ) { - $percent = 5 # Ensure the progress bar is not empty, as it looks weird + $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} + Invoke-WPFUIThread -ScriptBlock { $sync.ProgressBar.Value = $Percent} } diff --git a/functions/private/Show-CustomDialog.ps1 b/functions/private/Show-CustomDialog.ps1 index 560891d3..9ae289e6 100644 --- a/functions/private/Show-CustomDialog.ps1 +++ b/functions/private/Show-CustomDialog.ps1 @@ -193,6 +193,7 @@ function Show-CustomDialog { # Define the Regex to find hyperlinks formatted as HTML tags $regex = [regex]::new('([^<]+)') $lastPos = 0 + $linkHoverBrush = $LinkHoverForegroundColor # Iterate through each match and add regular text and hyperlinks foreach ($match in $regex.Matches($Message)) { @@ -210,20 +211,23 @@ function Show-CustomDialog { $hyperlink.Foreground = $LinkForegroundColor $hyperlink.Add_Click({ - param($sender, $args) - Start-Process $sender.NavigateUri.AbsoluteUri + param($eventSender, $routedEvent) + $null = $routedEvent + Start-Process $eventSender.NavigateUri.AbsoluteUri }) $hyperlink.Add_MouseEnter({ - param($sender, $args) - $sender.Foreground = $LinkHoverForegroundColor - $sender.FontSize = ($FontSize + ($FontSize / 4)) - $sender.FontWeight = "SemiBold" + param($eventSender, $routedEvent) + $null = $routedEvent + $eventSender.Foreground = $linkHoverBrush + $eventSender.FontSize = ($FontSize + ($FontSize / 4)) + $eventSender.FontWeight = "SemiBold" }) $hyperlink.Add_MouseLeave({ - param($sender, $args) - $sender.Foreground = $LinkForegroundColor - $sender.FontSize = $FontSize - $sender.FontWeight = "Normal" + param($eventSender, $routedEvent) + $null = $routedEvent + $eventSender.Foreground = $LinkForegroundColor + $eventSender.FontSize = $FontSize + $eventSender.FontWeight = "Normal" }) $messageTextBlock.Inlines.Add($hyperlink) diff --git a/functions/private/Show-WPFInstallAppBusy.ps1 b/functions/private/Show-WPFInstallAppBusy.ps1 index 94d0c0f9..20211f79 100644 --- a/functions/private/Show-WPFInstallAppBusy.ps1 +++ b/functions/private/Show-WPFInstallAppBusy.ps1 @@ -10,11 +10,13 @@ function Show-WPFInstallAppBusy { 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 = $text + $sync.InstallAppAreaOverlayText.Text = $overlayText $sync.InstallAppAreaBorder.IsEnabled = $false $sync.InstallAppAreaScrollViewer.Effect.Radius = 5 } diff --git a/functions/private/Start-WinUtilInstallAppRendering.ps1 b/functions/private/Start-WinUtilInstallAppRendering.ps1 index afface44..2eed3844 100644 --- a/functions/private/Start-WinUtilInstallAppRendering.ps1 +++ b/functions/private/Start-WinUtilInstallAppRendering.ps1 @@ -15,7 +15,6 @@ function Invoke-WinUtilInstallAppRenderBatch { function Complete-WinUtilInstallAppRendering { $sync.InstallAppEntriesRendered = $true - Write-WinUtilPerformanceCheckpoint -Name "Install app entries rendered" } function Invoke-WinUtilInstallAppRenderNextBatch { diff --git a/functions/public/Invoke-WPFAppxRemoval.ps1 b/functions/public/Invoke-WPFAppxRemoval.ps1 index 074a30df..7facc196 100644 --- a/functions/public/Invoke-WPFAppxRemoval.ps1 +++ b/functions/public/Invoke-WPFAppxRemoval.ps1 @@ -7,7 +7,7 @@ function Invoke-WPFAppxRemoval { $selected = $sync.selectedAppx $apps = $sync.configs.appxHashtable - $handle = Invoke-WPFRunspace -ParameterList @(("selected", $selected), ("apps", $apps)) -ScriptBlock { + Invoke-WPFRunspace -ParameterList @(("selected", $selected), ("apps", $apps)) -ScriptBlock { param($selected, $apps) $sync.ProcessRunning = $true diff --git a/functions/public/Invoke-WPFButton.ps1 b/functions/public/Invoke-WPFButton.ps1 index 454cce28..9b76f8f7 100644 --- a/functions/public/Invoke-WPFButton.ps1 +++ b/functions/public/Invoke-WPFButton.ps1 @@ -35,7 +35,7 @@ function Invoke-WPFButton { if ($buttonConfig.InvokeScript -and $buttonConfig.InvokeScript.Count -gt 0) { foreach ($script in $buttonConfig.InvokeScript) { if (-not [string]::IsNullOrWhiteSpace($script)) { - Invoke-Expression $script + Invoke-Command -ScriptBlock ([scriptblock]::Create($script)) -ErrorAction Stop } } return diff --git a/functions/public/Invoke-WPFFeatureInstall.ps1 b/functions/public/Invoke-WPFFeatureInstall.ps1 index 958ba434..6efdc235 100644 --- a/functions/public/Invoke-WPFFeatureInstall.ps1 +++ b/functions/public/Invoke-WPFFeatureInstall.ps1 @@ -12,7 +12,7 @@ function Invoke-WPFFeatureInstall { return } - $handle = Invoke-WPFRunspace -ScriptBlock { + Invoke-WPFRunspace -ScriptBlock { $Features = $sync.selectedFeatures $sync.ProcessRunning = $true if ($Features.count -eq 1) { @@ -26,7 +26,7 @@ function Invoke-WPFFeatureInstall { $Features | ForEach-Object { Invoke-WinUtilFeatureInstall $_ $X++ - Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($x/$CheckBox.Count) } + Invoke-WPFUIThread -ScriptBlock { Set-WinUtilTaskbaritem -value ($x/$Features.Count) } } $sync.ProcessRunning = $false diff --git a/functions/public/Invoke-WPFInstall.ps1 b/functions/public/Invoke-WPFInstall.ps1 index d7a70cb2..243bc8de 100644 --- a/functions/public/Invoke-WPFInstall.ps1 +++ b/functions/public/Invoke-WPFInstall.ps1 @@ -24,7 +24,7 @@ function Invoke-WPFInstall { $packageSummary = Get-WinUtilPackageLogSummary -Packages $PackagesToInstall -Preference $ManagerPreference Write-WinUtilLog -Component "Install" -Message "Install selected package(s): $($packageSummary -join '; ')" - $handle = Invoke-WPFRunspace -ParameterList @(("PackagesToInstall", $PackagesToInstall),("ManagerPreference", $ManagerPreference)) -ScriptBlock { + Invoke-WPFRunspace -ParameterList @(("PackagesToInstall", $PackagesToInstall),("ManagerPreference", $ManagerPreference)) -ScriptBlock { param($PackagesToInstall, $ManagerPreference) $packagesSorted = Get-WinUtilSelectedPackages -PackageList $PackagesToInstall -Preference $ManagerPreference diff --git a/functions/public/Invoke-WPFRunspace.ps1 b/functions/public/Invoke-WPFRunspace.ps1 index 39c25d40..570e810b 100644 --- a/functions/public/Invoke-WPFRunspace.ps1 +++ b/functions/public/Invoke-WPFRunspace.ps1 @@ -24,6 +24,7 @@ function Invoke-WPFRunspace { #> [CmdletBinding()] + [OutputType([System.IAsyncResult])] Param ( $ScriptBlock, $ArgumentList, diff --git a/functions/public/Invoke-WPFUIElements.ps1 b/functions/public/Invoke-WPFUIElements.ps1 index b6badfb3..47531c4c 100644 --- a/functions/public/Invoke-WPFUIElements.ps1 +++ b/functions/public/Invoke-WPFUIElements.ps1 @@ -223,6 +223,18 @@ function Invoke-WPFUIElements { $sync[$entryInfo.Name].Add_Unchecked({ $this.Content = $this.Tag.contentOff }) + + if ($null -eq $sync.Buttons) { + $sync.Buttons = [System.Collections.Generic.List[PSObject]]::new() + } + + if ($sync.Buttons -notcontains $toggleButton.Name) { + $toggleButton.Add_Click({ + [System.Object]$Sender = $args[0] + Invoke-WPFButton $Sender.name + }) + $sync.Buttons.Add($toggleButton.Name) | Out-Null + } } "Combobox" { @@ -294,6 +306,18 @@ function Invoke-WPFUIElements { $itemsControl.Items.Add($button) | Out-Null $sync[$entryInfo.Name] = $button + + if ($null -eq $sync.Buttons) { + $sync.Buttons = [System.Collections.Generic.List[PSObject]]::new() + } + + if ($sync.Buttons -notcontains $button.Name) { + $button.Add_Click({ + [System.Object]$Sender = $args[0] + Invoke-WPFButton $Sender.name + }) + $sync.Buttons.Add($button.Name) | Out-Null + } } "RadioButton" { diff --git a/functions/public/Invoke-WPFtweaksbutton.ps1 b/functions/public/Invoke-WPFtweaksbutton.ps1 index f6186c40..9f484210 100644 --- a/functions/public/Invoke-WPFtweaksbutton.ps1 +++ b/functions/public/Invoke-WPFtweaksbutton.ps1 @@ -57,7 +57,7 @@ function Invoke-WPFtweaksbutton { } # The leading "," in the ParameterList is necessary because we only provide one argument and powershell cannot be convinced that we want a nested loop with only one argument otherwise - $handle = Invoke-WPFRunspace -ParameterList @(("tweaks", $tweaksToRun), ("dnsProvider", $dnsProvider), ("completedSteps", $completedSteps), ("totalSteps", $totalSteps)) -ScriptBlock { + Invoke-WPFRunspace -ParameterList @(("tweaks", $tweaksToRun), ("dnsProvider", $dnsProvider), ("completedSteps", $completedSteps), ("totalSteps", $totalSteps)) -ScriptBlock { param($tweaks, $dnsProvider, $completedSteps, $totalSteps) $sync.ProcessRunning = $true diff --git a/pester/appx.Tests.ps1 b/pester/appx.Tests.ps1 index d1b459f9..c56c8061 100644 --- a/pester/appx.Tests.ps1 +++ b/pester/appx.Tests.ps1 @@ -39,9 +39,11 @@ BeforeAll { process { } } function Get-Package { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlets', '', Justification='Test shim is intentionally mocked by Pester.')] param($Name, $ErrorAction) } function Uninstall-Package { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlets', '', Justification='Test shim is intentionally mocked by Pester.')] param( [Parameter(ValueFromPipeline = $true)] $InputObject, diff --git a/pester/assets.Tests.ps1 b/pester/assets.Tests.ps1 index 028654f2..e4340f0a 100644 --- a/pester/assets.Tests.ps1 +++ b/pester/assets.Tests.ps1 @@ -32,11 +32,4 @@ Describe "Rendered asset caching" { $taskbarScript | Should -Match 'Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo \$false -IncludeStatusAssets \$true' } - It "records individual taskbar overlay render checkpoints" { - $overlayScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTaskbarOverlayAssets.ps1") -Raw - - $overlayScript | Should -Match 'Taskbar logo asset rendered' - $overlayScript | Should -Match 'Taskbar checkmark asset rendered' - $overlayScript | Should -Match 'Taskbar warning asset rendered' - } } diff --git a/pester/install-rendering.Tests.ps1 b/pester/install-rendering.Tests.ps1 index d680c1c2..9bb4d366 100644 --- a/pester/install-rendering.Tests.ps1 +++ b/pester/install-rendering.Tests.ps1 @@ -21,7 +21,7 @@ Describe "Install app rendering startup contract" { $renderScript | Should -Match 'Dispatcher\.BeginInvoke' $renderScript | Should -Match 'Invoke-WinUtilInstallAppRenderNextBatch' $renderScript | Should -Match 'Initialize-InstallAppEntry' - $renderScript | Should -Match 'Install app entries rendered' + $renderScript | Should -Match '\$sync\.InstallAppEntriesRendered = \$true' } It "does not use dispatcher timers for deferred install rendering" { @@ -41,7 +41,6 @@ Describe "Install app rendering startup contract" { $previousSync = Get-Variable -Name sync -Scope Global -ErrorAction SilentlyContinue $previousInitializeAppEntry = Get-Item -Path Function:\Initialize-InstallAppEntry -ErrorAction SilentlyContinue $previousSearch = Get-Item -Path Function:\Find-AppsByNameOrDescription -ErrorAction SilentlyContinue - $previousCheckpoint = Get-Item -Path Function:\Write-WinUtilPerformanceCheckpoint -ErrorAction SilentlyContinue $errorCountBefore = $global:Error.Count try { @@ -52,7 +51,6 @@ Describe "Install app rendering startup contract" { $global:sync.InstallAppRenderQueue = [System.Collections.Queue]::new() $renderedApps = [System.Collections.Generic.List[string]]::new() - $checkpoints = [System.Collections.Generic.List[string]]::new() function global:Initialize-InstallAppEntry { param($TargetElement, $AppKey) @@ -65,11 +63,6 @@ Describe "Install app rendering startup contract" { throw "Search should not run for an empty search box in this test." } - function global:Write-WinUtilPerformanceCheckpoint { - param([string]$Name) - $checkpoints.Add($Name) - } - $global:sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{ TargetElement = [pscustomobject]@{}; AppKeys = @("AppA", "AppB") }) $global:sync.InstallAppRenderQueue.Enqueue([pscustomobject]@{ TargetElement = [pscustomobject]@{}; AppKeys = @("AppC") }) @@ -80,8 +73,8 @@ Describe "Install app rendering startup contract" { $closeTimer = New-Object System.Windows.Threading.DispatcherTimer $closeTimer.Interval = [TimeSpan]::FromMilliseconds(25) $closeTimer.Add_Tick({ - param($sender) - $timer = [System.Windows.Threading.DispatcherTimer]$sender + param($eventSender) + $timer = [System.Windows.Threading.DispatcherTimer]$eventSender if ($global:sync.InstallAppEntriesRendered -or $timeout.Elapsed.TotalSeconds -gt 5) { $timer.Stop() @@ -95,7 +88,6 @@ Describe "Install app rendering startup contract" { $global:sync.InstallAppEntriesRendered | Should -BeTrue $global:sync.InstallAppRenderQueue.Count | Should -Be 0 @($renderedApps) | Should -Be @("AppA", "AppB", "AppC") - @($checkpoints) | Should -Contain "Install app entries rendered" $global:Error.Count | Should -Be $errorCountBefore } finally { if ($previousSync) { @@ -106,8 +98,7 @@ Describe "Install app rendering startup contract" { foreach ($functionBackup in @( @{ Name = "Initialize-InstallAppEntry"; Backup = $previousInitializeAppEntry }, - @{ Name = "Find-AppsByNameOrDescription"; Backup = $previousSearch }, - @{ Name = "Write-WinUtilPerformanceCheckpoint"; Backup = $previousCheckpoint } + @{ Name = "Find-AppsByNameOrDescription"; Backup = $previousSearch } )) { if ($functionBackup.Backup) { Set-Item -Path "Function:\$($functionBackup.Name)" -Value $functionBackup.Backup.ScriptBlock diff --git a/pester/lazy-tabs.Tests.ps1 b/pester/lazy-tabs.Tests.ps1 index 7df27972..9e9f67f4 100644 --- a/pester/lazy-tabs.Tests.ps1 +++ b/pester/lazy-tabs.Tests.ps1 @@ -11,9 +11,6 @@ BeforeAll { function Initialize-WPFUI { param([string]$TargetGridName) } - function Write-WinUtilPerformanceCheckpoint { - param([string]$Name) - } function Invoke-WinUtilISOCheckExistingWork { } . (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTabContent.ps1") @@ -32,7 +29,6 @@ Describe "Initialize-WinUtilTabContent" { Mock Invoke-WPFUIElements { } Mock Initialize-WPFUI { } - Mock Write-WinUtilPerformanceCheckpoint { } } AfterEach { @@ -91,4 +87,13 @@ Describe "Startup lazy tab wiring" { $tabScript | Should -Match 'Initialize-WinUtilTabContent -TabName \$sync\.currentTab' } + + It "binds generated button clicks 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)"Button"\s*\{.*\$button\.Add_Click\(\{.*Invoke-WPFButton \$Sender\.name' + $rendererScript | Should -Match '\$sync\.Buttons\.Add\(\$button\.Name\)' + $mainScript | Should -Match '\$sync\.Buttons -notcontains \$psitem' + } } diff --git a/pester/performance.Tests.ps1 b/pester/performance.Tests.ps1 deleted file mode 100644 index 50c1b56c..00000000 --- a/pester/performance.Tests.ps1 +++ /dev/null @@ -1,116 +0,0 @@ -#=========================================================================== -# Tests - Performance tracing -#=========================================================================== - -BeforeAll { - $script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path - - . (Join-Path $script:repoRoot "functions\private\Write-WinUtilLog.ps1") - . (Join-Path $script:repoRoot "tools\perf\Test-WinUtilPerformanceTrace.ps1") - . (Join-Path $script:repoRoot "tools\perf\Write-WinUtilPerformanceCheckpoint.ps1") - . (Join-Path $script:repoRoot "tools\perf\Start-WinUtilPerformanceTrace.ps1") - . (Join-Path $script:repoRoot "tools\perf\Stop-WinUtilPerformanceTrace.ps1") -} - -AfterAll { - & (Join-Path $script:repoRoot "Compile.ps1") -} - -Describe "WinUtil performance tracing helpers" { - BeforeEach { - $script:originalPerfEnv = $env:WINUTIL_PERF_LOG - Remove-Item Env:\WINUTIL_PERF_LOG -ErrorAction SilentlyContinue - $script:sync = [Hashtable]::Synchronized(@{}) - } - - AfterEach { - if ($null -eq $script:originalPerfEnv) { - Remove-Item Env:\WINUTIL_PERF_LOG -ErrorAction SilentlyContinue - } else { - $env:WINUTIL_PERF_LOG = $script:originalPerfEnv - } - - Remove-Variable -Name sync -Scope Script -ErrorAction SilentlyContinue - Remove-Variable -Name originalPerfEnv -Scope Script -ErrorAction SilentlyContinue - } - - It "is disabled by default" { - Test-WinUtilPerformanceTrace | Should -BeFalse - } - - It "can be enabled by environment variable" { - $env:WINUTIL_PERF_LOG = "1" - - Test-WinUtilPerformanceTrace | Should -BeTrue - } - - It "writes startup checkpoints through the normal WinUtil log helper" { - $env:WINUTIL_PERF_LOG = "1" - Mock Write-WinUtilLog { } - - Start-WinUtilPerformanceTrace - Write-WinUtilPerformanceCheckpoint -Name "XAML loaded" - Stop-WinUtilPerformanceTrace - - Should -Invoke -CommandName Write-WinUtilLog -Times 3 -Exactly -ParameterFilter { - $Component -eq "StartupPerf" -and $Level -eq "DEBUG" - } - Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter { - $Message -like "XAML loaded:*" - } - } -} - -Describe "Startup performance checkpoints" { - It "keeps performance tracing out of normal compiled output" { - & (Join-Path $script:repoRoot "Compile.ps1") - - $compiledScript = Get-Content -Path (Join-Path $script:repoRoot "winutil.ps1") -Raw - - $compiledScript | Should -Not -Match "Test-WinUtilPerformanceTrace" - $compiledScript | Should -Not -Match "Start-WinUtilPerformanceTrace" - $compiledScript | Should -Not -Match "Write-WinUtilPerformanceCheckpoint" - $compiledScript | Should -Not -Match "Stop-WinUtilPerformanceTrace" - $compiledScript | Should -Not -Match "PerformanceTraceEnabled" - } - - It "adds config-load checkpoints only to trace compiled output" { - $compileScript = Get-Content -Path (Join-Path $script:repoRoot "Compile.ps1") -Raw - - $compileScript | Should -Match '\[switch\]\$Trace' - $compileScript | Should -Match "Start-WinUtilPerformanceTrace" - $compileScript | Should -Match "Config load start" - $compileScript | Should -Match "Config load complete" - $compileScript | Should -Match "Config .* loaded" - - & (Join-Path $script:repoRoot "Compile.ps1") -Trace - $compiledScript = Get-Content -Path (Join-Path $script:repoRoot "winutil.ps1") -Raw - - $compiledScript | Should -Match "function Test-WinUtilPerformanceTrace" - $compiledScript | Should -Match '\$sync\.PerformanceTraceEnabled = \$true' - $compiledScript | Should -Match "Config load start" - $compiledScript | Should -Match "Config load complete" - } - - It "adds runtime checkpoints for startup hotspots" { - $mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw - $lazyTabScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTabContent.ps1") -Raw - $runspaceScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1") -Raw - $overlayScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTaskbarOverlayAssets.ps1") -Raw - $startupText = "$mainScript`n$lazyTabScript`n$runspaceScript`n$overlayScript" - - foreach ($checkpoint in @( - "Runspace pool initialized", - "XAML loaded", - "Theme applied", - "Install UI created", - "Tweaks UI created", - "Features UI created", - "AppX UI created", - "Taskbar logo asset rendered", - "First content rendered" - )) { - $startupText | Should -Match ([regex]::Escape($checkpoint)) - } - } -} diff --git a/pester/runspace-lifecycle.Tests.ps1 b/pester/runspace-lifecycle.Tests.ps1 index 00b79851..68a50074 100644 --- a/pester/runspace-lifecycle.Tests.ps1 +++ b/pester/runspace-lifecycle.Tests.ps1 @@ -12,9 +12,6 @@ Describe "Initialize-WinUtilRunspacePool" { BeforeEach { $script:sync = [Hashtable]::Synchronized(@{}) $script:PARAM_OFFLINE = $false - - function Write-WinUtilPerformanceCheckpoint { param($Name) } - Mock Write-WinUtilPerformanceCheckpoint { } } AfterEach { @@ -29,9 +26,6 @@ Describe "Initialize-WinUtilRunspacePool" { $firstPool.RunspacePoolStateInfo.State | Should -Be ([System.Management.Automation.Runspaces.RunspacePoolState]::Opened) [object]::ReferenceEquals($firstPool, $secondPool) | Should -BeTrue - Should -Invoke -CommandName Write-WinUtilPerformanceCheckpoint -Times 1 -Exactly -ParameterFilter { - $Name -eq "Runspace pool initialized" - } } It "closes and removes the active runspace pool" { diff --git a/pester/runspace.Tests.ps1 b/pester/runspace.Tests.ps1 index 2fec90a5..ae404db9 100644 --- a/pester/runspace.Tests.ps1 +++ b/pester/runspace.Tests.ps1 @@ -20,8 +20,6 @@ BeforeAll { $initialSessionState.Variables.Add($syncVariable) $script:sync.runspace = [runspacefactory]::CreateRunspacePool(1, 2, $initialSessionState, $Host) $script:sync.runspace.Open() - - function Write-WinUtilPerformanceCheckpoint { param($Name) } } function script:Clear-WinUtilRunspaceTestContext { diff --git a/pester/sanity.Tests.ps1 b/pester/sanity.Tests.ps1 index ee9725c2..89f0c131 100644 --- a/pester/sanity.Tests.ps1 +++ b/pester/sanity.Tests.ps1 @@ -208,8 +208,6 @@ Describe "Runspace sanity" { . (Join-Path $script:repoRoot "functions\public\Invoke-WPFRunspace.ps1") . (Join-Path $script:repoRoot "functions\private\Close-WinUtilRunspacePool.ps1") . (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1") - - function Write-WinUtilPerformanceCheckpoint { param($Name) } } It "returns a single async handle and runs a scriptblock with arguments in the shared runspace pool" { diff --git a/pester/win11creator.Tests.ps1 b/pester/win11creator.Tests.ps1 index 1c7d0c26..6769e3ef 100644 --- a/pester/win11creator.Tests.ps1 +++ b/pester/win11creator.Tests.ps1 @@ -35,6 +35,7 @@ Describe "Win11 Creator setup media" { return $functionAst.Extent.Text } + $script:modifyFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Invoke-WinUtilISOModify" $script:editionIdFunction = Get-WinUtilFunctionText -Path $script:isoWorkflowPath -FunctionName "Get-WinUtilEditionIdFromName" $script:addDriversFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Add-DriversToImage" $script:answerFileChildElementFunction = Get-WinUtilFunctionText -Path $script:isoScriptPath -FunctionName "Get-WinUtilISOScriptChildElement" @@ -69,6 +70,27 @@ Describe "Win11 Creator setup media" { } } + It "starts each new ISO modification in a fresh working directory" { + foreach ($expectedText in @( + '$workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format ''yyyyMMdd_HHmmss'')"', + '$workDir = Join-Path $env:TEMP "WinUtil_Win11ISO_$(Get-Date -Format ''yyyyMMdd_HHmmss'')_$(([guid]::NewGuid()).ToString(''N'').Substring(0, 8))"' + )) { + $script:modifyFunction | Should -Match ([regex]::Escape($expectedText)) + } + + $script:modifyFunction | Should -Not -Match ([regex]::Escape("Reusing existing temp directory")) + } + + It "mounts the copied image file that was verified from the ISO" { + foreach ($expectedText in @( + '$sourceImageFileName = Split-Path $wimPath -Leaf', + '$localWim = Join-Path $isoContents "sources\$sourceImageFileName"', + 'Copied ISO image file not found: sources\$sourceImageFileName' + )) { + $script:modifyFunction | Should -Match ([regex]::Escape($expectedText)) + } + } + It "maps Windows edition names to setup edition IDs" { . ([scriptblock]::Create($script:editionIdFunction)) diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 5bd2987a..44e76f9f 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -57,10 +57,8 @@ $sync.configs.appxHashtable = @{} $sync.configs.appx.PSObject.Properties | ForEach-Object { $sync.configs.appxHashtable[$_.Name] = $_.Value } -Write-WinUtilPerformanceCheckpoint -Name "Config hashtables initialized" Set-Preferences -Write-WinUtilPerformanceCheckpoint -Name "Preferences loaded" if ($Preset) { Initialize-WinUtilRunspacePool | Out-Null @@ -101,7 +99,6 @@ $reader = (New-Object System.Xml.XmlNodeReader $xaml) try { $sync["Form"] = [Windows.Markup.XamlReader]::Load( $reader ) $readerOperationSuccessful = $true - Write-WinUtilPerformanceCheckpoint -Name "XAML loaded" } catch [System.Management.Automation.MethodInvocationException] { Write-Host "We ran into a problem with the XAML code. Check the syntax for this control..." -ForegroundColor Red Write-Host $error[0].Exception.Message -ForegroundColor Red @@ -136,6 +133,7 @@ $sync.Form.Add_Loaded({ [System.IntPtr]$lParam, [ref]$handled ) + $null = $hwnd, $wParam, $lParam # Check for the Event WM_SETTINGCHANGE (0x1001A) and validate that Button shows the icon for "Auto" => [char]0xF08C if (($msg -eq 0x001A) -and $sync.ThemeButton.Content -eq [char]0xF08C) { $currentTime = [datetime]::Now @@ -150,7 +148,6 @@ $sync.Form.Add_Loaded({ }) Invoke-WinutilThemeChange -theme $sync.preferences.theme -Write-WinUtilPerformanceCheckpoint -Name "Theme applied" # Build only the default tab before first paint; other tabs initialize on first activation. @@ -184,17 +181,23 @@ switch ($sync.preferences.packagemanager) { $sync.keys | ForEach-Object { if($sync.$psitem) { if($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "ToggleButton") { - $sync["$psitem"].Add_Click({ - [System.Object]$Sender = $args[0] - Invoke-WPFButton $Sender.name - }) + if ($sync.Buttons -notcontains $psitem) { + $sync["$psitem"].Add_Click({ + [System.Object]$Sender = $args[0] + Invoke-WPFButton $Sender.name + }) + $sync.Buttons.Add($psitem) | Out-Null + } } if($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "Button") { - $sync["$psitem"].Add_Click({ - [System.Object]$Sender = $args[0] - Invoke-WPFButton $Sender.name - }) + if ($sync.Buttons -notcontains $psitem) { + $sync["$psitem"].Add_Click({ + [System.Object]$Sender = $args[0] + Invoke-WPFButton $Sender.name + }) + $sync.Buttons.Add($psitem) | Out-Null + } } if ($($sync["$psitem"].GetType() | Select-Object -ExpandProperty Name) -eq "TextBlock") { @@ -221,7 +224,6 @@ Set-WinUtilTaskbaritem -state "None" $sync["Form"].title = $sync["Form"].title + " " + $sync.version # Set the commands that will run when the form is closed $sync["Form"].Add_Closing({ - Stop-WinUtilPerformanceTrace -Name "Window closing" Close-WinUtilRunspacePool [System.GC]::Collect() }) @@ -290,7 +292,6 @@ $sync["Form"].Add_Deactivated({ }) $sync["Form"].Add_ContentRendered({ - Write-WinUtilPerformanceCheckpoint -Name "First content rendered" # Load the Windows Forms assembly Add-Type -AssemblyName System.Windows.Forms $primaryScreen = [System.Windows.Forms.Screen]::PrimaryScreen @@ -378,6 +379,7 @@ $sync["SearchBar"].Add_TextChanged({ $sync["Form"].Add_Loaded({ param($e) + $null = $e $sync.Form.MinWidth = "1000" $sync["Form"].MaxWidth = [Double]::PositiveInfinity $sync["Form"].MaxHeight = [Double]::PositiveInfinity diff --git a/tools/devdocs-generator.ps1 b/tools/devdocs-generator.ps1 index eeaaa33a..ab9b1adc 100644 --- a/tools/devdocs-generator.ps1 +++ b/tools/devdocs-generator.ps1 @@ -27,12 +27,10 @@ function Get-RawJsonBlock { $escapedName = [regex]::Escape($ItemName) $startIndex = -1 - $startIndent = "" for ($i = 0; $i -lt $JsonLines.Count; $i++) { if ($JsonLines[$i] -match "^(\s*)`"$escapedName`"\s*:\s*\{") { $startIndex = $i - $startIndent = $matches[1] break } } @@ -47,7 +45,7 @@ function Get-RawJsonBlock { $depth = 1 # We're starting inside the opening brace for ($i = ($startIndex + 1); $i -lt $JsonLines.Count; $i++) { $line = $JsonLines[$i] - + # Count braces in this line, ignoring those in strings $inString = $false $chars = $line.ToCharArray() @@ -59,7 +57,7 @@ function Get-RawJsonBlock { elseif ($chars[$k] -eq '}') { $depth-- } } } - + # Found the closing brace of the item if ($depth -eq 0) { $endIndex = $i @@ -173,14 +171,14 @@ function Add-LinkAttributeToJson { for ($j = $startIdx + 1; $j -lt $lines.Count; $j++) { $line = $lines[$j] - + # Check for existing "link" property at top-level (depth 1 before processing braces on this line) # Match at any indentation level (user may have manually changed indentation) if ($depth -eq 1 -and $line -match '^\s*"link"\s*:') { # Mark this line for removal $linesToRemove += $j } - + # Count braces in this line, ignoring those in strings $inString = $false $chars = $line.ToCharArray() @@ -192,7 +190,7 @@ function Add-LinkAttributeToJson { elseif ($chars[$k] -eq '}') { $depth-- } } } - + # Found the closing brace of the item if ($depth -eq 0) { $closeBraceIdx = $j diff --git a/tools/perf/Start-WinUtilPerformanceTrace.ps1 b/tools/perf/Start-WinUtilPerformanceTrace.ps1 deleted file mode 100644 index 0d936232..00000000 --- a/tools/perf/Start-WinUtilPerformanceTrace.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -function Start-WinUtilPerformanceTrace { - if (-not (Test-WinUtilPerformanceTrace) -or $null -eq $sync) { - return - } - - if (-not $sync.ContainsKey("PerformanceTrace")) { - $sync.PerformanceTrace = @{ - Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - LastElapsedMs = 0.0 - } - } - - Write-WinUtilPerformanceCheckpoint -Name "Startup trace started" -} diff --git a/tools/perf/Stop-WinUtilPerformanceTrace.ps1 b/tools/perf/Stop-WinUtilPerformanceTrace.ps1 deleted file mode 100644 index aa507fe5..00000000 --- a/tools/perf/Stop-WinUtilPerformanceTrace.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -function Stop-WinUtilPerformanceTrace { - param( - [string]$Name = "Startup trace complete" - ) - - if (-not (Test-WinUtilPerformanceTrace) -or $null -eq $sync -or -not $sync.ContainsKey("PerformanceTrace")) { - return - } - - Write-WinUtilPerformanceCheckpoint -Name $Name - - if ($null -ne $sync.PerformanceTrace.Stopwatch) { - $sync.PerformanceTrace.Stopwatch.Stop() - } -} diff --git a/tools/perf/Test-WinUtilPerformanceTrace.ps1 b/tools/perf/Test-WinUtilPerformanceTrace.ps1 deleted file mode 100644 index 1b899590..00000000 --- a/tools/perf/Test-WinUtilPerformanceTrace.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -function Test-WinUtilPerformanceTrace { - $enabledValue = ([string]$env:WINUTIL_PERF_LOG).ToLowerInvariant() - if ($enabledValue -in @("1", "true", "yes", "on")) { - return $true - } - - if ($null -ne $sync -and $sync.ContainsKey("PerformanceTraceEnabled")) { - return [bool]$sync.PerformanceTraceEnabled - } - - return $false -} diff --git a/tools/perf/Write-WinUtilPerformanceCheckpoint.ps1 b/tools/perf/Write-WinUtilPerformanceCheckpoint.ps1 deleted file mode 100644 index 9f244c16..00000000 --- a/tools/perf/Write-WinUtilPerformanceCheckpoint.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -function Write-WinUtilPerformanceCheckpoint { - param( - [Parameter(Mandatory = $true)] - [string]$Name - ) - - if (-not (Test-WinUtilPerformanceTrace) -or $null -eq $sync) { - return - } - - if (-not $sync.ContainsKey("PerformanceTrace") -or $null -eq $sync.PerformanceTrace.Stopwatch) { - $sync.PerformanceTrace = @{ - Stopwatch = [System.Diagnostics.Stopwatch]::StartNew() - LastElapsedMs = 0.0 - } - } - - $elapsedMs = [math]::Round($sync.PerformanceTrace.Stopwatch.Elapsed.TotalMilliseconds, 2) - $deltaMs = [math]::Round($elapsedMs - [double]$sync.PerformanceTrace.LastElapsedMs, 2) - $sync.PerformanceTrace.LastElapsedMs = $elapsedMs - - Write-WinUtilLog -Level "DEBUG" -Component "StartupPerf" -Message ("{0}: {1:N2} ms (+{2:N2} ms)" -f $Name, $elapsedMs, $deltaMs) -} diff --git a/windev.ps1 b/windev.ps1 index 488f3f31..b6429fce 100644 --- a/windev.ps1 +++ b/windev.ps1 @@ -1,4 +1,5 @@ # Runs the pre-release version of winutil $latestTag = (Invoke-RestMethod https://api.github.com/repos/ChrisTitusTech/winutil/tags).Name | Select-Object -First 1 -Invoke-RestMethod -Uri https://github.com/ChrisTitusTech/winutil/releases/download/$latestTag/winutil.ps1 | Invoke-Expression +$script = Invoke-RestMethod -Uri https://github.com/ChrisTitusTech/winutil/releases/download/$latestTag/winutil.ps1 +Invoke-Command -ScriptBlock ([scriptblock]::Create($script)) -ErrorAction Stop