mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-07-02 22:58:34 +00:00
Refactor: Cleanup app removal, remove legacy app list generator and CustomAppsList file support (#662)
* remove support for uninstalling old sunset apps * Add color legend on app removal screen * Remove legacy app list generator and custom apps file support Replaced by GUI config export/import, dynamic RemovalMethod, and CLI app removal settings saved to LastUsedSettings.json. * Verify app removal by checking actual installation state instead of trusting winget output
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
|||||||
LastSettings
|
|
||||||
SavedSettings
|
|
||||||
LastUsedSettings.json
|
LastUsedSettings.json
|
||||||
CustomAppsList
|
|
||||||
Logs/*
|
Logs/*
|
||||||
Win11Debloat.log
|
|
||||||
Backups/*
|
Backups/*
|
||||||
493
Config/Apps.json
493
Config/Apps.json
File diff suppressed because it is too large
Load Diff
@@ -339,18 +339,6 @@
|
|||||||
"MinVersion": null,
|
"MinVersion": null,
|
||||||
"MaxVersion": null
|
"MaxVersion": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"FeatureId": "RemoveAppsCustom",
|
|
||||||
"Label": "Remove custom selection of apps",
|
|
||||||
"Category": null,
|
|
||||||
"RegistryKey": null,
|
|
||||||
"ApplyText": "Removing selected apps",
|
|
||||||
"UndoLabel": null,
|
|
||||||
"ApplyUndoText": null,
|
|
||||||
"RegistryUndoKey": null,
|
|
||||||
"MinVersion": null,
|
|
||||||
"MaxVersion": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"FeatureId": "RemoveGamingApps",
|
"FeatureId": "RemoveGamingApps",
|
||||||
"Label": "Remove the Xbox App and Xbox Gamebar",
|
"Label": "Remove the Xbox App and Xbox Gamebar",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -184,6 +184,11 @@
|
|||||||
<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 -->
|
||||||
|
<SolidColorBrush x:Key="AppRecommendationSafeColor" Color="#4CAF50"/>
|
||||||
|
<SolidColorBrush x:Key="AppRecommendationOptionalColor" Color="#FFC107"/>
|
||||||
|
<SolidColorBrush x:Key="AppRecommendationUnsafeColor" Color="#F44336"/>
|
||||||
|
|
||||||
<!-- Title Bar Button Style -->
|
<!-- Title Bar Button Style -->
|
||||||
<Style x:Key="TitleBarButton" TargetType="Button">
|
<Style x:Key="TitleBarButton" TargetType="Button">
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
@@ -703,10 +708,32 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Status Info -->
|
<!-- Status Info & Color Legend -->
|
||||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="20,0,20,0">
|
<Grid Grid.Row="1" Margin="20,0">
|
||||||
<TextBlock x:Name="AppSelectionStatus" Text="" Foreground="{DynamicResource AppFgColor}" Margin="10,0,0,5" HorizontalAlignment="Left"/>
|
<Grid.ColumnDefinitions>
|
||||||
</StackPanel>
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock x:Name="AppSelectionStatus" Grid.Column="0" Text="" Foreground="{DynamicResource AppFgColor}" Margin="10,0,0,5" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
|
||||||
|
<StackPanel x:Name="AppColorLegend" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,10,5">
|
||||||
|
<TextBlock Text="Legend:" Foreground="{DynamicResource AppFgColor}" FontSize="12" FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,10,0" Opacity="0.8"/>
|
||||||
|
<!-- Recommended (safe) -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,0,16,0" ToolTip="[Recommended] Safe to remove for most users">
|
||||||
|
<Ellipse Width="9" Height="9" Fill="{DynamicResource AppRecommendationSafeColor}" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||||
|
<TextBlock Text="Recommended" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- Optional (default) -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,0,16,0" ToolTip="[Optional] Can be safely removed if you don't need this app">
|
||||||
|
<Ellipse Width="9" Height="9" Fill="{DynamicResource AppRecommendationOptionalColor}" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||||
|
<TextBlock Text="Optional" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- Not Recommended (unsafe) -->
|
||||||
|
<StackPanel Orientation="Horizontal" ToolTip="[Not Recommended] Only remove if you know what you are doing">
|
||||||
|
<Ellipse Width="9" Height="9" Fill="{DynamicResource AppRecommendationUnsafeColor}" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||||||
|
<TextBlock Text="Not Recommended" Foreground="{DynamicResource AppFgColor}" FontSize="12" VerticalAlignment="Center" Opacity="0.8"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
@@ -1,6 +1,22 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns a list of installed apps from winget as structured objects.
|
||||||
|
|
||||||
# Run winget list and return installed apps.
|
.DESCRIPTION
|
||||||
# Use -NonBlocking to keep the UI responsive (GUI mode) via Invoke-NonBlocking.
|
Runs `winget list` and parses the output into PSCustomObject arrays.
|
||||||
|
Use -NonBlocking to keep the UI responsive in GUI mode; otherwise
|
||||||
|
runs synchronously with an optional timeout.
|
||||||
|
|
||||||
|
.PARAMETER TimeOut
|
||||||
|
Maximum seconds to wait for winget to complete. Default is 10.
|
||||||
|
|
||||||
|
.PARAMETER NonBlocking
|
||||||
|
When set, runs via Invoke-NonBlocking so the GUI thread stays responsive.
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
PSCustomObject[] with Name and Id properties. Returns $null on
|
||||||
|
failure, or an empty array when winget succeeds but lists no apps.
|
||||||
|
#>
|
||||||
function GetInstalledAppsViaWinget {
|
function GetInstalledAppsViaWinget {
|
||||||
param (
|
param (
|
||||||
[int]$TimeOut = 10,
|
[int]$TimeOut = 10,
|
||||||
@@ -11,13 +27,76 @@ function GetInstalledAppsViaWinget {
|
|||||||
|
|
||||||
$fetchBlock = {
|
$fetchBlock = {
|
||||||
param($timeOut)
|
param($timeOut)
|
||||||
$job = Start-Job { return winget list --accept-source-agreements --disable-interactivity }
|
$job = Start-Job {
|
||||||
|
$rawOutput = $null
|
||||||
|
try {
|
||||||
|
$originalEncoding = [Console]::OutputEncoding
|
||||||
|
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
|
||||||
|
try {
|
||||||
|
$rawOutput = winget list --accept-source-agreements --disable-interactivity
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
[Console]::OutputEncoding = $originalEncoding
|
||||||
|
}
|
||||||
|
return $rawOutput
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$done = $job | Wait-Job -Timeout $timeOut
|
$done = $job | Wait-Job -Timeout $timeOut
|
||||||
if ($done) {
|
if ($done) {
|
||||||
$result = Receive-Job -Job $job
|
$result = Receive-Job -Job $job
|
||||||
Remove-Job -Job $job -ErrorAction SilentlyContinue
|
Remove-Job -Job $job -ErrorAction SilentlyContinue
|
||||||
return $result
|
|
||||||
|
if (-not $result) { return $null }
|
||||||
|
|
||||||
|
# winget list outputs:
|
||||||
|
# [progress line] / [blank] / header / --- separator / data rows
|
||||||
|
$textOutput = $result -join "`n"
|
||||||
|
$lines = $textOutput -split "`r`n|`n"
|
||||||
|
|
||||||
|
# Find the separator line to know where data starts
|
||||||
|
$dataStart = -1
|
||||||
|
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||||
|
if ($lines[$i] -match '^-{3,}') {
|
||||||
|
$dataStart = $i + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dataStart -lt 0 -or $dataStart -ge $lines.Count) { return @() }
|
||||||
|
|
||||||
|
$apps = [System.Collections.Generic.List[object]]::new()
|
||||||
|
|
||||||
|
for ($i = $dataStart; $i -lt $lines.Count; $i++) {
|
||||||
|
$line = $lines[$i]
|
||||||
|
if ($line.Trim() -eq '') { continue }
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Split on 2+ spaces; extract Name and Id columns.
|
||||||
|
$fields = [regex]::Split($line.Trim(), '\s{2,}')
|
||||||
|
if ($fields.Count -lt 2) { continue }
|
||||||
|
|
||||||
|
$name = $fields[0].Trim()
|
||||||
|
$id = $fields[1].Trim()
|
||||||
|
|
||||||
|
if (-not $id) { continue }
|
||||||
|
|
||||||
|
$null = $apps.Add([PSCustomObject]@{
|
||||||
|
Name = $name
|
||||||
|
Id = $id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Skip lines that can't be parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @($apps)
|
||||||
}
|
}
|
||||||
|
|
||||||
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
|
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
|
||||||
return $null
|
return $null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Iterates over the provided list of app identifiers and removes each one.
|
Iterates over the provided list of app identifiers and removes each one.
|
||||||
Apps are removed via WinGet (for OneDrive and Microsoft Edge) or via
|
The removal method (winget vs. Appx cmdlets) is determined per-app from
|
||||||
Remove-AppxPackage / Remove-ProvisionedAppxPackage (for all other apps).
|
Apps.json. Microsoft Edge is deferred to the end of the loop so that all
|
||||||
The target scope is determined by script-level parameters:
|
winget attempts run before any force-remove prompt. A scheduled task is
|
||||||
-Sysprep removes from the OS image for future users; -User targets a
|
only created when the User or Sysprep parameter was passed.
|
||||||
specific user; otherwise the current user is targeted.
|
After each winget removal, the system is checked to confirm whether the
|
||||||
|
app is still installed before reporting an error.
|
||||||
|
|
||||||
.PARAMETER appsList
|
.PARAMETER appsList
|
||||||
An array of app package identifiers to remove (e.g. 'Microsoft.BingNews').
|
An array of app package identifiers to remove (e.g. 'Microsoft.BingNews').
|
||||||
@@ -19,7 +20,6 @@
|
|||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
RemoveApps -appsList (GenerateAppsList)
|
RemoveApps -appsList (GenerateAppsList)
|
||||||
#>
|
#>
|
||||||
# Removes apps specified during function call based on the target scope.
|
|
||||||
function RemoveApps {
|
function RemoveApps {
|
||||||
param (
|
param (
|
||||||
$appslist
|
$appslist
|
||||||
@@ -34,137 +34,359 @@ function RemoveApps {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
# Determine target from script-level params, defaulting to AllUsers
|
|
||||||
$targetUser = GetTargetUserForAppRemoval
|
$targetUser = GetTargetUserForAppRemoval
|
||||||
|
|
||||||
$appIndex = 0
|
|
||||||
$appCount = @($appsList).Count
|
$appCount = @($appsList).Count
|
||||||
|
$appIndex = 0
|
||||||
|
|
||||||
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
|
$edgeIds = @('Microsoft.Edge', 'XPFFTQ037JWMHS')
|
||||||
$edgeUninstallSucceeded = $false
|
$edgeAppsInList = @()
|
||||||
$edgeScheduledTaskAdded = $false
|
$wingetRemovedApps = @()
|
||||||
|
|
||||||
Foreach ($app in $appsList) {
|
Foreach ($app in $appsList) {
|
||||||
if ($script:CancelRequested) {
|
if ($script:CancelRequested) { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$appIndex++
|
$appIndex++
|
||||||
|
|
||||||
# Update step name and sub-progress to show which app is being removed (only for bulk removal)
|
|
||||||
if ($script:ApplySubStepCallback -and $appCount -gt 1) {
|
if ($script:ApplySubStepCallback -and $appCount -gt 1) {
|
||||||
& $script:ApplySubStepCallback "Removing apps... ($appIndex/$appCount)" $appIndex $appCount
|
& $script:ApplySubStepCallback "Removing apps ($appIndex/$appCount)" $appIndex $appCount
|
||||||
|
}
|
||||||
|
|
||||||
|
# Microsoft Edge is handled after the loop to avoid duplicate scheduled tasks and allow fallback if winget fails
|
||||||
|
if ($edgeIds -contains $app) {
|
||||||
|
$edgeAppsInList += $app
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Removing $app"
|
Write-Host "Removing $app"
|
||||||
|
|
||||||
# Use WinGet only to remove OneDrive and Edge
|
if ((Get-AppRemovalMethod $app) -eq 'WinGet') {
|
||||||
if (($app -eq "Microsoft.OneDrive") -or ($edgeIds -contains $app)) {
|
Remove-WinGetApp -app $app
|
||||||
if ($script:WingetInstalled -eq $false) {
|
$wingetRemovedApps += $app
|
||||||
Write-Host "WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
|
}
|
||||||
continue
|
else {
|
||||||
|
Remove-AppxApp -app $app -targetUser $targetUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove Microsoft Edge
|
||||||
|
if ($edgeAppsInList.Count -gt 0) {
|
||||||
|
Remove-EdgeApp -edgeAppsInList $edgeAppsInList
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check whether any winget-removed apps are still present, and report errors for each one.
|
||||||
|
if ($wingetRemovedApps.Count -gt 0 -or $edgeAppsInList.Count -gt 0) {
|
||||||
|
$postRemovalList = if ($script:WingetInstalled) { GetInstalledAppsViaWinget -TimeOut 10 -NonBlocking } else { $null }
|
||||||
|
foreach ($app in $wingetRemovedApps) {
|
||||||
|
if (Test-AppStillInstalled -appId $app -InstalledList $postRemovalList) {
|
||||||
|
Write-Host "Unable to uninstall $app via WinGet" -ForegroundColor Red
|
||||||
}
|
}
|
||||||
|
|
||||||
$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
|
|
||||||
if ($script:Params.ContainsKey("User")) {
|
|
||||||
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")) {
|
|
||||||
if (-not ($isEdgeId -and $edgeScheduledTaskAdded)) {
|
|
||||||
ImportRegistryFile "Adding scheduled task to uninstall $app after for new users..." "Uninstall_$($appName).reg"
|
|
||||||
if ($isEdgeId) { $edgeScheduledTaskAdded = $true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Uninstall app via WinGet
|
|
||||||
$wingetResult = Invoke-NonBlocking -ScriptBlock {
|
|
||||||
param($appId)
|
|
||||||
$global:LASTEXITCODE = 0
|
|
||||||
$output = winget uninstall --accept-source-agreements --disable-interactivity --id $appId
|
|
||||||
[PSCustomObject]@{ ExitCode = $LASTEXITCODE; Output = $output }
|
|
||||||
} -ArgumentList $app
|
|
||||||
|
|
||||||
# winget reports success/failure via its exit code, which is locale-independent.
|
|
||||||
# The previous match on English console text silently passed on non-English Windows.
|
|
||||||
# Treat a null result (timed out / not run) or any non-zero exit code as a failure.
|
|
||||||
$wingetFailed = ($null -eq $wingetResult) -or ($wingetResult.ExitCode -ne 0)
|
|
||||||
if ($isEdgeId) {
|
|
||||||
if (-not $wingetFailed) {
|
|
||||||
$edgeUninstallSucceeded = $true
|
|
||||||
}
|
|
||||||
|
|
||||||
# Prompt immediately after the final selected Edge ID attempt (if all attempts failed)
|
|
||||||
$hasRemainingEdgeIds = $false
|
|
||||||
if ($appIndex -lt $appCount) {
|
|
||||||
$remainingApps = @($appsList)[($appIndex)..($appCount - 1)]
|
|
||||||
$hasRemainingEdgeIds = @($remainingApps | Where-Object { $edgeIds -contains $_ }).Count -gt 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ""
|
|
||||||
ForceRemoveEdge
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elseif ($wingetFailed) {
|
|
||||||
Write-Host "Unable to uninstall $app via WinGet" -ForegroundColor Red
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use Remove-AppxPackage to remove all other apps
|
# Verify Edge separately (triggers its own force-remove path if still installed)
|
||||||
$appPattern = '*' + $app + '*'
|
$edgeStillInstalled = $false
|
||||||
|
foreach ($edgeApp in $edgeAppsInList) {
|
||||||
try {
|
if (Test-AppStillInstalled -appId $edgeApp -InstalledList $postRemovalList) {
|
||||||
switch ($targetUser) {
|
$edgeStillInstalled = $true
|
||||||
"AllUsers" {
|
break
|
||||||
# Remove installed app for all existing users, and from OS image
|
|
||||||
Invoke-NonBlocking -ScriptBlock {
|
|
||||||
param($pattern)
|
|
||||||
Get-AppxPackage -Name $pattern -AllUsers | Remove-AppxPackage -AllUsers -ErrorAction Continue
|
|
||||||
Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like $pattern } | ForEach-Object { Remove-ProvisionedAppxPackage -Online -AllUsers -PackageName $_.PackageName }
|
|
||||||
} -ArgumentList $appPattern
|
|
||||||
}
|
|
||||||
"CurrentUser" {
|
|
||||||
# Remove installed app for current user only
|
|
||||||
Invoke-NonBlocking -ScriptBlock {
|
|
||||||
param($pattern)
|
|
||||||
Get-AppxPackage -Name $pattern | Remove-AppxPackage -ErrorAction Continue
|
|
||||||
} -ArgumentList $appPattern
|
|
||||||
}
|
|
||||||
default {
|
|
||||||
# Target is a specific username - remove app for that user only
|
|
||||||
Invoke-NonBlocking -ScriptBlock {
|
|
||||||
param($pattern, $user)
|
|
||||||
$userAccount = New-Object System.Security.Principal.NTAccount($user)
|
|
||||||
$userSid = $userAccount.Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
||||||
Get-AppxPackage -Name $pattern -User $userSid | Remove-AppxPackage -User $userSid -ErrorAction Continue
|
|
||||||
} -ArgumentList @($appPattern, $targetUser)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch {
|
if ($edgeStillInstalled) {
|
||||||
Write-Verbose "Something went wrong while trying to remove $($app): $_"
|
Write-Host "Unable to uninstall Microsoft Edge via WinGet" -ForegroundColor Red
|
||||||
|
Request-EdgeForceRemove
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Uninstalls a non-Edge 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.
|
||||||
|
After uninstall, the system is checked to confirm whether the app
|
||||||
|
is still present — winget output is not trusted on its
|
||||||
|
own, as it sometimes reports failure after a successful removal.
|
||||||
|
Edge apps are handled separately after the main loop.
|
||||||
|
|
||||||
|
.PARAMETER app
|
||||||
|
The WinGet package ID to uninstall (e.g. 'Microsoft.BingNews').
|
||||||
|
#>
|
||||||
|
function Remove-WinGetApp {
|
||||||
|
param([string]$app)
|
||||||
|
|
||||||
|
if (-not $script:WingetInstalled) {
|
||||||
|
Write-Host "ERROR: WinGet is either not installed or is outdated, $app could not be removed" -ForegroundColor Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:Params.ContainsKey("User")) {
|
||||||
|
Write-Host "Adding scheduled task to uninstall $app for user $(GetUserName)..."
|
||||||
|
Set-RunOnceWingetTask -appId $app
|
||||||
|
}
|
||||||
|
elseif ($script:Params.ContainsKey("Sysprep")) {
|
||||||
|
Write-Host "Adding scheduled task to uninstall $app for new users..."
|
||||||
|
Set-RunOnceWingetTask -appId $app
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoke-NonBlocking -ScriptBlock {
|
||||||
|
param($appId)
|
||||||
|
winget uninstall --accept-source-agreements --disable-interactivity --id $appId
|
||||||
|
} -ArgumentList $app
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Removes Microsoft Edge via WinGet (both AppIds), with fallback to force-remove.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Edge has multiple package IDs. Runs winget uninstall for each one,
|
||||||
|
then creates a single scheduled task if the User or Sysprep parameter
|
||||||
|
was passed. After all attempts, the system is checked to confirm
|
||||||
|
whether Edge is still present. The force-remove prompt only
|
||||||
|
appears if Edge remains installed — winget false positives are ignored.
|
||||||
|
|
||||||
|
.PARAMETER edgeAppsInList
|
||||||
|
The Edge AppIds that appear in the removal list (one or both).
|
||||||
|
#>
|
||||||
|
function Remove-EdgeApp {
|
||||||
|
param([string[]]$edgeAppsInList)
|
||||||
|
|
||||||
|
if (-not $script:WingetInstalled) {
|
||||||
|
Write-Host "ERROR: WinGet is either not installed or is outdated, Microsoft Edge could not be removed" -ForegroundColor Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:Params.ContainsKey("User")) {
|
||||||
|
Write-Host "Adding scheduled task to uninstall Microsoft Edge for user $(GetUserName)..."
|
||||||
|
Set-RunOnceWingetTask -appId 'Microsoft.Edge'
|
||||||
|
}
|
||||||
|
elseif ($script:Params.ContainsKey("Sysprep")) {
|
||||||
|
Write-Host "Adding scheduled task to uninstall Microsoft Edge for new users..."
|
||||||
|
Set-RunOnceWingetTask -appId 'Microsoft.Edge'
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($edgeApp in $edgeAppsInList) {
|
||||||
|
Write-Host "Removing $edgeApp"
|
||||||
|
Invoke-NonBlocking -ScriptBlock {
|
||||||
|
param($appId)
|
||||||
|
winget uninstall --accept-source-agreements --disable-interactivity --id $appId
|
||||||
|
} -ArgumentList $edgeApp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Removes an app via Remove-AppxPackage / Remove-ProvisionedAppxPackage.
|
||||||
|
|
||||||
|
.PARAMETER app
|
||||||
|
The package identifier to remove (e.g. 'Clipchamp.Clipchamp').
|
||||||
|
|
||||||
|
.PARAMETER targetUser
|
||||||
|
Target scope: "AllUsers", "CurrentUser", or a specific username.
|
||||||
|
#>
|
||||||
|
function Remove-AppxApp {
|
||||||
|
param([string]$app, [string]$targetUser)
|
||||||
|
|
||||||
|
$appPattern = '*' + $app + '*'
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($targetUser) {
|
||||||
|
"AllUsers" {
|
||||||
|
Invoke-NonBlocking -ScriptBlock {
|
||||||
|
param($pattern)
|
||||||
|
Get-AppxPackage -Name $pattern -AllUsers | Remove-AppxPackage -AllUsers -ErrorAction Continue
|
||||||
|
Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like $pattern } | ForEach-Object { Remove-ProvisionedAppxPackage -Online -AllUsers -PackageName $_.PackageName }
|
||||||
|
} -ArgumentList $appPattern
|
||||||
|
}
|
||||||
|
"CurrentUser" {
|
||||||
|
Invoke-NonBlocking -ScriptBlock {
|
||||||
|
param($pattern)
|
||||||
|
Get-AppxPackage -Name $pattern | Remove-AppxPackage -ErrorAction Continue
|
||||||
|
} -ArgumentList $appPattern
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
Invoke-NonBlocking -ScriptBlock {
|
||||||
|
param($pattern, $user)
|
||||||
|
$userAccount = New-Object System.Security.Principal.NTAccount($user)
|
||||||
|
$userSid = $userAccount.Translate([System.Security.Principal.SecurityIdentifier]).Value
|
||||||
|
Get-AppxPackage -Name $pattern -User $userSid | Remove-AppxPackage -User $userSid -ErrorAction Continue
|
||||||
|
} -ArgumentList @($appPattern, $targetUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Verbose "Something went wrong while trying to remove $($app): $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Checks whether an app package is still installed after a removal attempt.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Checks Get-AppxPackage across all users first (fast, no process launch),
|
||||||
|
then falls back to a pre-fetched or live winget list for non-Appx packages.
|
||||||
|
Uses Test-AppInWingetList which provides exact-match-first with substring
|
||||||
|
fallback against the parsed winget objects.
|
||||||
|
Returns $true if the app is still present, $false otherwise.
|
||||||
|
|
||||||
|
.PARAMETER appId
|
||||||
|
The package identifier to check (e.g. 'Microsoft.BingNews').
|
||||||
|
|
||||||
|
.PARAMETER InstalledList
|
||||||
|
Optional pre-fetched array of winget objects from GetInstalledAppsViaWinget.
|
||||||
|
When provided, used directly; otherwise a live winget call is made.
|
||||||
|
#>
|
||||||
|
function Test-AppStillInstalled {
|
||||||
|
param(
|
||||||
|
[string]$appId,
|
||||||
|
[object[]]$InstalledList
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check Get-AppxPackage for all users first (fast, covers all Store apps).
|
||||||
|
if (Get-AppxPackage -Name "$appId" -AllUsers -ErrorAction SilentlyContinue) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use the pre-fetched list if provided; otherwise fall back to a live winget call.
|
||||||
|
if ($InstalledList) {
|
||||||
|
return (Test-AppInWingetList -appId $appId -InstalledList $InstalledList)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:WingetInstalled) {
|
||||||
|
$liveList = GetInstalledAppsViaWinget -TimeOut 10 -NonBlocking
|
||||||
|
if (Test-AppInWingetList -appId $appId -InstalledList $liveList) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "Unable to verify whether '$appId' is still installed (WinGet is unavailable)"
|
||||||
|
}
|
||||||
|
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns the removal method for an app identifier.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Parses Apps.json once (cached in script scope) to build a lookup of
|
||||||
|
AppId -> RemovalMethod. Returns 'WinGet' if the app should be removed
|
||||||
|
via winget, or 'Appx' if via Remove-AppxPackage. Defaults to 'Appx'
|
||||||
|
for unknown IDs.
|
||||||
|
|
||||||
|
.PARAMETER appId
|
||||||
|
The package identifier (e.g. 'Clipchamp.Clipchamp').
|
||||||
|
#>
|
||||||
|
function Get-AppRemovalMethod {
|
||||||
|
param([string]$appId)
|
||||||
|
|
||||||
|
if (-not $script:AppRemovalMethodCache) {
|
||||||
|
$script:AppRemovalMethodCache = @{}
|
||||||
|
try {
|
||||||
|
if (Test-Path $script:AppsListFilePath) {
|
||||||
|
$appsJson = Get-Content -Path $script:AppsListFilePath -Raw | ConvertFrom-Json
|
||||||
|
foreach ($appData in $appsJson.Apps) {
|
||||||
|
$rawMethod = $appData.RemovalMethod
|
||||||
|
$method = if ($rawMethod -and $rawMethod -eq 'WinGet') { 'WinGet' } else { 'Appx' }
|
||||||
|
if ($appData.AppId -is [array]) {
|
||||||
|
foreach ($id in $appData.AppId) { $script:AppRemovalMethodCache[$id.Trim()] = $method }
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$script:AppRemovalMethodCache[$appData.AppId.Trim()] = $method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Failed to load app removal methods from '$script:AppsListFilePath'. Defaulting unknown apps to Appx. Error: $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:AppRemovalMethodCache.ContainsKey($appId)) {
|
||||||
|
return $script:AppRemovalMethodCache[$appId]
|
||||||
|
}
|
||||||
|
return 'Appx'
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Prompts the user to forcefully remove Microsoft Edge when winget cannot uninstall it.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Only invoked after it has been confirmed that Edge is still present
|
||||||
|
following all winget uninstall attempts. In GUI mode, displays a
|
||||||
|
warning message box; in CLI mode, prompts via Read-Host. On
|
||||||
|
confirmation, performs a force-remove of the Edge package.
|
||||||
|
#>
|
||||||
|
function Request-EdgeForceRemove {
|
||||||
|
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 ""
|
||||||
|
ForceRemoveEdge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Dynamically sets a RunOnce registry key to schedule a winget uninstall.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Writes directly to HKEY_USERS\Default\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
|
||||||
|
via the PowerShell registry API within Invoke-WithTargetUserHive,
|
||||||
|
which handles hive loading and HKEY_USERS\Default → SID remapping.
|
||||||
|
Used instead of static .reg files to avoid file dependency for each WinGet app.
|
||||||
|
|
||||||
|
The winget command is Base64-encoded and invoked via powershell.exe -EncodedCommand
|
||||||
|
rather than interpolated directly into cmd.exe /c. This prevents shell metacharacters
|
||||||
|
(such as &, |, <, >, ^, ") in the app ID from being interpreted as command syntax,
|
||||||
|
even if future catalog updates introduce IDs containing those characters.
|
||||||
|
|
||||||
|
.PARAMETER appId
|
||||||
|
The winget package ID to schedule for uninstall (e.g. 'XP9CXNGPPJ97XX').
|
||||||
|
#>
|
||||||
|
function Set-RunOnceWingetTask {
|
||||||
|
param([string]$appId)
|
||||||
|
|
||||||
|
$targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") }
|
||||||
|
|
||||||
|
# Sanitize appId for use in registry value names (backslashes are path separators)
|
||||||
|
$safeAppId = $appId.Replace('\', '_')
|
||||||
|
|
||||||
|
$taskName = "Uninstall_$safeAppId"
|
||||||
|
|
||||||
|
# Escape single quotes in appId, then wrap in single quotes so cmd/pwsh metacharacters
|
||||||
|
# like & | < > ^ " are treated as literals. Base64-encode the whole command so the
|
||||||
|
# RunOnce value contains only [A-Za-z0-9+/=] — safe in any shell parser.
|
||||||
|
$escapedAppId = $appId.Replace("'", "''")
|
||||||
|
$wingetCommand = "winget uninstall --accept-source-agreements --disable-interactivity --id '$escapedAppId'"
|
||||||
|
$encodedWingetCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($wingetCommand))
|
||||||
|
|
||||||
|
$operation = [PSCustomObject]@{
|
||||||
|
KeyPath = 'HKEY_USERS\Default\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce'
|
||||||
|
ValueName = $taskName
|
||||||
|
ValueType = 'String'
|
||||||
|
ValueData = "powershell.exe -NoProfile -EncodedCommand $encodedWingetCommand"
|
||||||
|
OperationType = 'SetValue'
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Invoke-WithTargetUserHive -TargetUserName $targetUserName -ScriptBlock {
|
||||||
|
param($op)
|
||||||
|
Invoke-RegistryOperation -Operation $op -RegFilePath '<dynamic>'
|
||||||
|
} -ArgumentObject $operation
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Failed to schedule uninstall task for $($appId): $_" -ForegroundColor Red
|
||||||
|
}
|
||||||
}
|
}
|
||||||
43
Scripts/AppRemoval/Test-AppInWingetList.ps1
Normal file
43
Scripts/AppRemoval/Test-AppInWingetList.ps1
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Checks whether an app ID appears in a parsed winget installed list.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Tries an exact match against the .Id property first. When that
|
||||||
|
fails, falls back to a substring search guarded by a word-boundary
|
||||||
|
regex so that short IDs don't accidentally match longer ones
|
||||||
|
(e.g. 'Microsoft.Edge' will not match 'Microsoft.EdgeDev').
|
||||||
|
|
||||||
|
.PARAMETER appId
|
||||||
|
The identifier to search for (e.g. 'Microsoft.Copilot').
|
||||||
|
|
||||||
|
.PARAMETER InstalledList
|
||||||
|
An array of PSCustomObject from GetInstalledAppsViaWinget.
|
||||||
|
#>
|
||||||
|
function Test-AppInWingetList {
|
||||||
|
param(
|
||||||
|
[string]$appId,
|
||||||
|
[object[]]$InstalledList
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $InstalledList) { return $false }
|
||||||
|
|
||||||
|
# Normalize to array
|
||||||
|
$list = @($InstalledList)
|
||||||
|
|
||||||
|
# Exact match first (fast and precise)
|
||||||
|
if ($list.Id -contains $appId) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Substring fallback with word-boundary guard
|
||||||
|
$boundaryPattern = '(?<![a-zA-Z0-9])' + [regex]::Escape($appId) + '(?![a-zA-Z0-9])'
|
||||||
|
|
||||||
|
foreach ($entry in $list) {
|
||||||
|
if ($entry.Id -like "*$appId*" -and $entry.Id -match $boundaryPattern) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $false
|
||||||
|
}
|
||||||
@@ -1,4 +1,17 @@
|
|||||||
# Prints all pending changes that will be made by the script
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Prints a summary of all pending changes to the console for the user to review.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Iterates over every non-control parameter in $script:Params and emits a
|
||||||
|
human-readable line for each change that will be applied. For the
|
||||||
|
'RemoveApps' parameter the list of targeted app names is displayed
|
||||||
|
inline. Feature labels are resolved from Features.json when available;
|
||||||
|
otherwise the raw parameter name is used as a fallback.
|
||||||
|
|
||||||
|
After printing the summary the function pauses until the user presses
|
||||||
|
Enter, giving them an opportunity to review and cancel via Ctrl+C.
|
||||||
|
#>
|
||||||
function PrintPendingChanges {
|
function PrintPendingChanges {
|
||||||
Write-Output "Win11Debloat will make the following changes:"
|
Write-Output "Win11Debloat will make the following changes:"
|
||||||
|
|
||||||
@@ -31,19 +44,6 @@ function PrintPendingChanges {
|
|||||||
Write-Host $appsList -ForegroundColor DarkGray
|
Write-Host $appsList -ForegroundColor DarkGray
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
'RemoveAppsCustom' {
|
|
||||||
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath
|
|
||||||
|
|
||||||
if ($appsList.Count -eq 0) {
|
|
||||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
|
||||||
Write-Output ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Output "- Remove $($appsList.Count) apps:"
|
|
||||||
Write-Host $appsList -ForegroundColor DarkGray
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default {
|
default {
|
||||||
if ($script:Features -and $script:Features.ContainsKey($parameterName)) {
|
if ($script:Features -and $script:Features.ContainsKey($parameterName)) {
|
||||||
$message = $script:Features[$parameterName].Label
|
$message = $script:Features[$parameterName].Label
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ function ShowCLIAppRemoval {
|
|||||||
|
|
||||||
if ($result -eq $true) {
|
if ($result -eq $true) {
|
||||||
Write-Output "You have selected $($script:SelectedApps.Count) apps for removal"
|
Write-Output "You have selected $($script:SelectedApps.Count) apps for removal"
|
||||||
AddParameter 'RemoveAppsCustom'
|
AddParameter 'RemoveApps'
|
||||||
|
AddParameter 'Apps' ($script:SelectedApps -join ',')
|
||||||
|
|
||||||
SaveSettings
|
SaveSettings
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ function ShowCLIDefaultModeOptions {
|
|||||||
AddParameter 'Apps' 'Default'
|
AddParameter 'Apps' 'Default'
|
||||||
}
|
}
|
||||||
'2' {
|
'2' {
|
||||||
AddParameter 'RemoveAppsCustom'
|
AddParameter 'RemoveApps'
|
||||||
|
AddParameter 'Apps' ($script:SelectedApps -join ',')
|
||||||
|
|
||||||
if ($DisableGameBarIntegrationInput) {
|
if ($DisableGameBarIntegrationInput) {
|
||||||
AddParameter 'DisableDVR'
|
AddParameter 'DisableDVR'
|
||||||
|
|||||||
@@ -20,10 +20,12 @@ function Invoke-FeatureApply {
|
|||||||
if ($script:Features.ContainsKey($FeatureId)) {
|
if ($script:Features.ContainsKey($FeatureId)) {
|
||||||
$feature = $script:Features[$FeatureId]
|
$feature = $script:Features[$FeatureId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$applyText = if ($feature -and $feature.ApplyText) { $feature.ApplyText } else { $FeatureId }
|
||||||
|
|
||||||
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
||||||
if ($feature -and $feature.RegistryKey -and $feature.ApplyText) {
|
if ($feature -and $feature.RegistryKey) {
|
||||||
ImportRegistryFile "> $($feature.ApplyText)..." $feature.RegistryKey
|
ImportRegistryFile "> $applyText..." $feature.RegistryKey
|
||||||
|
|
||||||
# Post-import side effects for specific features
|
# Post-import side effects for specific features
|
||||||
switch ($FeatureId) {
|
switch ($FeatureId) {
|
||||||
@@ -32,8 +34,8 @@ function Invoke-FeatureApply {
|
|||||||
RemoveApps @('Microsoft.BingSearch')
|
RemoveApps @('Microsoft.BingSearch')
|
||||||
}
|
}
|
||||||
'DisableCopilot' {
|
'DisableCopilot' {
|
||||||
# Also remove the app package for Copilot
|
# Also remove the app packages for Copilot
|
||||||
RemoveApps @('Microsoft.Copilot')
|
RemoveApps @('Microsoft.Copilot', 'XP9CXNGPPJ97XX')
|
||||||
}
|
}
|
||||||
'DisableTelemetry' {
|
'DisableTelemetry' {
|
||||||
# Also disable telemetry scheduled tasks
|
# Also disable telemetry scheduled tasks
|
||||||
@@ -44,8 +46,6 @@ function Invoke-FeatureApply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ---- Custom features (no registry backing, or special handling required) ----
|
# ---- Custom features (no registry backing, or special handling required) ----
|
||||||
# Resolve a safe apply-text fallback in case the feature is missing from Features.json
|
|
||||||
$applyText = if ($feature -and $feature.ApplyText) { $feature.ApplyText } else { $FeatureId }
|
|
||||||
switch ($FeatureId) {
|
switch ($FeatureId) {
|
||||||
'RemoveApps' {
|
'RemoveApps' {
|
||||||
Write-Host "> $applyText for $(GetFriendlyTargetUserName)..."
|
Write-Host "> $applyText for $(GetFriendlyTargetUserName)..."
|
||||||
@@ -61,20 +61,6 @@ function Invoke-FeatureApply {
|
|||||||
RemoveApps $appsList
|
RemoveApps $appsList
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
'RemoveAppsCustom' {
|
|
||||||
Write-Host "> $applyText..."
|
|
||||||
$appsList = LoadAppsFromFile $script:CustomAppsListFilePath
|
|
||||||
|
|
||||||
if ($appsList.Count -eq 0) {
|
|
||||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
|
||||||
Write-Host ""
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "$($appsList.Count) apps selected for removal"
|
|
||||||
RemoveApps $appsList
|
|
||||||
return
|
|
||||||
}
|
|
||||||
'RemoveGamingApps' {
|
'RemoveGamingApps' {
|
||||||
$appsList = @('Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay')
|
$appsList = @('Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay')
|
||||||
Write-Host "> $applyText..."
|
Write-Host "> $applyText..."
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
function LoadAppsDetailsFromJson {
|
function LoadAppsDetailsFromJson {
|
||||||
param (
|
param (
|
||||||
[switch]$OnlyInstalled,
|
[switch]$OnlyInstalled,
|
||||||
[string]$InstalledList = "",
|
[object[]]$InstalledList = $null,
|
||||||
[switch]$InitialCheckedFromJson
|
[switch]$InitialCheckedFromJson
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,22 +24,19 @@ function LoadAppsDetailsFromJson {
|
|||||||
if ($OnlyInstalled) {
|
if ($OnlyInstalled) {
|
||||||
$isInstalled = $false
|
$isInstalled = $false
|
||||||
foreach ($appId in $appIdArray) {
|
foreach ($appId in $appIdArray) {
|
||||||
if (($InstalledList -like ("*$appId*")) -or (Get-AppxPackage -Name $appId)) {
|
# Check Get-AppxPackage first (fast, no process launch)
|
||||||
|
if (Get-AppxPackage -Name $appId) {
|
||||||
$isInstalled = $true
|
$isInstalled = $true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (($appId -eq "Microsoft.Edge") -and ($InstalledList -like "* Microsoft.Edge *")) {
|
|
||||||
$isInstalled = $true
|
# Then check the pre-fetched winget list
|
||||||
break
|
if ($InstalledList -and (Test-AppInWingetList -appId $appId -InstalledList $InstalledList)) {
|
||||||
}
|
|
||||||
if (($appId -eq "Microsoft.OneDrive") -and (
|
|
||||||
(Test-Path "$env:ProgramFiles\Microsoft OneDrive\OneDrive.exe") -or
|
|
||||||
(Test-Path "$env:LOCALAPPDATA\Microsoft\OneDrive\OneDrive.exe")
|
|
||||||
)) {
|
|
||||||
$isInstalled = $true
|
$isInstalled = $true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $isInstalled) { continue }
|
if (-not $isInstalled) { continue }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +56,7 @@ function LoadAppsDetailsFromJson {
|
|||||||
Description = $appData.Description
|
Description = $appData.Description
|
||||||
SelectedByDefault = $appData.SelectedByDefault
|
SelectedByDefault = $appData.SelectedByDefault
|
||||||
Recommendation = $appData.Recommendation
|
Recommendation = $appData.Recommendation
|
||||||
|
RemovalMethod = if ($appData.RemovalMethod -and $appData.RemovalMethod -eq 'WinGet') { 'WinGet' } else { 'Appx' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,19 @@
|
|||||||
# Returns list of apps from the specified file, it trims the app names and removes any comments
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Returns a list of app IDs from the specified JSON file.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Reads an Apps.json file and returns the AppIds for every entry where
|
||||||
|
SelectedByDefault is $true. Each app entry may declare a single AppId
|
||||||
|
or an array of AppIds; both forms are handled transparently.
|
||||||
|
|
||||||
|
.PARAMETER appsFilePath
|
||||||
|
Path to a JSON file in the Config/Apps.json format.
|
||||||
|
|
||||||
|
.OUTPUTS
|
||||||
|
System.String[]. An array of app ID strings, or an empty array if the
|
||||||
|
file does not exist or contains no selected-by-default apps.
|
||||||
|
#>
|
||||||
function LoadAppsFromFile {
|
function LoadAppsFromFile {
|
||||||
param (
|
param (
|
||||||
$appsFilePath
|
$appsFilePath
|
||||||
@@ -11,30 +26,14 @@ function LoadAppsFromFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Check if file is JSON or text format
|
$jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json
|
||||||
if ($appsFilePath -like "*.json") {
|
Foreach ($appData in $jsonContent.Apps) {
|
||||||
# JSON file format
|
# Handle AppId as array (could be single or multiple IDs)
|
||||||
$jsonContent = Get-Content -Path $appsFilePath -Raw | ConvertFrom-Json
|
$appIdArray = if ($appData.AppId -is [array]) { $appData.AppId } else { @($appData.AppId) }
|
||||||
Foreach ($appData in $jsonContent.Apps) {
|
$appIdArray = $appIdArray | ForEach-Object { $_.Trim() } | Where-Object { $_.length -gt 0 }
|
||||||
# Handle AppId as array (could be single or multiple IDs)
|
$selectedByDefault = $appData.SelectedByDefault
|
||||||
$appIdArray = if ($appData.AppId -is [array]) { $appData.AppId } else { @($appData.AppId) }
|
if ($selectedByDefault -and $appIdArray.Count -gt 0) {
|
||||||
$appIdArray = $appIdArray | ForEach-Object { $_.Trim() } | Where-Object { $_.length -gt 0 }
|
$appsList += $appIdArray
|
||||||
$selectedByDefault = $appData.SelectedByDefault
|
|
||||||
if ($selectedByDefault -and $appIdArray.Count -gt 0) {
|
|
||||||
$appsList += $appIdArray
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Legacy text file format
|
|
||||||
Foreach ($app in (Get-Content -Path $appsFilePath | Where-Object { $_ -notmatch '^#.*' -and $_ -notmatch '^\s*$' } )) {
|
|
||||||
if (-not ($app.IndexOf('#') -eq -1)) {
|
|
||||||
$app = $app.Substring(0, $app.IndexOf('#'))
|
|
||||||
}
|
|
||||||
|
|
||||||
$app = $app.Trim()
|
|
||||||
$appString = $app.Trim('*')
|
|
||||||
$appsList += $appString
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
# Saves the provided appsList to the CustomAppsList file
|
|
||||||
function SaveCustomAppsListToFile {
|
|
||||||
param (
|
|
||||||
$appsList
|
|
||||||
)
|
|
||||||
|
|
||||||
if ($script:Params.ContainsKey("WhatIf")) {
|
|
||||||
Write-Host "[WhatIf] Save custom apps list to file" -ForegroundColor Cyan
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$script:SelectedApps = $appsList
|
|
||||||
|
|
||||||
# Create file that stores selected apps if it doesn't exist
|
|
||||||
if (-not (Test-Path $script:CustomAppsListFilePath)) {
|
|
||||||
$null = New-Item $script:CustomAppsListFilePath -ItemType File
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Content -Path $script:CustomAppsListFilePath -Value $script:SelectedApps
|
|
||||||
}
|
|
||||||
@@ -311,12 +311,13 @@ function Load-AppsWithList {
|
|||||||
[System.Windows.Controls.CheckBox]$OnlyInstalledAppsBox,
|
[System.Windows.Controls.CheckBox]$OnlyInstalledAppsBox,
|
||||||
[System.Windows.Controls.Border]$LoadingAppsIndicator,
|
[System.Windows.Controls.Border]$LoadingAppsIndicator,
|
||||||
[System.Windows.Controls.MenuItem]$ImportConfigBtn,
|
[System.Windows.Controls.MenuItem]$ImportConfigBtn,
|
||||||
[string]$ListOfApps
|
[object[]]$ListOfApps
|
||||||
)
|
)
|
||||||
|
|
||||||
$script:MainWindowLastSelectedCheckbox = $null
|
$script:MainWindowLastSelectedCheckbox = $null
|
||||||
|
|
||||||
$loaderScriptPath = $script:LoadAppsDetailsScriptPath
|
$loaderScriptPath = $script:LoadAppsDetailsScriptPath
|
||||||
|
$helperScriptPath = $script:TestAppInWingetListScriptPath
|
||||||
$appsFilePath = $script:AppsListFilePath
|
$appsFilePath = $script:AppsListFilePath
|
||||||
$onlyInstalled = [bool]$OnlyInstalledAppsBox.IsChecked
|
$onlyInstalled = [bool]$OnlyInstalledAppsBox.IsChecked
|
||||||
|
|
||||||
@@ -326,13 +327,16 @@ function Load-AppsWithList {
|
|||||||
$script:PreloadedAppData = $null
|
$script:PreloadedAppData = $null
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
# Load apps details in a background job to keep the UI responsive
|
# Load apps details in a background job to keep the UI responsive.
|
||||||
|
# The helper is dot-sourced inside the job because the runspace
|
||||||
|
# does not inherit the parent scope's dot-sourced functions.
|
||||||
$rawAppData = Invoke-NonBlocking -ScriptBlock {
|
$rawAppData = Invoke-NonBlocking -ScriptBlock {
|
||||||
param($loaderScript, $appsListFilePath, $installedList, $onlyInstalled)
|
param($loaderScript, $helperScript, $appsListFilePath, $installedList, $onlyInstalled)
|
||||||
$script:AppsListFilePath = $appsListFilePath
|
$script:AppsListFilePath = $appsListFilePath
|
||||||
|
. $helperScript
|
||||||
. $loaderScript
|
. $loaderScript
|
||||||
LoadAppsDetailsFromJson -OnlyInstalled:$onlyInstalled -InstalledList $installedList -InitialCheckedFromJson:$false
|
LoadAppsDetailsFromJson -OnlyInstalled:$onlyInstalled -InstalledList $installedList -InitialCheckedFromJson:$false
|
||||||
} -ArgumentList $loaderScriptPath, $appsFilePath, $ListOfApps, $onlyInstalled
|
} -ArgumentList $loaderScriptPath, $helperScriptPath, $appsFilePath, $ListOfApps, $onlyInstalled
|
||||||
}
|
}
|
||||||
|
|
||||||
$appsToAdd = @($rawAppData | Where-Object { $_ -and ($_.AppId -or $_.FriendlyName) } | Sort-Object -Property FriendlyName)
|
$appsToAdd = @($rawAppData | Where-Object { $_ -and ($_.AppId -or $_.FriendlyName) } | Sort-Object -Property FriendlyName)
|
||||||
@@ -348,10 +352,9 @@ function Load-AppsWithList {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$brushSafe = [System.Windows.Media.BrushConverter]::new().ConvertFromString('#4CAF50')
|
$brushSafe = $Window.Resources['AppRecommendationSafeColor']
|
||||||
$brushUnsafe = [System.Windows.Media.BrushConverter]::new().ConvertFromString('#F44336')
|
$brushDefault = $Window.Resources['AppRecommendationOptionalColor']
|
||||||
$brushDefault = [System.Windows.Media.BrushConverter]::new().ConvertFromString('#FFC107')
|
$brushUnsafe = $Window.Resources['AppRecommendationUnsafeColor']
|
||||||
$brushSafe.Freeze(); $brushUnsafe.Freeze(); $brushDefault.Freeze()
|
|
||||||
|
|
||||||
# Create WPF controls; pump the Dispatcher every batch so the spinner keeps animating.
|
# Create WPF controls; pump the Dispatcher every batch so the spinner keeps animating.
|
||||||
$batchSize = 20
|
$batchSize = 20
|
||||||
@@ -381,7 +384,7 @@ function Load-AppsWithList {
|
|||||||
$dot.ToolTip = switch ($app.Recommendation) {
|
$dot.ToolTip = switch ($app.Recommendation) {
|
||||||
'safe' { '[Recommended] Safe to remove for most users' }
|
'safe' { '[Recommended] Safe to remove for most users' }
|
||||||
'unsafe' { '[Not Recommended] Only remove if you know what you are doing' }
|
'unsafe' { '[Not Recommended] Only remove if you know what you are doing' }
|
||||||
default { "[Optional] Remove if you don't need this app" }
|
default { "[Optional] Can be safely removed if you don't need this app" }
|
||||||
}
|
}
|
||||||
[System.Windows.Controls.Grid]::SetColumn($dot, 0)
|
[System.Windows.Controls.Grid]::SetColumn($dot, 0)
|
||||||
|
|
||||||
@@ -512,7 +515,7 @@ function Load-AppsIntoMainUI {
|
|||||||
$Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action] {})
|
$Window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action] {})
|
||||||
$Window.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action] {
|
$Window.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action] {
|
||||||
try {
|
try {
|
||||||
$listOfApps = ""
|
$listOfApps = $null
|
||||||
|
|
||||||
if ($OnlyInstalledAppsBox.IsChecked -and ($script:WingetInstalled -eq $true)) {
|
if ($OnlyInstalledAppsBox.IsChecked -and ($script:WingetInstalled -eq $true)) {
|
||||||
Write-Host "Retrieving installed apps via winget..."
|
Write-Host "Retrieving installed apps via winget..."
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ function Show-AppSelectionWindow {
|
|||||||
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{})
|
$window.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{})
|
||||||
|
|
||||||
$appsPanel.Children.Clear()
|
$appsPanel.Children.Clear()
|
||||||
$listOfApps = ""
|
$listOfApps = $null
|
||||||
|
|
||||||
if ($onlyInstalledBox.IsChecked -and ($script:WingetInstalled -eq $true)) {
|
if ($onlyInstalledBox.IsChecked -and ($script:WingetInstalled -eq $true)) {
|
||||||
# Attempt to get a list of installed apps via WinGet, times out after 10 seconds
|
# Attempt to get a list of installed apps via WinGet, times out after 10 seconds
|
||||||
$listOfApps = GetInstalledAppsViaWinget -TimeOut 10
|
$listOfApps = GetInstalledAppsViaWinget -TimeOut 10 -NonBlocking
|
||||||
if (-not $listOfApps) {
|
if ($null -eq $listOfApps) {
|
||||||
# Show error that the script was unable to get list of apps from WinGet
|
# Show error that the script was unable to get list of apps from WinGet
|
||||||
Show-MessageBox -Message 'Unable to load list of installed apps via WinGet.' -Title 'Error' -Button 'OK' -Icon 'Error' -Owner $window | Out-Null
|
Show-MessageBox -Message 'Unable to load list of installed apps via WinGet.' -Title 'Error' -Button 'OK' -Icon 'Error' -Owner $window | Out-Null
|
||||||
$onlyInstalledBox.IsChecked = $false
|
$onlyInstalledBox.IsChecked = $false
|
||||||
@@ -134,7 +134,7 @@ function Show-AppSelectionWindow {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveCustomAppsListToFile -appsList $selectedApps
|
$script:SelectedApps = $selectedApps
|
||||||
|
|
||||||
$window.DialogResult = $true
|
$window.DialogResult = $true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -940,7 +940,7 @@
|
|||||||
|
|
||||||
# ---- Preload app data ----
|
# ---- Preload app data ----
|
||||||
try {
|
try {
|
||||||
$script:PreloadedAppData = LoadAppsDetailsFromJson -OnlyInstalled:$false -InstalledList '' -InitialCheckedFromJson:$false
|
$script:PreloadedAppData = LoadAppsDetailsFromJson -OnlyInstalled:$false -InstalledList $null -InitialCheckedFromJson:$false
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Warning "Failed to preload apps list: $_"
|
Write-Warning "Failed to preload apps list: $_"
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ param (
|
|||||||
[string]$User,
|
[string]$User,
|
||||||
[switch]$NoRestartExplorer,
|
[switch]$NoRestartExplorer,
|
||||||
[switch]$CreateRestorePoint,
|
[switch]$CreateRestorePoint,
|
||||||
[switch]$RunAppsListGenerator,
|
|
||||||
[switch]$RunDefaults,
|
[switch]$RunDefaults,
|
||||||
[switch]$RunDefaultsLite,
|
[switch]$RunDefaultsLite,
|
||||||
[switch]$RunSavedSettings,
|
[switch]$RunSavedSettings,
|
||||||
@@ -15,7 +14,6 @@ param (
|
|||||||
[string]$Apps,
|
[string]$Apps,
|
||||||
[string]$AppRemovalTarget,
|
[string]$AppRemovalTarget,
|
||||||
[switch]$RemoveApps,
|
[switch]$RemoveApps,
|
||||||
[switch]$RemoveAppsCustom,
|
|
||||||
[switch]$RemoveGamingApps,
|
[switch]$RemoveGamingApps,
|
||||||
[switch]$RemoveHPApps,
|
[switch]$RemoveHPApps,
|
||||||
[switch]$ForceRemoveEdge,
|
[switch]$ForceRemoveEdge,
|
||||||
@@ -152,7 +150,6 @@ if (Test-Path "$configDir") {
|
|||||||
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
|
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
|
||||||
|
|
||||||
$filesToKeep = @(
|
$filesToKeep = @(
|
||||||
'CustomAppsList',
|
|
||||||
'LastUsedSettings.json'
|
'LastUsedSettings.json'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ param (
|
|||||||
[string]$User,
|
[string]$User,
|
||||||
[switch]$NoRestartExplorer,
|
[switch]$NoRestartExplorer,
|
||||||
[switch]$CreateRestorePoint,
|
[switch]$CreateRestorePoint,
|
||||||
[switch]$RunAppsListGenerator,
|
|
||||||
[switch]$RunDefaults,
|
[switch]$RunDefaults,
|
||||||
[switch]$RunDefaultsLite,
|
[switch]$RunDefaultsLite,
|
||||||
[switch]$RunSavedSettings,
|
[switch]$RunSavedSettings,
|
||||||
@@ -15,7 +14,6 @@ param (
|
|||||||
[string]$Apps,
|
[string]$Apps,
|
||||||
[string]$AppRemovalTarget,
|
[string]$AppRemovalTarget,
|
||||||
[switch]$RemoveApps,
|
[switch]$RemoveApps,
|
||||||
[switch]$RemoveAppsCustom,
|
|
||||||
[switch]$RemoveGamingApps,
|
[switch]$RemoveGamingApps,
|
||||||
[switch]$RemoveHPApps,
|
[switch]$RemoveHPApps,
|
||||||
[switch]$ForceRemoveEdge,
|
[switch]$ForceRemoveEdge,
|
||||||
@@ -153,7 +151,6 @@ if (Test-Path "$configDir") {
|
|||||||
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
|
New-Item -ItemType Directory -Path "$backupDir" -Force | Out-Null
|
||||||
|
|
||||||
$filesToKeep = @(
|
$filesToKeep = @(
|
||||||
'CustomAppsList',
|
|
||||||
'LastUsedSettings.json'
|
'LastUsedSettings.json'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ param (
|
|||||||
[string]$User,
|
[string]$User,
|
||||||
[switch]$NoRestartExplorer,
|
[switch]$NoRestartExplorer,
|
||||||
[switch]$CreateRestorePoint,
|
[switch]$CreateRestorePoint,
|
||||||
[switch]$RunAppsListGenerator,
|
|
||||||
[switch]$RunDefaults,
|
[switch]$RunDefaults,
|
||||||
[switch]$RunDefaultsLite,
|
[switch]$RunDefaultsLite,
|
||||||
[switch]$RunSavedSettings,
|
[switch]$RunSavedSettings,
|
||||||
@@ -15,7 +14,6 @@ param (
|
|||||||
[string]$Apps,
|
[string]$Apps,
|
||||||
[string]$AppRemovalTarget,
|
[string]$AppRemovalTarget,
|
||||||
[switch]$RemoveApps,
|
[switch]$RemoveApps,
|
||||||
[switch]$RemoveAppsCustom,
|
|
||||||
[switch]$RemoveGamingApps,
|
[switch]$RemoveGamingApps,
|
||||||
[switch]$RemoveHPApps,
|
[switch]$RemoveHPApps,
|
||||||
[switch]$ForceRemoveEdge,
|
[switch]$ForceRemoveEdge,
|
||||||
@@ -149,7 +147,6 @@ $script:AppsListFilePath = Join-Path $configPath 'Apps.json'
|
|||||||
$script:DefaultSettingsFilePath = Join-Path $configPath 'DefaultSettings.json'
|
$script:DefaultSettingsFilePath = Join-Path $configPath 'DefaultSettings.json'
|
||||||
$script:FeaturesFilePath = Join-Path $configPath 'Features.json'
|
$script:FeaturesFilePath = Join-Path $configPath 'Features.json'
|
||||||
$script:SavedSettingsFilePath = Join-Path $configPath 'LastUsedSettings.json'
|
$script:SavedSettingsFilePath = Join-Path $configPath 'LastUsedSettings.json'
|
||||||
$script:CustomAppsListFilePath = Join-Path $configPath 'CustomAppsList'
|
|
||||||
$script:DefaultLogPath = Join-Path $logsPath 'Win11Debloat.log'
|
$script:DefaultLogPath = Join-Path $logsPath 'Win11Debloat.log'
|
||||||
$script:RegfilesPath = Join-Path $PSScriptRoot 'Regfiles'
|
$script:RegfilesPath = Join-Path $PSScriptRoot 'Regfiles'
|
||||||
$script:RegistryBackupsPath = Join-Path $PSScriptRoot 'Backups'
|
$script:RegistryBackupsPath = Join-Path $PSScriptRoot 'Backups'
|
||||||
@@ -164,8 +161,9 @@ $script:BubbleHintSchema = Join-Path $schemasPath 'BubbleHint.xaml'
|
|||||||
$script:ImportExportConfigSchema = Join-Path $schemasPath 'ImportExportConfigWindow.xaml'
|
$script:ImportExportConfigSchema = Join-Path $schemasPath 'ImportExportConfigWindow.xaml'
|
||||||
$script:RestoreBackupWindowSchema = Join-Path $schemasPath 'RestoreBackupWindow.xaml'
|
$script:RestoreBackupWindowSchema = Join-Path $schemasPath 'RestoreBackupWindow.xaml'
|
||||||
$script:LoadAppsDetailsScriptPath = Join-Path (Join-Path $scriptsPath 'FileIO') 'LoadAppsDetailsFromJson.ps1'
|
$script:LoadAppsDetailsScriptPath = Join-Path (Join-Path $scriptsPath 'FileIO') 'LoadAppsDetailsFromJson.ps1'
|
||||||
|
$script:TestAppInWingetListScriptPath = Join-Path (Join-Path $scriptsPath 'AppRemoval') 'Test-AppInWingetList.ps1'
|
||||||
|
|
||||||
$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Silent', 'Sysprep', 'User', 'NoRestartExplorer', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'Config', 'RunAppsListGenerator', 'CLI', 'AppRemovalTarget'
|
$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Silent', 'Sysprep', 'User', 'NoRestartExplorer', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'Config', 'CLI', 'AppRemovalTarget'
|
||||||
|
|
||||||
# Script-level variables for GUI elements
|
# Script-level variables for GUI elements
|
||||||
$script:GuiWindow = $null
|
$script:GuiWindow = $null
|
||||||
@@ -289,6 +287,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
|||||||
. "$PSScriptRoot/Scripts/AppRemoval/ForceRemoveEdge.ps1"
|
. "$PSScriptRoot/Scripts/AppRemoval/ForceRemoveEdge.ps1"
|
||||||
. "$PSScriptRoot/Scripts/AppRemoval/RemoveApps.ps1"
|
. "$PSScriptRoot/Scripts/AppRemoval/RemoveApps.ps1"
|
||||||
. "$PSScriptRoot/Scripts/AppRemoval/GetInstalledAppsViaWinget.ps1"
|
. "$PSScriptRoot/Scripts/AppRemoval/GetInstalledAppsViaWinget.ps1"
|
||||||
|
. "$PSScriptRoot/Scripts/AppRemoval/Test-AppInWingetList.ps1"
|
||||||
|
|
||||||
# CLI functions
|
# CLI functions
|
||||||
. "$PSScriptRoot/Scripts/CLI/AwaitKeyToExit.ps1"
|
. "$PSScriptRoot/Scripts/CLI/AwaitKeyToExit.ps1"
|
||||||
@@ -322,7 +321,6 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
|||||||
. "$PSScriptRoot/Scripts/FileIO/SaveToFile.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/SaveToFile.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/SaveSettings.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/SaveSettings.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/LoadSettings.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/LoadSettings.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/SaveCustomAppsListToFile.ps1"
|
|
||||||
. "$PSScriptRoot/Scripts/FileIO/ValidateAppslist.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/ValidateAppslist.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/LoadAppsFromFile.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/LoadAppsFromFile.ps1"
|
||||||
. "$PSScriptRoot/Scripts/FileIO/LoadAppsDetailsFromJson.ps1"
|
. "$PSScriptRoot/Scripts/FileIO/LoadAppsDetailsFromJson.ps1"
|
||||||
@@ -448,24 +446,6 @@ if ((Test-Path $script:SavedSettingsFilePath) -and ([String]::IsNullOrWhiteSpace
|
|||||||
# Default to CLI mode for deployment-targeted parameters.
|
# Default to CLI mode for deployment-targeted parameters.
|
||||||
$launchInCLI = $CLI -or $script:Params.ContainsKey("User") -or $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("AppRemovalTarget")
|
$launchInCLI = $CLI -or $script:Params.ContainsKey("User") -or $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("AppRemovalTarget")
|
||||||
|
|
||||||
# Only run the app selection form if the 'RunAppsListGenerator' parameter was passed to the script
|
|
||||||
if ($RunAppsListGenerator) {
|
|
||||||
PrintHeader "Custom Apps List Generator"
|
|
||||||
|
|
||||||
$result = Show-AppSelectionWindow
|
|
||||||
|
|
||||||
# Show different message based on whether the app selection was saved or cancelled
|
|
||||||
if ($result -ne $true) {
|
|
||||||
Write-Host "Application selection window was closed without saving." -ForegroundColor Red
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Output "Your app selection was saved to the 'CustomAppsList' file, found at:"
|
|
||||||
Write-Host "$PSScriptRoot" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
AwaitKeyToExit
|
|
||||||
}
|
|
||||||
|
|
||||||
# Change script execution based on provided parameters or user input
|
# Change script execution based on provided parameters or user input
|
||||||
if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSavedSettings -or $Config -or ($controlParamsCount -eq $script:Params.Count)) {
|
if ((-not $script:Params.Count) -or $RunDefaults -or $RunDefaultsLite -or $RunSavedSettings -or $Config -or ($controlParamsCount -eq $script:Params.Count)) {
|
||||||
if ($RunDefaults -or $RunDefaultsLite) {
|
if ($RunDefaults -or $RunDefaultsLite) {
|
||||||
|
|||||||
Reference in New Issue
Block a user