Add support for multiple AppIds for app removal (#526)

This commit is contained in:
Jeffrey
2026-03-23 22:59:04 +01:00
committed by GitHub
parent edd815fdbb
commit e05af92acc
7 changed files with 85 additions and 50 deletions

View File

@@ -647,7 +647,7 @@
}, },
{ {
"FriendlyName": "Microsoft Edge", "FriendlyName": "Microsoft Edge",
"AppId": "Microsoft.Edge", "AppId": ["Microsoft.Edge", "XPFFTQ037JWMHS"],
"Description": "Windows' default browser, WARNING: Removing this app also removes the only browser from Windows Sandbox and could affect other apps", "Description": "Windows' default browser, WARNING: Removing this app also removes the only browser from Windows Sandbox and could affect other apps",
"SelectedByDefault": false, "SelectedByDefault": false,
"Recommendation": "unsafe" "Recommendation": "unsafe"

View File

@@ -9,6 +9,9 @@ function RemoveApps {
$appIndex = 0 $appIndex = 0
$appCount = @($appsList).Count $appCount = @($appsList).Count
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
$edgeUninstallSucceeded = $false
$edgeScheduledTaskAdded = $false
Foreach ($app in $appsList) { Foreach ($app in $appsList) {
if ($script:CancelRequested) { if ($script:CancelRequested) {
@@ -25,20 +28,27 @@ function RemoveApps {
Write-Host "Attempting to remove $app..." Write-Host "Attempting to remove $app..."
# Use WinGet only to remove OneDrive and Edge # Use WinGet only to remove OneDrive and Edge
if (($app -eq "Microsoft.OneDrive") -or ($app -eq "Microsoft.Edge")) { if (($app -eq "Microsoft.OneDrive") -or ($edgeIds -contains $app)) {
if ($script:WingetInstalled -eq $false) { if ($script:WingetInstalled -eq $false) {
Write-Host "WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red Write-Host "WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
continue continue
} }
$appName = $app -replace '\.', '_' $isEdgeId = $edgeIds -contains $app
$appName = if ($isEdgeId) { 'Microsoft_Edge' } else { $app -replace '\.', '_' }
# Uninstall app via WinGet, or create a scheduled task to uninstall it later # Uninstall app via WinGet, or create a scheduled task to uninstall it later
if ($script:Params.ContainsKey("User")) { if ($script:Params.ContainsKey("User")) {
ImportRegistryFile "Adding scheduled task to uninstall $app for user $(GetUserName)..." "Uninstall_$($appName).reg" if (-not ($isEdgeId -and $edgeScheduledTaskAdded)) {
ImportRegistryFile "Adding scheduled task to uninstall $app for user $(GetUserName)..." "Uninstall_$($appName).reg"
if ($isEdgeId) { $edgeScheduledTaskAdded = $true }
}
} }
elseif ($script:Params.ContainsKey("Sysprep")) { elseif ($script:Params.ContainsKey("Sysprep")) {
ImportRegistryFile "Adding scheduled task to uninstall $app after for new users..." "Uninstall_$($appName).reg" if (-not ($isEdgeId -and $edgeScheduledTaskAdded)) {
ImportRegistryFile "Adding scheduled task to uninstall $app after for new users..." "Uninstall_$($appName).reg"
if ($isEdgeId) { $edgeScheduledTaskAdded = $true }
}
} }
else { else {
# Uninstall app via WinGet # Uninstall app via WinGet
@@ -47,21 +57,35 @@ function RemoveApps {
winget uninstall --accept-source-agreements --disable-interactivity --id $appId winget uninstall --accept-source-agreements --disable-interactivity --id $appId
} -ArgumentList $app } -ArgumentList $app
If (($app -eq "Microsoft.Edge") -and (Select-String -InputObject $wingetOutput -Pattern "Uninstall failed with exit code")) { $wingetFailed = Select-String -InputObject $wingetOutput -Pattern "Uninstall failed with exit code|No installed package found matching input criteria|No package found matching input criteria" -SimpleMatch:$false
Write-Host "Unable to uninstall Microsoft Edge via WinGet" -ForegroundColor Red if ($isEdgeId) {
if (-not $wingetFailed) {
$edgeUninstallSucceeded = $true
}
if ($script:GuiWindow) { # Prompt immediately after the final selected Edge ID attempt (if all attempts failed)
$result = Show-MessageBox -Message 'Unable to uninstall Microsoft Edge via WinGet. Would you like to forcefully uninstall it? NOT RECOMMENDED!' -Title 'Force Uninstall Microsoft Edge?' -Button 'YesNo' -Icon 'Warning' $hasRemainingEdgeIds = $false
if ($appIndex -lt $appCount) {
$remainingApps = @($appsList)[($appIndex)..($appCount - 1)]
$hasRemainingEdgeIds = @($remainingApps | Where-Object { $edgeIds -contains $_ }).Count -gt 0
}
if ($result -eq 'Yes') { if (-not $hasRemainingEdgeIds -and -not $edgeUninstallSucceeded) {
Write-Host "Unable to uninstall Microsoft Edge via WinGet" -ForegroundColor Red
if ($script:GuiWindow) {
$result = Show-MessageBox -Message 'Unable to uninstall Microsoft Edge via WinGet. Would you like to forcefully uninstall it? NOT RECOMMENDED!' -Title 'Force Uninstall Microsoft Edge?' -Button 'YesNo' -Icon 'Warning'
if ($result -eq 'Yes') {
Write-Host ""
ForceRemoveEdge
}
}
elseif ($( Read-Host -Prompt "Would you like to forcefully uninstall Microsoft Edge? NOT RECOMMENDED! (y/n)" ) -eq 'y') {
Write-Host "" Write-Host ""
ForceRemoveEdge ForceRemoveEdge
} }
} }
elseif ($( Read-Host -Prompt "Would you like to forcefully uninstall Microsoft Edge? NOT RECOMMENDED! (y/n)" ) -eq 'y') {
Write-Host ""
ForceRemoveEdge
}
} }
} }

View File

@@ -16,24 +16,36 @@ function LoadAppsDetailsFromJson {
} }
foreach ($appData in $jsonContent.Apps) { foreach ($appData in $jsonContent.Apps) {
$appId = $appData.AppId.Trim() # Handle AppId as array (could be single or multiple IDs)
if ($appId.length -eq 0) { continue } $appIdArray = if ($appData.AppId -is [array]) { $appData.AppId } else { @($appData.AppId) }
$appIdArray = $appIdArray | ForEach-Object { $_.Trim() } | Where-Object { $_.length -gt 0 }
if ($appIdArray.Count -eq 0) { continue }
if ($OnlyInstalled) { if ($OnlyInstalled) {
if (-not ($InstalledList -like ("*$appId*")) -and -not (Get-AppxPackage -Name $appId)) { $isInstalled = $false
continue foreach ($appId in $appIdArray) {
} if (($InstalledList -like ("*$appId*")) -or (Get-AppxPackage -Name $appId)) {
if (($appId -eq "Microsoft.Edge") -and -not ($InstalledList -like "* Microsoft.Edge *")) { $isInstalled = $true
continue break
}
if (($appId -eq "Microsoft.Edge") -and ($InstalledList -like "* Microsoft.Edge *")) {
$isInstalled = $true
break
}
} }
if (-not $isInstalled) { continue }
} }
$friendlyName = if ($appData.FriendlyName) { $appData.FriendlyName } else { $appId } # Use first AppId for fallback names, join all for display
$displayName = if ($appData.FriendlyName) { "$($appData.FriendlyName) ($appId)" } else { $appId } $primaryAppId = $appIdArray[0]
$appIdDisplay = $appIdArray -join ', '
$friendlyName = if ($appData.FriendlyName) { $appData.FriendlyName } else { $primaryAppId }
$displayName = if ($appData.FriendlyName) { "$($appData.FriendlyName) ($appIdDisplay)" } else { $appIdDisplay }
$isChecked = if ($InitialCheckedFromJson) { $appData.SelectedByDefault } else { $false } $isChecked = if ($InitialCheckedFromJson) { $appData.SelectedByDefault } else { $false }
$apps += [PSCustomObject]@{ $apps += [PSCustomObject]@{
AppId = $appId AppId = $appIdArray
AppIdDisplay = $appIdDisplay
FriendlyName = $friendlyName FriendlyName = $friendlyName
DisplayName = $displayName DisplayName = $displayName
IsChecked = $isChecked IsChecked = $isChecked

View File

@@ -16,10 +16,12 @@ function LoadAppsFromFile {
# JSON file format # JSON file format
$jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json $jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json
Foreach ($appData in $jsonContent.Apps) { Foreach ($appData in $jsonContent.Apps) {
$appId = $appData.AppId.Trim() # Handle AppId as array (could be single or multiple IDs)
$appIdArray = if ($appData.AppId -is [array]) { $appData.AppId } else { @($appData.AppId) }
$appIdArray = $appIdArray | ForEach-Object { $_.Trim() } | Where-Object { $_.length -gt 0 }
$selectedByDefault = $appData.SelectedByDefault $selectedByDefault = $appData.SelectedByDefault
if ($selectedByDefault -and $appId.length -gt 0) { if ($selectedByDefault -and $appIdArray.Count -gt 0) {
$appsList += $appId $appsList += $appIdArray
} }
} }
} }

View File

@@ -4,7 +4,7 @@ function ValidateAppslist {
$appsList $appsList
) )
$supportedAppsList = (LoadAppsDetailsFromJson | ForEach-Object { $_.AppId }) $supportedAppsList = @(LoadAppsDetailsFromJson | ForEach-Object { @($_.AppId) }) | ForEach-Object { $_.Trim() } | Where-Object { $_.Length -gt 0 }
$validatedAppsList = @() $validatedAppsList = @()
# Validate provided appsList against supportedAppsList # Validate provided appsList against supportedAppsList

View File

@@ -75,7 +75,8 @@ function Show-AppSelectionWindow {
$checkbox = New-Object System.Windows.Controls.CheckBox $checkbox = New-Object System.Windows.Controls.CheckBox
$checkbox.Content = $_.DisplayName $checkbox.Content = $_.DisplayName
$checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $_.DisplayName) $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $_.DisplayName)
$checkbox.Tag = $_.AppId $checkbox.Tag = $_.AppIdDisplay
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIds' -Value @($_.AppId)
$checkbox.IsChecked = $_.IsChecked $checkbox.IsChecked = $_.IsChecked
$checkbox.ToolTip = $_.Description $checkbox.ToolTip = $_.Description
$checkbox.Style = $window.Resources["AppsPanelCheckBoxStyle"] $checkbox.Style = $window.Resources["AppsPanelCheckBoxStyle"]
@@ -118,9 +119,10 @@ function Show-AppSelectionWindow {
$selectedApps = @() $selectedApps = @()
foreach ($child in $appsPanel.Children) { foreach ($child in $appsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) { if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
$selectedApps += $child.Tag $selectedApps += @($child.AppIds)
} }
} }
$selectedApps = @($selectedApps | Where-Object { $_ } | Select-Object -Unique)
# Close form without saving if no apps were selected # Close form without saving if no apps were selected
if ($selectedApps.Count -eq 0) { if ($selectedApps.Count -eq 0) {

View File

@@ -241,7 +241,7 @@ function Show-MainWindow {
$check = ($this.IsChecked -eq $true) $check = ($this.IsChecked -eq $true)
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false } if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
$presetIds = $this.PresetAppIds $presetIds = $this.PresetAppIds
ApplyPresetToApps -MatchFilter { param($c) $presetIds -contains $c.Tag }.GetNewClosure() -Check $check ApplyPresetToApps -MatchFilter { param($c) (@($c.AppIds) | Where-Object { $presetIds -contains $_ }).Count -gt 0 }.GetNewClosure() -Check $check
}) })
} }
@@ -317,7 +317,7 @@ function Show-MainWindow {
$key = switch ($script:SortColumn) { $key = switch ($script:SortColumn) {
'Name' { { $_.AppName } } 'Name' { { $_.AppName } }
'Description' { { $_.AppDescription } } 'Description' { { $_.AppDescription } }
'AppId' { { $_.Tag } } 'AppId' { { $_.AppIdDisplay } }
} }
$sorted = $children | Sort-Object $key -Descending:(-not $script:SortAscending) $sorted = $children | Sort-Object $key -Descending:(-not $script:SortAscending)
$appsPanel.Children.Clear() $appsPanel.Children.Clear()
@@ -379,14 +379,6 @@ function Show-MainWindow {
function UpdatePresetStates { function UpdatePresetStates {
$script:UpdatingPresets = $true $script:UpdatingPresets = $true
try { try {
# Build a set of currently checked app tags for fast lookup
$checkedTags = @{}
foreach ($child in $appsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
$checkedTags[$child.Tag] = $true
}
}
# Helper: count matching and checked apps, set checkbox state # Helper: count matching and checked apps, set checkbox state
function SetPresetState($checkbox, [scriptblock]$MatchFilter) { function SetPresetState($checkbox, [scriptblock]$MatchFilter) {
$total = 0; $checked = 0 $total = 0; $checked = 0
@@ -394,7 +386,7 @@ function Show-MainWindow {
if ($child -is [System.Windows.Controls.CheckBox]) { if ($child -is [System.Windows.Controls.CheckBox]) {
if (& $MatchFilter $child) { if (& $MatchFilter $child) {
$total++ $total++
if ($checkedTags.ContainsKey($child.Tag)) { $checked++ } if ($child.IsChecked) { $checked++ }
} }
} }
} }
@@ -416,12 +408,12 @@ function Show-MainWindow {
SetPresetState $presetDefaultApps { param($c) $c.SelectedByDefault -eq $true } SetPresetState $presetDefaultApps { param($c) $c.SelectedByDefault -eq $true }
foreach ($jsonCb in $script:JsonPresetCheckboxes) { foreach ($jsonCb in $script:JsonPresetCheckboxes) {
$localIds = $jsonCb.PresetAppIds $localIds = $jsonCb.PresetAppIds
SetPresetState $jsonCb { param($c) $localIds -contains $c.Tag }.GetNewClosure() SetPresetState $jsonCb { param($c) (@($c.AppIds) | Where-Object { $localIds -contains $_ }).Count -gt 0 }.GetNewClosure()
} }
# Last used preset: only update if it's visible (has saved apps) # Last used preset: only update if it's visible (has saved apps)
if ($presetLastUsed.Visibility -ne 'Collapsed' -and $script:SavedAppIds) { if ($presetLastUsed.Visibility -ne 'Collapsed' -and $script:SavedAppIds) {
SetPresetState $presetLastUsed { param($c) $script:SavedAppIds -contains $c.Tag } SetPresetState $presetLastUsed { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 }
} }
} }
finally { finally {
@@ -760,9 +752,9 @@ function Show-MainWindow {
$app = $appsToAdd[$i] $app = $appsToAdd[$i]
$checkbox = New-Object System.Windows.Controls.CheckBox $checkbox = New-Object System.Windows.Controls.CheckBox
$automationName = if ($app.FriendlyName) { $app.FriendlyName } elseif ($app.AppId) { $app.AppId } else { $null } $automationName = if ($app.FriendlyName) { $app.FriendlyName } elseif ($app.AppIdDisplay) { $app.AppIdDisplay } else { $null }
if ($automationName) { $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $automationName) } if ($automationName) { $checkbox.SetValue([System.Windows.Automation.AutomationProperties]::NameProperty, $automationName) }
$checkbox.Tag = $app.AppId $checkbox.Tag = $app.AppIdDisplay
$checkbox.IsChecked = $app.IsChecked $checkbox.IsChecked = $app.IsChecked
$checkbox.Style = $window.Resources['AppsPanelCheckBoxStyle'] $checkbox.Style = $window.Resources['AppsPanelCheckBoxStyle']
@@ -798,9 +790,9 @@ function Show-MainWindow {
[System.Windows.Controls.Grid]::SetColumn($tbDesc, 2) [System.Windows.Controls.Grid]::SetColumn($tbDesc, 2)
$tbId = New-Object System.Windows.Controls.TextBlock $tbId = New-Object System.Windows.Controls.TextBlock
$tbId.Text = $app.AppId $tbId.Text = $app.AppIdDisplay
$tbId.Style = $window.Resources['AppIdTextStyle'] $tbId.Style = $window.Resources["AppIdTextStyle"]
$tbId.ToolTip = $app.AppId $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($dot) | Out-Null
@@ -812,6 +804,8 @@ function Show-MainWindow {
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppName' -Value $app.FriendlyName Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppName' -Value $app.FriendlyName
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescription' -Value $app.Description Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppDescription' -Value $app.Description
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'SelectedByDefault' -Value $app.SelectedByDefault Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'SelectedByDefault' -Value $app.SelectedByDefault
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIds' -Value @($app.AppId)
Add-Member -InputObject $checkbox -MemberType NoteProperty -Name 'AppIdDisplay' -Value $app.AppIdDisplay
$checkbox.Add_Checked({ UpdateAppSelectionStatus }) $checkbox.Add_Checked({ UpdateAppSelectionStatus })
$checkbox.Add_Unchecked({ UpdateAppSelectionStatus }) $checkbox.Add_Unchecked({ UpdateAppSelectionStatus })
@@ -1537,9 +1531,10 @@ function Show-MainWindow {
$selectedApps = @() $selectedApps = @()
foreach ($child in $appsPanel.Children) { foreach ($child in $appsPanel.Children) {
if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) { if ($child -is [System.Windows.Controls.CheckBox] -and $child.IsChecked) {
$selectedApps += $child.Tag $selectedApps += @($child.AppIds)
} }
} }
$selectedApps = @($selectedApps | Where-Object { $_ } | Select-Object -Unique)
if ($selectedApps.Count -gt 0) { if ($selectedApps.Count -gt 0) {
# Check if Microsoft Store is selected # Check if Microsoft Store is selected
@@ -1760,7 +1755,7 @@ function Show-MainWindow {
if ($script:UpdatingPresets) { return } if ($script:UpdatingPresets) { return }
$check = ($this.IsChecked -eq $true) $check = ($this.IsChecked -eq $true)
if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false } if ($this.IsChecked -eq $null) { $this.IsChecked = $false; $check = $false }
ApplyPresetToApps -MatchFilter { param($c) $script:SavedAppIds -contains $c.Tag } -Check $check ApplyPresetToApps -MatchFilter { param($c) (@($c.AppIds) | Where-Object { $script:SavedAppIds -contains $_ }).Count -gt 0 } -Check $check
}) })
} }
else { else {