mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-10-08 06:26:45 +00:00
Fix uninstallation of user-scope apps in WinGet with deferred uninstall
This commit is contained in:
@@ -134,10 +134,12 @@
|
|||||||
"ApplyCompletionTitleSuccess": "Changes Applied",
|
"ApplyCompletionTitleSuccess": "Changes Applied",
|
||||||
"ApplyCompletionMessageDefault": "Please note that some changes will only take effect after a reboot. Thanks for using Win11Debloat!",
|
"ApplyCompletionMessageDefault": "Please note that some changes will only take effect after a reboot. Thanks for using Win11Debloat!",
|
||||||
"ApplyRebootRequiredHeader": "A reboot is required for these changes to take effect:",
|
"ApplyRebootRequiredHeader": "A reboot is required for these changes to take effect:",
|
||||||
|
"ApplyRebootRequiredWingetDeferred": "Uninstallation of {0}",
|
||||||
"ApplyCompletionTitleCancelled": "Cancelled",
|
"ApplyCompletionTitleCancelled": "Cancelled",
|
||||||
"ApplyCompletionMessageCancelled": "Script execution was cancelled by the user.",
|
"ApplyCompletionMessageCancelled": "Script execution was cancelled by the user.",
|
||||||
"ApplyCompletionTitleErrors": "Changes Applied with Errors",
|
"ApplyCompletionTitleErrors": "Changes Applied with Errors",
|
||||||
"ApplyCompletionMessageVerificationUnavailable": "All changes were applied without errors, but Win11Debloat could not confirm that all selected apps were successfully uninstalled.",
|
"ApplyCompletionMessageVerificationUnavailable": "All changes were applied without errors, but Win11Debloat could not confirm that all selected apps were successfully uninstalled.",
|
||||||
|
"WingetUserScopeUninstallDeferred": "WinGet could not uninstall {0} while running as administrator because it is installed for user scope. Restart the PC or sign out and back in as {1} to complete removal.",
|
||||||
"ApplyCompletionMessageFailures_one": "{0} change failed. See console for details.",
|
"ApplyCompletionMessageFailures_one": "{0} change failed. See console for details.",
|
||||||
"ApplyCompletionMessageFailures_other": "{0} changes failed. See console for details.",
|
"ApplyCompletionMessageFailures_other": "{0} changes failed. See console for details.",
|
||||||
"ApplyCompletionMessageReady": "Your system is ready. Thanks for using Win11Debloat!",
|
"ApplyCompletionMessageReady": "Your system is ready. Thanks for using Win11Debloat!",
|
||||||
|
|||||||
@@ -125,10 +125,12 @@
|
|||||||
"ApplyCompletionTitleSuccess": "Modifiche applicate",
|
"ApplyCompletionTitleSuccess": "Modifiche applicate",
|
||||||
"ApplyCompletionMessageDefault": "Nota che alcune modifiche avranno effetto solo dopo il riavvio. Grazie per aver usato Win11Debloat!",
|
"ApplyCompletionMessageDefault": "Nota che alcune modifiche avranno effetto solo dopo il riavvio. Grazie per aver usato Win11Debloat!",
|
||||||
"ApplyRebootRequiredHeader": "È necessario riavviare il sistema affinché le seguenti modifiche abbiano effetto:",
|
"ApplyRebootRequiredHeader": "È necessario riavviare il sistema affinché le seguenti modifiche abbiano effetto:",
|
||||||
|
"ApplyRebootRequiredWingetDeferred": "Disinstallazione di {0}",
|
||||||
"ApplyCompletionTitleCancelled": "Annullato",
|
"ApplyCompletionTitleCancelled": "Annullato",
|
||||||
"ApplyCompletionMessageCancelled": "L'esecuzione dello script è stata annullata dall'utente.",
|
"ApplyCompletionMessageCancelled": "L'esecuzione dello script è stata annullata dall'utente.",
|
||||||
"ApplyCompletionTitleErrors": "Modifiche applicate con errori",
|
"ApplyCompletionTitleErrors": "Modifiche applicate con errori",
|
||||||
"ApplyCompletionMessageVerificationUnavailable": "Tutte le modifiche sono state applicate senza errori, ma Win11Debloat non ha potuto confermare che tutte le app selezionate siano state disinstallate correttamente.",
|
"ApplyCompletionMessageVerificationUnavailable": "Tutte le modifiche sono state applicate senza errori, ma Win11Debloat non ha potuto confermare che tutte le app selezionate siano state disinstallate correttamente.",
|
||||||
|
"WingetUserScopeUninstallDeferred": "WinGet non ha potuto disinstallare {0} con privilegi di amministratore perché è installata per l'utente. Riavvia il PC oppure esci e accedi di nuovo come {1} per completare la rimozione.",
|
||||||
"ApplyCompletionMessageFailures_one": "{0} modifica non riuscita. Consulta la console per i dettagli.",
|
"ApplyCompletionMessageFailures_one": "{0} modifica non riuscita. Consulta la console per i dettagli.",
|
||||||
"ApplyCompletionMessageFailures_other": "{0} modifiche non riuscite. Consulta la console per i dettagli.",
|
"ApplyCompletionMessageFailures_other": "{0} modifiche non riuscite. Consulta la console per i dettagli.",
|
||||||
"ApplyCompletionMessageReady": "Il sistema è pronto. Grazie per aver usato Win11Debloat!",
|
"ApplyCompletionMessageReady": "Il sistema è pronto. Grazie per aver usato Win11Debloat!",
|
||||||
|
|||||||
@@ -124,11 +124,13 @@
|
|||||||
"ApplyCreatingRestorePoint": "Systeemherstelpunt maken. Dit kan even duren...",
|
"ApplyCreatingRestorePoint": "Systeemherstelpunt maken. Dit kan even duren...",
|
||||||
"ApplyCompletionTitleSuccess": "Wijzigingen toegepast",
|
"ApplyCompletionTitleSuccess": "Wijzigingen toegepast",
|
||||||
"ApplyCompletionMessageDefault": "Sommige wijzigingen worden pas actief nadat je de computer opnieuw hebt opgestart. Bedankt voor het gebruik van Win11Debloat!",
|
"ApplyCompletionMessageDefault": "Sommige wijzigingen worden pas actief nadat je de computer opnieuw hebt opgestart. Bedankt voor het gebruik van Win11Debloat!",
|
||||||
"ApplyRebootRequiredHeader": "Start de computer opnieuw op om de volgende wijzigingen te activeren:",
|
"ApplyRebootRequiredHeader": "De volgende wijzigingen vereisen een herstart:",
|
||||||
|
"ApplyRebootRequiredWingetDeferred": "Verwijderen van {0}",
|
||||||
"ApplyCompletionTitleCancelled": "Geannuleerd",
|
"ApplyCompletionTitleCancelled": "Geannuleerd",
|
||||||
"ApplyCompletionMessageCancelled": "Het toepassen van de wijzigingen is geannuleerd.",
|
"ApplyCompletionMessageCancelled": "Het toepassen van de wijzigingen is geannuleerd.",
|
||||||
"ApplyCompletionTitleErrors": "Wijzigingen toegepast met fouten",
|
"ApplyCompletionTitleErrors": "Wijzigingen toegepast met fouten",
|
||||||
"ApplyCompletionMessageVerificationUnavailable": "Er zijn geen fouten gemeld, maar Win11Debloat kon niet controleren of alle geselecteerde apps zijn verwijderd.",
|
"ApplyCompletionMessageVerificationUnavailable": "Er zijn geen fouten gemeld, maar Win11Debloat kon niet controleren of alle geselecteerde apps zijn verwijderd.",
|
||||||
|
"WingetUserScopeUninstallDeferred": "WinGet kon {0} niet verwijderen als administrator, omdat de app voor een gebruiker is geïnstalleerd. Start de pc opnieuw op of meld je af en weer aan als {1} om de verwijdering te voltooien.",
|
||||||
"ApplyCompletionMessageFailures_one": "{0} wijziging is mislukt. Zie de console voor meer informatie.",
|
"ApplyCompletionMessageFailures_one": "{0} wijziging is mislukt. Zie de console voor meer informatie.",
|
||||||
"ApplyCompletionMessageFailures_other": "{0} wijzigingen zijn mislukt. Zie de console voor meer informatie.",
|
"ApplyCompletionMessageFailures_other": "{0} wijzigingen zijn mislukt. Zie de console voor meer informatie.",
|
||||||
"ApplyCompletionMessageReady": "Je systeem is gereed. Bedankt voor het gebruik van Win11Debloat!",
|
"ApplyCompletionMessageReady": "Je systeem is gereed. Bedankt voor het gebruik van Win11Debloat!",
|
||||||
|
|||||||
@@ -134,10 +134,12 @@
|
|||||||
"ApplyCompletionTitleSuccess": "Alterações aplicadas",
|
"ApplyCompletionTitleSuccess": "Alterações aplicadas",
|
||||||
"ApplyCompletionMessageDefault": "Observe que algumas alterações só terão efeito após uma reinicialização. Obrigado por usar o Win11Debloat!",
|
"ApplyCompletionMessageDefault": "Observe que algumas alterações só terão efeito após uma reinicialização. Obrigado por usar o Win11Debloat!",
|
||||||
"ApplyRebootRequiredHeader": "Uma reinicialização é necessária para que estas alterações tenham efeito:",
|
"ApplyRebootRequiredHeader": "Uma reinicialização é necessária para que estas alterações tenham efeito:",
|
||||||
|
"ApplyRebootRequiredWingetDeferred": "Desinstalação de {0}",
|
||||||
"ApplyCompletionTitleCancelled": "Cancelado",
|
"ApplyCompletionTitleCancelled": "Cancelado",
|
||||||
"ApplyCompletionMessageCancelled": "A execução do script foi cancelada pelo usuário.",
|
"ApplyCompletionMessageCancelled": "A execução do script foi cancelada pelo usuário.",
|
||||||
"ApplyCompletionTitleErrors": "Alterações aplicadas com erros",
|
"ApplyCompletionTitleErrors": "Alterações aplicadas com erros",
|
||||||
"ApplyCompletionMessageVerificationUnavailable": "Todas as alterações foram aplicadas sem erros, mas o Win11Debloat não conseguiu confirmar que todos os apps selecionados foram desinstalados com sucesso.",
|
"ApplyCompletionMessageVerificationUnavailable": "Todas as alterações foram aplicadas sem erros, mas o Win11Debloat não conseguiu confirmar que todos os apps selecionados foram desinstalados com sucesso.",
|
||||||
|
"WingetUserScopeUninstallDeferred": "O WinGet não conseguiu desinstalar {0} com privilégios de administrador porque ele está instalado para o usuário. Reinicie o PC ou saia e entre novamente como {1} para concluir a remoção.",
|
||||||
"ApplyCompletionMessageFailures_one": "{0} alteração falhou. Veja o console para detalhes.",
|
"ApplyCompletionMessageFailures_one": "{0} alteração falhou. Veja o console para detalhes.",
|
||||||
"ApplyCompletionMessageFailures_other": "{0} alterações falharam. Veja o console para detalhes.",
|
"ApplyCompletionMessageFailures_other": "{0} alterações falharam. Veja o console para detalhes.",
|
||||||
"ApplyCompletionMessageFailures_many": "{0} de alterações falharam. Veja o console para detalhes.",
|
"ApplyCompletionMessageFailures_many": "{0} de alterações falharam. Veja o console para detalhes.",
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ function Remove-SelectedApps {
|
|||||||
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
|
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
|
||||||
$wingetRemovedApps = @()
|
$wingetRemovedApps = @()
|
||||||
$wingetRemovalFailures = @{}
|
$wingetRemovalFailures = @{}
|
||||||
|
if (-not $script:WingetDeferredRemovals) { $script:WingetDeferredRemovals = @{} }
|
||||||
|
|
||||||
Foreach ($app in $appsList) {
|
Foreach ($app in $appsList) {
|
||||||
if ($script:CancelRequested) { return $false }
|
if ($script:CancelRequested) { return $false }
|
||||||
@@ -74,6 +75,10 @@ function Remove-SelectedApps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check whether any winget-removed apps are still present, and report errors for each one.
|
# Check whether any winget-removed apps are still present, and report errors for each one.
|
||||||
|
if ($wingetRemovedApps.Count -gt 0) {
|
||||||
|
$wingetRemovedApps = @($wingetRemovedApps | Where-Object { -not $script:WingetDeferredRemovals.ContainsKey($_) })
|
||||||
|
}
|
||||||
|
|
||||||
if ($wingetRemovedApps.Count -gt 0) {
|
if ($wingetRemovedApps.Count -gt 0) {
|
||||||
$postRemovalList = if ($script:WingetInstalled) { Get-WingetInstalledApps -TimeOut 10 -NonBlocking } else { $null }
|
$postRemovalList = if ($script:WingetInstalled) { Get-WingetInstalledApps -TimeOut 10 -NonBlocking } else { $null }
|
||||||
$edgeForceRemoveRequested = $false
|
$edgeForceRemoveRequested = $false
|
||||||
@@ -122,10 +127,10 @@ function Remove-SelectedApps {
|
|||||||
Runs winget uninstall for a single app, with a bounded execution time.
|
Runs winget uninstall for a single app, with a bounded execution time.
|
||||||
WinGet's own exit code/success reporting is unreliable and is only logged
|
WinGet's own exit code/success reporting is unreliable and is only logged
|
||||||
for diagnostics; it never causes this function to report failure. Callers
|
for diagnostics; it never causes this function to report failure. Callers
|
||||||
verify removal with a post-removal inventory check instead. This function
|
verify removal with a post-removal inventory check instead. If WinGet blocks
|
||||||
only reports failure when the winget invocation itself throws a terminating
|
an elevated uninstall of a user-scope package, removal is deferred to the
|
||||||
error (e.g. it times out or cannot be started). If the User or Sysprep
|
target user's next logon. If the User or Sysprep parameter was passed, this
|
||||||
parameter was passed, also schedules removal for future logins.
|
function also schedules removal for future logins.
|
||||||
|
|
||||||
.PARAMETER app
|
.PARAMETER app
|
||||||
The WinGet package ID to uninstall (e.g. 'Microsoft.BingNews').
|
The WinGet package ID to uninstall (e.g. 'Microsoft.BingNews').
|
||||||
@@ -151,6 +156,7 @@ function Remove-WinGetApp {
|
|||||||
|
|
||||||
$uninstallCommandSucceeded = $true
|
$uninstallCommandSucceeded = $true
|
||||||
$exitCode = $null
|
$exitCode = $null
|
||||||
|
$uninstallDeferred = $false
|
||||||
try {
|
try {
|
||||||
$uninstallResult = Invoke-NonBlocking -ScriptBlock {
|
$uninstallResult = Invoke-NonBlocking -ScriptBlock {
|
||||||
param($appId)
|
param($appId)
|
||||||
@@ -163,6 +169,25 @@ function Remove-WinGetApp {
|
|||||||
Write-WinGetUninstallOutput -Output $(if ($uninstallResult) { $uninstallResult.Output } else { $null })
|
Write-WinGetUninstallOutput -Output $(if ($uninstallResult) { $uninstallResult.Output } else { $null })
|
||||||
$exitCode = if ($uninstallResult) { $uninstallResult.ExitCode } else { 'unknown' }
|
$exitCode = if ($uninstallResult) { $uninstallResult.ExitCode } else { 'unknown' }
|
||||||
Write-Verbose "WinGet uninstall for $app returned exit code $exitCode."
|
Write-Verbose "WinGet uninstall for $app returned exit code $exitCode."
|
||||||
|
|
||||||
|
# WinGet reports APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_ACTION_PROHIBITED (0x8A15007D).
|
||||||
|
# Keep the English message check as a fallback for clients that don't return the specific code.
|
||||||
|
$userScopeBlocked = ([string]$exitCode -eq '-1978335107') -or ([string]$exitCode -match '^0x0?8A15007D$')
|
||||||
|
if (-not $userScopeBlocked) {
|
||||||
|
$userScopeBlocked = @($uninstallResult.Output | Where-Object {
|
||||||
|
$null -ne $_ -and $_.ToString() -match 'package installed for user scope cannot be uninstalled when running with administrator privileges'
|
||||||
|
}).Count -gt 0
|
||||||
|
}
|
||||||
|
if ($userScopeBlocked) {
|
||||||
|
$targetUserName = Get-RunOnceWingetTargetUserName
|
||||||
|
|
||||||
|
$uninstallDeferred = Set-RunOnceWingetTask -appId $app
|
||||||
|
if ($uninstallDeferred) {
|
||||||
|
if (-not $script:WingetDeferredRemovals) { $script:WingetDeferredRemovals = @{} }
|
||||||
|
$script:WingetDeferredRemovals[$app] = $targetUserName
|
||||||
|
Write-Host (Get-Translation -Key 'WingetUserScopeUninstallDeferred' -FormatArgs @($app, $targetUserName)) -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
$uninstallCommandSucceeded = $false
|
$uninstallCommandSucceeded = $false
|
||||||
@@ -175,11 +200,11 @@ function Remove-WinGetApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scheduleSucceeded = $true
|
$scheduleSucceeded = $true
|
||||||
if ($script:Params.ContainsKey("User")) {
|
if (-not $uninstallDeferred -and $script:Params.ContainsKey("User")) {
|
||||||
Write-Host "Adding scheduled task to uninstall $app for user $(Get-UserName)..."
|
Write-Host "Adding scheduled task to uninstall $app for user $(Get-UserName)..."
|
||||||
$scheduleSucceeded = Set-RunOnceWingetTask -appId $app
|
$scheduleSucceeded = Set-RunOnceWingetTask -appId $app
|
||||||
}
|
}
|
||||||
elseif ($script:Params.ContainsKey("Sysprep")) {
|
elseif (-not $uninstallDeferred -and $script:Params.ContainsKey("Sysprep")) {
|
||||||
Write-Host "Adding scheduled task to uninstall $app for new users..."
|
Write-Host "Adding scheduled task to uninstall $app for new users..."
|
||||||
$scheduleSucceeded = Set-RunOnceWingetTask -appId $app
|
$scheduleSucceeded = Set-RunOnceWingetTask -appId $app
|
||||||
}
|
}
|
||||||
@@ -343,6 +368,11 @@ function Request-EdgeForceRemove {
|
|||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-RunOnceWingetTargetUserName {
|
||||||
|
if ($script:Params.ContainsKey("Sysprep")) { return "Default" }
|
||||||
|
return Get-UserName
|
||||||
|
}
|
||||||
|
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Dynamically sets a RunOnce registry key to schedule a winget uninstall.
|
Dynamically sets a RunOnce registry key to schedule a winget uninstall.
|
||||||
@@ -364,7 +394,7 @@ function Request-EdgeForceRemove {
|
|||||||
function Set-RunOnceWingetTask {
|
function Set-RunOnceWingetTask {
|
||||||
param([string]$appId)
|
param([string]$appId)
|
||||||
|
|
||||||
$targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") }
|
$targetUserName = Get-RunOnceWingetTargetUserName
|
||||||
|
|
||||||
# Sanitize appId for use in registry value names (backslashes are path separators)
|
# Sanitize appId for use in registry value names (backslashes are path separators)
|
||||||
$safeAppId = $appId.Replace('\', '_')
|
$safeAppId = $appId.Replace('\', '_')
|
||||||
|
|||||||
@@ -351,6 +351,7 @@ function Invoke-AllChanges {
|
|||||||
$script:AppRemovalFailures = 0
|
$script:AppRemovalFailures = 0
|
||||||
$script:FeatureFailures = 0
|
$script:FeatureFailures = 0
|
||||||
$script:AppRemovalVerificationUnavailable = $false
|
$script:AppRemovalVerificationUnavailable = $false
|
||||||
|
$script:WingetDeferredRemovals = @{}
|
||||||
|
|
||||||
# ---- Gather work items ----
|
# ---- Gather work items ----
|
||||||
$applyIds = @()
|
$applyIds = @()
|
||||||
|
|||||||
@@ -184,6 +184,21 @@ function Show-ApplyModal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($script:WingetDeferredRemovals -and $script:WingetDeferredRemovals.Count -gt 0) {
|
||||||
|
foreach ($appId in @($script:WingetDeferredRemovals.Keys | Sort-Object)) {
|
||||||
|
$tb = [System.Windows.Controls.TextBlock]::new()
|
||||||
|
$appName = Get-Translation -Key $appId -Field 'FriendlyName' -Section 'Apps'
|
||||||
|
$label = Get-Translation -Key 'ApplyRebootRequiredWingetDeferred' -FormatArgs @($appName)
|
||||||
|
$tb.Text = "$([char]0x2022) $label"
|
||||||
|
$tb.FontSize = 12
|
||||||
|
$tb.SetResourceReference([System.Windows.Controls.TextBlock]::ForegroundProperty, "AppFgColor")
|
||||||
|
$tb.Opacity = 0.85
|
||||||
|
$tb.Margin = [System.Windows.Thickness]::new(0, 2, 0, 0)
|
||||||
|
$applyRebootList.Children.Add($tb) | Out-Null
|
||||||
|
}
|
||||||
|
$applyRebootPanel.Visibility = 'Visible'
|
||||||
|
}
|
||||||
|
|
||||||
$applyWindow.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action]{})
|
$applyWindow.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action]{})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ BeforeAll {
|
|||||||
function Test-AppInWingetList { param($appId, $InstalledList) $false }
|
function Test-AppInWingetList { param($appId, $InstalledList) $false }
|
||||||
function Invoke-NonBlocking { param($ScriptBlock, $ArgumentList, $TimeoutSeconds) }
|
function Invoke-NonBlocking { param($ScriptBlock, $ArgumentList, $TimeoutSeconds) }
|
||||||
function Get-UserName { 'Alice' }
|
function Get-UserName { 'Alice' }
|
||||||
|
function Get-Translation { param($Key, $FormatArgs) if ($FormatArgs) { return "$Key $($FormatArgs -join ' ')" }; return $Key }
|
||||||
function Invoke-ForceRemoveEdge {}
|
function Invoke-ForceRemoveEdge {}
|
||||||
function Show-MessageBox { 'No' }
|
function Show-MessageBox { 'No' }
|
||||||
function Invoke-WithTargetUserHive { param($TargetUserName, $ScriptBlock, $ArgumentObject) }
|
function Invoke-WithTargetUserHive { param($TargetUserName, $ScriptBlock, $ArgumentObject) }
|
||||||
@@ -20,6 +21,7 @@ Describe 'Remove-SelectedApps' {
|
|||||||
$script:CancelRequested = $false
|
$script:CancelRequested = $false
|
||||||
$script:ApplySubStepCallback = $null
|
$script:ApplySubStepCallback = $null
|
||||||
$script:WingetInstalled = $true
|
$script:WingetInstalled = $true
|
||||||
|
$script:WingetDeferredRemovals = @{}
|
||||||
$script:AppRemovalFailures = 0
|
$script:AppRemovalFailures = 0
|
||||||
$script:AppRemovalVerificationUnavailable = $false
|
$script:AppRemovalVerificationUnavailable = $false
|
||||||
Mock Get-TargetUserForAppRemoval { 'AllUsers' }
|
Mock Get-TargetUserForAppRemoval { 'AllUsers' }
|
||||||
@@ -54,6 +56,18 @@ Describe 'Remove-SelectedApps' {
|
|||||||
Should -Invoke Test-AppInWingetList -Times 1 -Exactly -ParameterFilter { $appId -eq 'Winget.App' -and $InstalledList[0].Id -eq 'Other.App' }
|
Should -Invoke Test-AppInWingetList -Times 1 -Exactly -ParameterFilter { $appId -eq 'Winget.App' -and $InstalledList[0].Id -eq 'Other.App' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
It 'skips immediate inventory verification for a deferred WinGet uninstall' {
|
||||||
|
Mock Get-AppRemovalMethod { 'WinGet' }
|
||||||
|
Mock Remove-WinGetApp {
|
||||||
|
$script:WingetDeferredRemovals[$app] = 'Alice'
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-SelectedApps -appsList @('Winget.App') | Should -BeTrue
|
||||||
|
|
||||||
|
Should -Invoke Get-WingetInstalledApps -Times 0 -Exactly
|
||||||
|
}
|
||||||
|
|
||||||
It 'stops before the first removal when cancellation is requested' {
|
It 'stops before the first removal when cancellation is requested' {
|
||||||
$script:CancelRequested = $true
|
$script:CancelRequested = $true
|
||||||
Remove-SelectedApps -appsList @('One.App')
|
Remove-SelectedApps -appsList @('One.App')
|
||||||
@@ -157,6 +171,7 @@ Describe 'Remove-WinGetApp' {
|
|||||||
BeforeEach {
|
BeforeEach {
|
||||||
$script:Params = @{}
|
$script:Params = @{}
|
||||||
$script:WingetInstalled = $true
|
$script:WingetInstalled = $true
|
||||||
|
$script:WingetDeferredRemovals = @{}
|
||||||
Mock Invoke-NonBlocking { [PSCustomObject]@{ Success = $true; ExitCode = 0; Output = @() } }
|
Mock Invoke-NonBlocking { [PSCustomObject]@{ Success = $true; ExitCode = 0; Output = @() } }
|
||||||
Mock Set-RunOnceWingetTask { $true }
|
Mock Set-RunOnceWingetTask { $true }
|
||||||
Mock Get-UserName { 'Alice' }
|
Mock Get-UserName { 'Alice' }
|
||||||
@@ -196,6 +211,17 @@ Describe 'Remove-WinGetApp' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
It 'defers a user-scope uninstall when WinGet returns the administrator-context error code' {
|
||||||
|
Mock Invoke-NonBlocking { [PSCustomObject]@{ ExitCode = -1978335107; Output = @('localized WinGet error') } }
|
||||||
|
|
||||||
|
Remove-WinGetApp -app 'One.App' | Should -BeTrue
|
||||||
|
|
||||||
|
Should -Invoke Set-RunOnceWingetTask -Times 1 -Exactly -ParameterFilter {
|
||||||
|
$appId -eq 'One.App'
|
||||||
|
}
|
||||||
|
$script:WingetDeferredRemovals['One.App'] | Should -Be 'Alice'
|
||||||
|
}
|
||||||
|
|
||||||
It 'reports a timed-out winget uninstall and continues' {
|
It 'reports a timed-out winget uninstall and continues' {
|
||||||
$script:Params = @{ User = 'Alice' }
|
$script:Params = @{ User = 'Alice' }
|
||||||
Mock Invoke-NonBlocking { throw 'Operation timed out after 120 seconds' }
|
Mock Invoke-NonBlocking { throw 'Operation timed out after 120 seconds' }
|
||||||
|
|||||||
Reference in New Issue
Block a user