Compare commits

..
13 changed files with 67 additions and 101 deletions
-2
View File
@@ -134,12 +134,10 @@
"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!",
+7
View File
@@ -202,6 +202,13 @@
"UndoLabel": "Enable AI features in Microsoft Edge", "UndoLabel": "Enable AI features in Microsoft Edge",
"ApplyUndoText": "Enabling AI features in Microsoft Edge" "ApplyUndoText": "Enabling AI features in Microsoft Edge"
}, },
"DisableChromeAI": {
"Label": "Disable AI features in Google Chrome",
"ToolTip": "Disables Google Chrome's built-in AI tools and integrations, including Gemini, AI search & suggestions, writing assistance, autofill and more. It also removes local AI models. This feature uses policies, which will lock down certain settings.",
"ApplyText": "Disabling AI features in Google Chrome",
"UndoLabel": "Enable AI features in Google Chrome",
"ApplyUndoText": "Enabling AI features in Google Chrome"
},
"DisablePaintAI": { "DisablePaintAI": {
"Label": "Disable AI features in Paint", "Label": "Disable AI features in Paint",
"ToolTip": "This will turn off AI features in Paint, such as the AI-powered image generation and editing tools. This feature uses policies, which will lock down certain settings.", "ToolTip": "This will turn off AI features in Paint, such as the AI-powered image generation and editing tools. This feature uses policies, which will lock down certain settings.",
-2
View File
@@ -125,12 +125,10 @@
"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!",
+1 -3
View File
@@ -124,13 +124,11 @@
"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": "De volgende wijzigingen vereisen een herstart:", "ApplyRebootRequiredHeader": "Start de computer opnieuw op om de volgende wijzigingen te activeren:",
"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!",
-2
View File
@@ -134,12 +134,10 @@
"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.",
+11 -6
View File
@@ -157,18 +157,23 @@
<Setter Property="Margin" Value="8,0,8,0"/> <Setter Property="Margin" Value="8,0,8,0"/>
</Style> </Style>
<!-- Column widths for the app table rows and header (dot | name | description | id) --> <!-- Column widths for the app table rows and header (indicator | name | description | id) -->
<GridLength x:Key="AppTableDotColWidth">16</GridLength> <GridLength x:Key="AppTableDotColWidth">16</GridLength>
<GridLength x:Key="AppTableNameColWidth">180</GridLength> <GridLength x:Key="AppTableNameColWidth">180</GridLength>
<GridLength x:Key="AppTableDescColWidth">1*</GridLength> <GridLength x:Key="AppTableDescColWidth">1*</GridLength>
<GridLength x:Key="AppTableIdColWidth">261</GridLength> <GridLength x:Key="AppTableIdColWidth">261</GridLength>
<!-- Recommendation dot shape style for app table rows --> <!-- Recommendation indicator style shared by circles, diamonds, and triangles -->
<Style x:Key="AppRecommendationDotStyle" TargetType="Ellipse"> <Style x:Key="AppRecommendationIndicatorStyle" TargetType="Shape">
<Setter Property="Width" Value="9"/> <Setter Property="Width" Value="9"/>
<Setter Property="Height" Value="9"/> <Setter Property="Height" Value="9"/>
<Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform Y="1"/>
</Setter.Value>
</Setter>
</Style> </Style>
<!-- Container style for each dynamically-created app table row --> <!-- Container style for each dynamically-created app table row -->
@@ -184,7 +189,7 @@
<SolidColorBrush x:Key="ValidationErrorColor" Color="#c42b1c"/> <SolidColorBrush x:Key="ValidationErrorColor" Color="#c42b1c"/>
<SolidColorBrush x:Key="ValidationSuccessColor" Color="#28a745"/> <SolidColorBrush x:Key="ValidationSuccessColor" Color="#28a745"/>
<!-- App recommendation dot / legend colors --> <!-- App recommendation indicator / legend colors -->
<SolidColorBrush x:Key="AppRecommendationSafeColor" Color="#4CAF50"/> <SolidColorBrush x:Key="AppRecommendationSafeColor" Color="#4CAF50"/>
<SolidColorBrush x:Key="AppRecommendationOptionalColor" Color="#FFC107"/> <SolidColorBrush x:Key="AppRecommendationOptionalColor" Color="#FFC107"/>
<SolidColorBrush x:Key="AppRecommendationUnsafeColor" Color="#F44336"/> <SolidColorBrush x:Key="AppRecommendationUnsafeColor" Color="#F44336"/>
@@ -710,12 +715,12 @@
</StackPanel> </StackPanel>
<!-- Optional (default) --> <!-- Optional (default) -->
<StackPanel Orientation="Horizontal" Margin="0,0,16,0" ToolTip="%LANG:AppLegendOptionalTooltip%"> <StackPanel Orientation="Horizontal" Margin="0,0,16,0" ToolTip="%LANG:AppLegendOptionalTooltip%">
<Ellipse Width="9" Height="9" Fill="{DynamicResource AppRecommendationOptionalColor}" VerticalAlignment="Center" Margin="0,0,6,0"/> <Polygon Points="4.5,0 9,4.5 4.5,9 0,4.5" Stretch="Fill" Width="9" Height="9" Fill="{DynamicResource AppRecommendationOptionalColor}" VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Text="%LANG:AppLegendOptional%" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/> <TextBlock Text="%LANG:AppLegendOptional%" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/>
</StackPanel> </StackPanel>
<!-- Not Recommended (unsafe) --> <!-- Not Recommended (unsafe) -->
<StackPanel Orientation="Horizontal" ToolTip="%LANG:AppLegendNotRecommendedTooltip%"> <StackPanel Orientation="Horizontal" ToolTip="%LANG:AppLegendNotRecommendedTooltip%">
<Ellipse Width="9" Height="9" Fill="{DynamicResource AppRecommendationUnsafeColor}" VerticalAlignment="Center" Margin="0,0,6,0"/> <Polygon Points="0,9 4.5,0 9,9" Stretch="Fill" Width="9" Height="9" Fill="{DynamicResource AppRecommendationUnsafeColor}" VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBlock Text="%LANG:AppLegendNotRecommended%" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/> <TextBlock Text="%LANG:AppLegendNotRecommended%" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
+7 -37
View File
@@ -43,7 +43,6 @@ 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 }
@@ -75,10 +74,6 @@ 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
@@ -127,10 +122,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. If WinGet blocks verify removal with a post-removal inventory check instead. This function
an elevated uninstall of a user-scope package, removal is deferred to the only reports failure when the winget invocation itself throws a terminating
target user's next logon. If the User or Sysprep parameter was passed, this error (e.g. it times out or cannot be started). If the User or Sysprep
function also schedules removal for future logins. parameter was passed, 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').
@@ -156,7 +151,6 @@ 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)
@@ -169,25 +163,6 @@ 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
@@ -200,11 +175,11 @@ function Remove-WinGetApp {
} }
$scheduleSucceeded = $true $scheduleSucceeded = $true
if (-not $uninstallDeferred -and $script:Params.ContainsKey("User")) { if ($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 (-not $uninstallDeferred -and $script:Params.ContainsKey("Sysprep")) { elseif ($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
} }
@@ -368,11 +343,6 @@ 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.
@@ -394,7 +364,7 @@ function Get-RunOnceWingetTargetUserName {
function Set-RunOnceWingetTask { function Set-RunOnceWingetTask {
param([string]$appId) param([string]$appId)
$targetUserName = Get-RunOnceWingetTargetUserName $targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") }
# 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('\', '_')
-1
View File
@@ -351,7 +351,6 @@ 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 = @()
+21 -7
View File
@@ -431,7 +431,7 @@ function Add-AppsToMainWindow {
$checkbox.IsChecked = $app.IsChecked $checkbox.IsChecked = $app.IsChecked
$checkbox.Style = $Window.Resources['AppsPanelCheckBoxStyle'] $checkbox.Style = $Window.Resources['AppsPanelCheckBoxStyle']
# Build table row: Recommendation dot | Name | Description | App ID # Build table row: Recommendation indicator | Name | Description | App ID
$row = New-Object System.Windows.Controls.Grid $row = New-Object System.Windows.Controls.Grid
$row.Style = $Window.Resources['AppTableRowStyle'] $row.Style = $Window.Resources['AppTableRowStyle']
$c0 = New-Object System.Windows.Controls.ColumnDefinition; $c0.Width = $Window.Resources['AppTableDotColWidth'] $c0 = New-Object System.Windows.Controls.ColumnDefinition; $c0.Width = $Window.Resources['AppTableDotColWidth']
@@ -441,16 +441,30 @@ function Add-AppsToMainWindow {
$row.ColumnDefinitions.Add($c0); $row.ColumnDefinitions.Add($c1) $row.ColumnDefinitions.Add($c0); $row.ColumnDefinitions.Add($c1)
$row.ColumnDefinitions.Add($c2); $row.ColumnDefinitions.Add($c3) $row.ColumnDefinitions.Add($c2); $row.ColumnDefinitions.Add($c3)
$dot = New-Object System.Windows.Shapes.Ellipse $indicator = switch ($app.Recommendation) {
$dot.Style = $Window.Resources['AppRecommendationDotStyle'] 'safe' { New-Object System.Windows.Shapes.Ellipse }
$dot.Fill = switch ($app.Recommendation) { 'safe' { $brushSafe } 'unsafe' { $brushUnsafe } default { $brushDefault } } 'unsafe' {
$triangle = New-Object System.Windows.Shapes.Polygon
$triangle.Points = [System.Windows.Media.PointCollection]::Parse('0,9 4.5,0 9,9')
$triangle.Stretch = 'Fill'
$triangle
}
default {
$diamond = New-Object System.Windows.Shapes.Polygon
$diamond.Points = [System.Windows.Media.PointCollection]::Parse('4.5,0 9,4.5 4.5,9 0,4.5')
$diamond.Stretch = 'Fill'
$diamond
}
}
$indicator.Style = $Window.Resources['AppRecommendationIndicatorStyle']
$indicator.Fill = switch ($app.Recommendation) { 'safe' { $brushSafe } 'unsafe' { $brushUnsafe } default { $brushDefault } }
$recommendationKey = switch ($app.Recommendation) { $recommendationKey = switch ($app.Recommendation) {
'safe' { 'AppLegendRecommendedTooltip' } 'safe' { 'AppLegendRecommendedTooltip' }
'unsafe' { 'AppLegendNotRecommendedTooltip' } 'unsafe' { 'AppLegendNotRecommendedTooltip' }
default { 'AppLegendOptionalTooltip' } default { 'AppLegendOptionalTooltip' }
} }
$dot.SetResourceReference([System.Windows.FrameworkElement]::ToolTipProperty, "Language_$recommendationKey") $indicator.SetResourceReference([System.Windows.FrameworkElement]::ToolTipProperty, "Language_$recommendationKey")
[System.Windows.Controls.Grid]::SetColumn($dot, 0) [System.Windows.Controls.Grid]::SetColumn($indicator, 0)
$tbName = New-Object System.Windows.Controls.TextBlock $tbName = New-Object System.Windows.Controls.TextBlock
$tbName.Text = $app.FriendlyName $tbName.Text = $app.FriendlyName
@@ -469,7 +483,7 @@ function Add-AppsToMainWindow {
$tbId.ToolTip = $app.AppIdDisplay $tbId.ToolTip = $app.AppIdDisplay
[System.Windows.Controls.Grid]::SetColumn($tbId, 3) [System.Windows.Controls.Grid]::SetColumn($tbId, 3)
$row.Children.Add($dot) | Out-Null $row.Children.Add($indicator) | Out-Null
$row.Children.Add($tbName) | Out-Null $row.Children.Add($tbName) | Out-Null
$row.Children.Add($tbDesc) | Out-Null $row.Children.Add($tbDesc) | Out-Null
$row.Children.Add($tbId) | Out-Null $row.Children.Add($tbId) | Out-Null
-15
View File
@@ -184,21 +184,6 @@ 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 {
+15
View File
@@ -89,4 +89,19 @@ Describe 'FeatureId parameter contracts' {
$missing | Should -HaveCount 0 -Because ($missing -join '; ') $missing | Should -HaveCount 0 -Because ($missing -join '; ')
} }
It 'has an en-US Features translation with a Label for every FeatureId' {
$enUsPath = Join-Path $script:RepoRoot 'Config\Languages\en-US\Features.json'
$translations = (Get-Content -LiteralPath $enUsPath -Raw | ConvertFrom-Json).Features
$missing = @(
foreach ($feature in $script:Features) {
$entry = $translations.PSObject.Properties[$feature.FeatureId]
if ($null -eq $entry -or [string]::IsNullOrWhiteSpace($entry.Value.Label)) {
$feature.FeatureId
}
}
)
$missing | Should -HaveCount 0 -Because ($missing -join ', ')
}
} }
-26
View File
@@ -4,7 +4,6 @@ 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) }
@@ -21,7 +20,6 @@ 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' }
@@ -56,18 +54,6 @@ 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')
@@ -171,7 +157,6 @@ 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' }
@@ -211,17 +196,6 @@ 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' }
+5
View File
@@ -9,6 +9,11 @@ BeforeAll {
} }
Describe 'Get-StartMenuUserNameFromPath' { Describe 'Get-StartMenuUserNameFromPath' {
BeforeEach {
# Restore copies are named to the second; clear them so tests sharing $TestDrive cannot see each other's copy
Get-ChildItem -LiteralPath $TestDrive -Filter 'Win11Debloat-StartRestore-*.bak' | Remove-Item -Force
}
It 'extracts a user name from a start-menu path and falls back for unknown paths' { It 'extracts a user name from a start-menu path and falls back for unknown paths' {
Get-StartMenuUserNameFromPath -StartMenuBinFile 'C:\Users\Alice\AppData\Local\Packages\Start\start2.bin' | Should -Be 'Alice' Get-StartMenuUserNameFromPath -StartMenuBinFile 'C:\Users\Alice\AppData\Local\Packages\Start\start2.bin' | Should -Be 'Alice'
Get-StartMenuUserNameFromPath -StartMenuBinFile 'C:\Temp\start2.bin' | Should -Be 'unknown' Get-StartMenuUserNameFromPath -StartMenuBinFile 'C:\Temp\start2.bin' | Should -Be 'unknown'