Files
winutil/scripts/start.ps1
T
Chris Titus a0d3c719a6 Improve UI startup after runspace overhaul (#5056)
* perf: keep UI startup responsive

* test: make favicon checks runner-safe

* fix: qualify favicon visibility state

* test: isolate favicon visibility fixture

* fix: enforce total favicon deadline

* fix: address favicon review feedback

* fix: recognize disabled SSH firewall rule

* perf: restore favicon download throughput

* docs: allow public PR review uploads

* perf: restore overlapping favicon loading
2026-09-07 11:33:39 -05:00

268 lines
11 KiB
PowerShell

<#
.NOTES
Author : Chris Titus @christitustech
Runspace Author: @DeveloperDurp
GitHub : https://github.com/ChrisTitusTech
Version : #{replaceme}
#>
param (
[string]$Config,
[ValidateSet("Standard", "Minimal", "Advanced", "")]
[string]$Preset,
[switch]$Offline
)
function Test-WinUtilOwnsFileProcess {
<#
.SYNOPSIS
Whether the current process was launched with this script as its file target
#>
param(
[string]$ScriptPath = $PSCommandPath,
[string[]]$CommandLineArgs = [Environment]::GetCommandLineArgs()
)
if ([string]::IsNullOrWhiteSpace($ScriptPath)) { return $false }
$hostOptionKinds = [ordered]@{
Command = "Command"
EncodedCommand = "Command"
CommandWithArgs = "Command"
File = "File"
ConfigurationFile = "Value"
ConfigurationName = "Value"
CustomPipeName = "Value"
ExecutionPolicy = "Value"
InputFormat = "Value"
Interactive = "Switch"
Login = "Switch"
MTA = "Switch"
NoLogo = "Switch"
NonInteractive = "Switch"
NoProfile = "Switch"
NoProfileLoadTime = "Switch"
OutputFormat = "Value"
PSConsoleFile = "Value"
SettingsFile = "Value"
SSHServerMode = "Switch"
STA = "Switch"
Version = "Value"
WindowStyle = "Value"
WorkingDirectory = "Value"
NoExit = "NoExit"
}
$hostOptionAliases = @{
c = "Command"; cwa = "Command"; e = "Command"; ec = "Command"; f = "File"
noe = "NoExit"
config = "Value"; ConfigName = "Value"; CustomPipe = "Value"; ep = "Value"; ex = "Value"
i = "Switch"; Input = "Value"; In = "Value"; if = "Value"
Output = "Value"; Out = "Value"; of = "Value"
Settings = "Value"; Window = "Value"; w = "Value"; Working = "Value"; wd = "Value"
}
function Get-WinUtilHostOptionKind {
param([string]$Argument)
if ([string]::IsNullOrWhiteSpace($Argument) -or -not $Argument.StartsWith("-")) {
return $null
}
$optionName = $Argument.TrimStart("-")
if ($hostOptionAliases.ContainsKey($optionName)) {
return $hostOptionAliases[$optionName]
}
# pwsh accepts any unambiguous prefix of a host option, such as -WorkingD.
$matchingOptions = @($hostOptionKinds.Keys | Where-Object {
$_.StartsWith($optionName, [StringComparison]::OrdinalIgnoreCase)
})
$matchingKinds = @($matchingOptions | ForEach-Object { $hostOptionKinds[$_] } | Select-Object -Unique)
if ($matchingKinds.Count -eq 1) {
return $matchingKinds[0]
}
return $null
}
:hostArguments for ($index = 1; $index -lt $CommandLineArgs.Count; $index++) {
$optionKind = Get-WinUtilHostOptionKind -Argument $CommandLineArgs[$index]
switch ($optionKind) {
"Command" { return $false }
"NoExit" { return $false }
"Switch" { continue hostArguments }
"File" {
if ($index + 1 -ge $CommandLineArgs.Count) { return $false }
$fileTarget = $CommandLineArgs[$index + 1]
if ([string]::IsNullOrWhiteSpace($fileTarget) -or $fileTarget -eq "-") {
return $false
}
return [string]::Equals(
[IO.Path]::GetFullPath($fileTarget),
[IO.Path]::GetFullPath($ScriptPath),
[StringComparison]::OrdinalIgnoreCase
)
}
"Value" {
$index++
continue hostArguments
}
}
if ($CommandLineArgs[$index].StartsWith("-")) {
continue
}
return [string]::Equals(
[IO.Path]::GetFullPath($CommandLineArgs[$index]),
[IO.Path]::GetFullPath($ScriptPath),
[StringComparison]::OrdinalIgnoreCase
)
}
return $false
}
# A headless script launched with powershell.exe/pwsh.exe -File owns its process and must set
# that process's exit code. An invoked or in-memory script must return without closing its caller.
$script:WinUtilIsFileProcess = Test-WinUtilOwnsFileProcess
$PARAM_OFFLINE = $false
if ($Offline) {
$PARAM_OFFLINE = $true
}
if ($ExecutionContext.SessionState.LanguageMode -ne 'FullLanguage') {
Write-Host "WinUtil is unable to run on your system. PowerShell execution is restricted by security policies." -ForegroundColor Red
$global:LASTEXITCODE = 1
if ($env:WINUTIL_HEADLESS_CHILD -eq "1" -or $script:WinUtilIsFileProcess) { exit 1 }
return 1
}
function New-WinUtilElevationCommand {
<#
.SYNOPSIS
Encodes the relaunch target and bound parameters as data for an elevated child
#>
param(
[string]$ScriptPath,
[hashtable]$Parameters = @{},
[switch]$Headless
)
$launchData = @{
ScriptPath = $ScriptPath
Parameters = $Parameters
Headless = [bool]$Headless
}
$serializedLaunch = [System.Management.Automation.PSSerializer]::Serialize($launchData)
$launchPayload = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($serializedLaunch))
# Only base64 is embedded in executable text. User-controlled values are deserialized and
# splatted as parameter data in the child, so quotes in Config cannot become PowerShell code.
$bootstrap = @"
`$launchXml = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('$launchPayload'))
`$launch = [System.Management.Automation.PSSerializer]::Deserialize(`$launchXml)
`$invokeParameters = `$launch.Parameters
if (`$launch.Headless) { `$env:WINUTIL_HEADLESS_CHILD = '1' }
if (`$launch.ScriptPath) {
& `$launch.ScriptPath @invokeParameters
} else {
`$remoteScript = [ScriptBlock]::Create((Invoke-RestMethod 'https://github.com/ChrisTitusTech/winutil/releases/latest/download/winutil.ps1'))
& `$remoteScript @invokeParameters
}
"@
return [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($bootstrap))
}
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Output "WinUtil needs to be run as Administrator. Attempting to relaunch."
$elevationParameters = @{}
foreach ($parameter in $PSBoundParameters.GetEnumerator()) {
$elevationParameters[$parameter.Key] = if ($parameter.Value -is [switch]) {
[bool]$parameter.Value
} else {
$parameter.Value
}
}
$powershellCmd = if (Get-Command pwsh -ErrorAction SilentlyContinue) { "pwsh" } else { "powershell" }
# A headless caller is waiting on this process for an outcome, so the elevated run has to be
# waited on and its code handed back. A terminal tab is skipped for the same reason: the
# exit code of wt.exe is its own, not the run's.
if ($Config -or $Preset) {
# A declined UAC prompt throws, which would leave $elevated null and exit 0: the caller
# waiting on this process would read that as a successful run
try {
$elevationCommand = New-WinUtilElevationCommand -ScriptPath $PSCommandPath -Parameters $elevationParameters -Headless
$elevated = Start-Process $powershellCmd -ArgumentList @("-ExecutionPolicy", "Bypass", "-NoProfile", "-EncodedCommand", $elevationCommand) -Verb RunAs -Wait -PassThru -ErrorAction Stop
} catch {
Write-Host "Elevation was declined or failed: $($_.Exception.Message)" -ForegroundColor Red
$global:LASTEXITCODE = 1
if ($script:WinUtilIsFileProcess) { exit 1 }
return 1
}
$global:LASTEXITCODE = $elevated.ExitCode
if ($script:WinUtilIsFileProcess) { exit $elevated.ExitCode }
return $elevated.ExitCode
}
$processCmd = if (Get-Command wt.exe -ErrorAction SilentlyContinue) { "wt.exe" } else { "$powershellCmd" }
$elevationCommand = New-WinUtilElevationCommand -ScriptPath $PSCommandPath -Parameters $elevationParameters
if ($processCmd -eq "wt.exe") {
Start-Process $processCmd -ArgumentList "$powershellCmd -ExecutionPolicy Bypass -NoProfile -EncodedCommand $elevationCommand" -Verb RunAs
} else {
Start-Process $processCmd -ArgumentList @("-ExecutionPolicy", "Bypass", "-NoProfile", "-EncodedCommand", $elevationCommand) -Verb RunAs
}
break
}
# Variable to sync between runspaces
$sync = [Hashtable]::Synchronized(@{})
$sync.version = "#{replaceme}"
$sync.IsLocalCompile = "#{islocalcompile}" -eq "true"
$sync.configs = @{}
$sync.Buttons = [System.Collections.Generic.List[PSObject]]::new()
$sync.preferences = @{}
# Name of the job currently running, or $null when idle. Owned by Start-WinUtilJob.
$sync.ActiveJob = $null
# Serializes worker-pool startup with recycling and shutdown.
$sync.RunspacePoolLock = [object]::new()
# Serializes the speculative and UI-thread taskbar overlay renderers.
$sync.AssetRenderLock = [object]::new()
$sync.RenderedAssetCache = [Hashtable]::Synchronized(@{})
# Every step recorded by Measure-WinUtilStep, from any thread
$sync.StepTimings = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())
# Every error logged, so a job can report that something went wrong even when it did not throw
$sync.LoggedErrors = [System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]::new())
$sync.StartedAt = Get-Date
$sync.selectedAppx = [System.Collections.Generic.List[string]]::new()
$sync.selectedApps = [System.Collections.Generic.List[string]]::new()
$sync.selectedTweaks = [System.Collections.Generic.List[string]]::new()
$sync.selectedToggles = [System.Collections.Generic.List[string]]::new()
$sync.selectedFeatures = [System.Collections.Generic.List[string]]::new()
$sync.currentTab = "Install"
$dateTime = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$winutildir = "$env:LocalAppData\winutil"
$sync.winutildir = $winutildir
$logdir = "$winutildir\logs"
# Start-Transcript fails outright when the directory is missing, which is every first run
if (-not (Test-Path $logdir)) {
New-Item -ItemType Directory -Path $logdir -Force | Out-Null
}
# Keep console output and structured entries in the path reported to the user. Write-WinUtilLog
# writes through the host while this transcript owns the file, avoiding competing file handles.
$sync.logPath = "$logdir\winutil_$dateTime.log"
$sync.transcriptPath = $sync.logPath
Start-Transcript -Path $sync.transcriptPath -Append -NoClobber | Out-Null
$Host.UI.RawUI.WindowTitle = "WinUtil"
Clear-Host