feat: add timeout for WinGet uninstall to prevent hanging (#731)

This commit is contained in:
Jeffrey
2026-08-09 01:24:27 +02:00
committed by GitHub
parent 5c838384d6
commit c0599cba1f
2 changed files with 51 additions and 8 deletions
+22 -4
View File
@@ -96,24 +96,42 @@ function Remove-SelectedApps {
Uninstalls an app via WinGet and/or schedules its removal.
.DESCRIPTION
Runs winget uninstall for a single app. If the User or Sysprep
parameter was passed, also schedules removal for future logins.
Runs winget uninstall for a single app, with a bounded execution time.
If the User or Sysprep parameter was passed, also schedules removal for
future logins.
.PARAMETER app
The WinGet package ID to uninstall (e.g. 'Microsoft.BingNews').
.PARAMETER TimeoutSeconds
Maximum time to allow the foreground WinGet uninstall to run. Defaults
to 120 seconds.
#>
function Remove-WinGetApp {
param([string]$app)
param(
[string]$app,
[int]$TimeoutSeconds = 120
)
if (-not $script:WingetInstalled) {
Write-Host "ERROR: WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
return
}
try {
Invoke-NonBlocking -ScriptBlock {
param($appId)
winget uninstall --accept-source-agreements --disable-interactivity --id $appId
} -ArgumentList $app
} -ArgumentList $app -TimeoutSeconds $TimeoutSeconds
}
catch {
if ($_.Exception.Message -like 'Operation timed out after *') {
Write-Host "WinGet uninstall for $app did not complete within $TimeoutSeconds seconds: $_" -ForegroundColor Red
}
else {
Write-Host "WinGet uninstall for $app failed: $_" -ForegroundColor Red
}
}
if ($script:Params.ContainsKey("User")) {
Write-Host "Adding scheduled task to uninstall $app for user $(Get-UserName)..."
+26 -1
View File
@@ -2,7 +2,7 @@ BeforeAll {
function Get-TargetUserForAppRemoval { 'AllUsers' }
function Get-WingetInstalledApps { param($TimeOut, [switch]$NonBlocking) @() }
function Test-AppInWingetList { param($appId, $InstalledList) $false }
function Invoke-NonBlocking { param($ScriptBlock, $ArgumentList) }
function Invoke-NonBlocking { param($ScriptBlock, $ArgumentList, $TimeoutSeconds) }
function Get-UserName { 'Alice' }
function Invoke-ForceRemoveEdge {}
function Show-MessageBox { 'No' }
@@ -109,6 +109,31 @@ Describe 'Remove-WinGetApp' {
Should -Invoke Invoke-NonBlocking -Times 1 -Exactly -ParameterFilter { $ArgumentList -eq 'One.App' }
Should -Invoke Set-RunOnceWingetTask -Times $Scheduled -Exactly -ParameterFilter { $appId -eq 'One.App' }
}
It 'limits a foreground winget uninstall to two minutes' {
Remove-WinGetApp -app 'One.App'
Should -Invoke Invoke-NonBlocking -Times 1 -Exactly -ParameterFilter {
$ArgumentList -eq 'One.App' -and $TimeoutSeconds -eq 120
}
}
It 'passes a specified foreground winget uninstall timeout' {
Remove-WinGetApp -app 'One.App' -TimeoutSeconds 30
Should -Invoke Invoke-NonBlocking -Times 1 -Exactly -ParameterFilter {
$ArgumentList -eq 'One.App' -and $TimeoutSeconds -eq 30
}
}
It 'reports a timed-out winget uninstall and continues' {
$script:Params = @{ User = 'Alice' }
Mock Invoke-NonBlocking { throw 'Operation timed out after 120 seconds' }
{ Remove-WinGetApp -app 'One.App' } | Should -Not -Throw
Should -Invoke Set-RunOnceWingetTask -Times 1 -Exactly
Should -Invoke Write-Host -Times 1 -Exactly -ParameterFilter {
$Object -like '*did not complete within 120 seconds*' -and $ForegroundColor -eq 'Red'
}
}
}
Describe 'Remove-AppxApp' {