From feef8f76b94e176acd249025a05a6d8657d35d05 Mon Sep 17 00:00:00 2001
From: Jeffrey <9938813+Raphire@users.noreply.github.com>
Date: Thu, 12 Feb 2026 22:50:22 +0100
Subject: [PATCH] Add option to select target user(s) for app removal (#462)
---
Assets/Schemas/MainWindow.xaml | 13 +++
Get.ps1 | 1 +
Win11Debloat.ps1 | 170 +++++++++++++++++++++++++++------
3 files changed, 156 insertions(+), 28 deletions(-)
diff --git a/Assets/Schemas/MainWindow.xaml b/Assets/Schemas/MainWindow.xaml
index 5ef53a3..9e59324 100644
--- a/Assets/Schemas/MainWindow.xaml
+++ b/Assets/Schemas/MainWindow.xaml
@@ -885,6 +885,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Get.ps1 b/Get.ps1
index 1bb5710..efd9c18 100644
--- a/Get.ps1
+++ b/Get.ps1
@@ -12,6 +12,7 @@ param (
[switch]$RunDefaultsLite,
[switch]$RunSavedSettings,
[string]$Apps,
+ [string]$AppRemovalTarget,
[switch]$RemoveApps,
[switch]$RemoveAppsCustom,
[switch]$RemoveGamingApps,
diff --git a/Win11Debloat.ps1 b/Win11Debloat.ps1
index b85e903..434ce60 100755
--- a/Win11Debloat.ps1
+++ b/Win11Debloat.ps1
@@ -14,6 +14,7 @@ param (
[switch]$RunDefaultsLite,
[switch]$RunSavedSettings,
[string]$Apps,
+ [string]$AppRemovalTarget,
[switch]$RemoveApps,
[switch]$RemoveAppsCustom,
[switch]$RemoveGamingApps,
@@ -99,7 +100,7 @@ $script:AppSelectionSchema = "$script:AssetsPath/Schemas/AppSelectionWindow.xaml
$script:MainWindowSchema = "$script:AssetsPath/Schemas/MainWindow.xaml"
$script:FeaturesFilePath = "$script:AssetsPath/Features.json"
-$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Silent', 'Sysprep', 'User', 'NoRestartExplorer', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'RunAppsListGenerator', 'CLI'
+$script:ControlParams = 'WhatIf', 'Confirm', 'Verbose', 'Debug', 'LogPath', 'Silent', 'Sysprep', 'User', 'NoRestartExplorer', 'RunDefaults', 'RunDefaultsLite', 'RunSavedSettings', 'RunAppsListGenerator', 'CLI', 'AppRemovalTarget'
# Script-level variables for GUI elements
$script:GuiConsoleOutput = $null
@@ -1397,6 +1398,11 @@ function OpenGUI {
$otherUsernameTextBox = $window.FindName('OtherUsernameTextBox')
$usernameTextBoxPlaceholder = $window.FindName('UsernameTextBoxPlaceholder')
$usernameValidationMessage = $window.FindName('UsernameValidationMessage')
+ $appRemovalScopeCombo = $window.FindName('AppRemovalScopeCombo')
+ $appRemovalScopeDescription = $window.FindName('AppRemovalScopeDescription')
+ $appRemovalScopeSection = $window.FindName('AppRemovalScopeSection')
+ $appRemovalScopeCurrentUser = $window.FindName('AppRemovalScopeCurrentUser')
+ $appRemovalScopeTargetUser = $window.FindName('AppRemovalScopeTargetUser')
# Navigation button handlers
function UpdateNavigationButtons {
@@ -1469,20 +1475,61 @@ function OpenGUI {
$userSelectionDescription.Text = "Changes will be applied to the currently logged-in user profile."
$otherUserPanel.Visibility = 'Collapsed'
$usernameValidationMessage.Text = ""
+ # Show "Current user only" option, hide "Target user only" option
+ $appRemovalScopeCurrentUser.Visibility = 'Visible'
+ $appRemovalScopeTargetUser.Visibility = 'Collapsed'
+ # Enable app removal scope selection for current user
+ $appRemovalScopeCombo.IsEnabled = $true
+ $appRemovalScopeCombo.SelectedIndex = 0
}
1 {
$userSelectionDescription.Text = "Changes will be applied to a different user profile on this system."
$otherUserPanel.Visibility = 'Visible'
$usernameValidationMessage.Text = ""
+ # Hide "Current user only" option, show "Target user only" option
+ $appRemovalScopeCurrentUser.Visibility = 'Collapsed'
+ $appRemovalScopeTargetUser.Visibility = 'Visible'
+ # Enable app removal scope selection for other user
+ $appRemovalScopeCombo.IsEnabled = $true
+ $appRemovalScopeCombo.SelectedIndex = 0
}
2 {
$userSelectionDescription.Text = "Changes will be applied to the default user template, affecting all new users created after this point. Useful for Sysprep deployment."
$otherUserPanel.Visibility = 'Collapsed'
$usernameValidationMessage.Text = ""
+ # Hide other user options since they don't apply to default user template
+ $appRemovalScopeCurrentUser.Visibility = 'Collapsed'
+ $appRemovalScopeTargetUser.Visibility = 'Collapsed'
+ # Lock app removal scope to "All users" when applying to sysprep
+ $appRemovalScopeCombo.IsEnabled = $false
+ $appRemovalScopeCombo.SelectedIndex = 0
}
}
})
+ # Helper function to update app removal scope description
+ function UpdateAppRemovalScopeDescription {
+ $selectedItem = $appRemovalScopeCombo.SelectedItem
+ if ($selectedItem) {
+ switch ($selectedItem.Content) {
+ "All users" {
+ $appRemovalScopeDescription.Text = "Apps will be removed for all users and from the Windows image to prevent reinstallation for new users."
+ }
+ "Current user only" {
+ $appRemovalScopeDescription.Text = "Apps will only be removed for the current user. Other users and new users will not be affected."
+ }
+ "Target user only" {
+ $appRemovalScopeDescription.Text = "Apps will only be removed for the specified target user. Other users and new users will not be affected."
+ }
+ }
+ }
+ }
+
+ # Update app removal scope description
+ $appRemovalScopeCombo.Add_SelectionChanged({
+ UpdateAppRemovalScopeDescription
+ })
+
$otherUsernameTextBox.Add_TextChanged({
# Show/hide placeholder
if ([string]::IsNullOrWhiteSpace($otherUsernameTextBox.Text)) {
@@ -1549,6 +1596,22 @@ function OpenGUI {
$changesList += "Remove $selectedAppsCount selected application(s)"
}
+ # Update app removal scope section based on whether apps are selected
+ if ($selectedAppsCount -gt 0) {
+ # Enable app removal scope selection (unless locked by sysprep mode)
+ if ($userSelectionCombo.SelectedIndex -ne 2) {
+ $appRemovalScopeCombo.IsEnabled = $true
+ }
+ $appRemovalScopeSection.Opacity = 1.0
+ UpdateAppRemovalScopeDescription
+ }
+ else {
+ # Disable app removal scope selection when no apps selected
+ $appRemovalScopeCombo.IsEnabled = $false
+ $appRemovalScopeSection.Opacity = 0.5
+ $appRemovalScopeDescription.Text = "No apps selected for removal."
+ }
+
# Collect all ComboBox/CheckBox selections from dynamically created controls
if ($script:UiControlMappings) {
foreach ($mappingKey in $script:UiControlMappings.Keys) {
@@ -1628,13 +1691,6 @@ function OpenGUI {
return
}
- $controlParamsCount = 0
- foreach ($Param in $script:ControlParams) {
- if ($script:Params.ContainsKey($Param)) {
- $controlParamsCount++
- }
- }
-
# App Removal - collect selected apps from integrated UI
$selectedApps = @()
foreach ($child in $appsPanel.Children) {
@@ -1660,6 +1716,23 @@ function OpenGUI {
AddParameter 'RemoveApps'
AddParameter 'Apps' ($selectedApps -join ',')
+
+ # Add app removal target parameter based on selection
+ $selectedScopeItem = $appRemovalScopeCombo.SelectedItem
+ if ($selectedScopeItem) {
+ switch ($selectedScopeItem.Content) {
+ "All users" {
+ AddParameter 'AppRemovalTarget' 'AllUsers'
+ }
+ "Current user only" {
+ AddParameter 'AppRemovalTarget' 'CurrentUser'
+ }
+ "Target user only" {
+ # Use the target username from Other User panel
+ AddParameter 'AppRemovalTarget' ($otherUsernameTextBox.Text.Trim())
+ }
+ }
+ }
}
# Apply dynamic tweaks selections
@@ -1696,12 +1769,19 @@ function OpenGUI {
}
}
+ $controlParamsCount = 0
+ foreach ($Param in $script:ControlParams) {
+ if ($script:Params.ContainsKey($Param)) {
+ $controlParamsCount++
+ }
+ }
+
# Check if any changes were selected
$totalChanges = $script:Params.Count - $controlParamsCount
# Apps parameter does not count as a change itself
if ($script:Params.ContainsKey('Apps')) {
- $totalChanges--
+ $totalChanges = $totalChanges - 1
}
if ($totalChanges -eq 0) {
@@ -2216,12 +2296,37 @@ function GetInstalledAppsViaWinget {
}
-# Removes apps specified during function call from all user accounts and from the OS image.
+# Target is determined from $script:Params["AppRemovalTarget"] or defaults to "AllUsers"
+# Target values: "AllUsers" (removes for all users + from image), "CurrentUser", or a specific username
+function GetTargetUserForAppRemoval {
+ if ($script:Params.ContainsKey("AppRemovalTarget")) {
+ return $script:Params["AppRemovalTarget"]
+ }
+
+ return "AllUsers"
+}
+
+
+function GetFriendlyTargetUserName {
+ $target = GetTargetUserForAppRemoval
+
+ switch ($target) {
+ "AllUsers" { return "all users" }
+ "CurrentUser" { return "the current user" }
+ default { return "user $target" }
+ }
+}
+
+
+# Removes apps specified during function call based on the target scope.
function RemoveApps {
param (
$appslist
)
+ # Determine target from script-level params, defaulting to AllUsers
+ $targetUser = GetTargetUserForAppRemoval
+
Foreach ($app in $appsList) {
if ($script:CancelRequested) {
return
@@ -2276,31 +2381,38 @@ function RemoveApps {
}
# Use Remove-AppxPackage to remove all other apps
- $app = '*' + $app + '*'
+ $appPattern = '*' + $app + '*'
- # Remove installed app for all existing users
try {
- Get-AppxPackage -Name $app -AllUsers | Remove-AppxPackage -AllUsers -ErrorAction Continue
+ switch ($targetUser) {
+ "AllUsers" {
+ # Remove installed app for all existing users
+ Get-AppxPackage -Name $appPattern -AllUsers | Remove-AppxPackage -AllUsers -ErrorAction Continue
- if ($DebugPreference -ne "SilentlyContinue") {
- Write-ToConsole "Removed $app for all users" -ForegroundColor DarkGray
+ # Remove provisioned app from OS image, so the app won't be installed for any new users
+ Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like $appPattern } | ForEach-Object { Remove-ProvisionedAppxPackage -Online -AllUsers -PackageName $_.PackageName }
+ }
+ "CurrentUser" {
+ # Remove installed app for current user only
+ Get-AppxPackage -Name $appPattern | Remove-AppxPackage -ErrorAction Continue
+ }
+ default {
+ # Target is a specific username - remove app for that user only
+ # Get the user's SID
+ $userAccount = New-Object System.Security.Principal.NTAccount($targetUser)
+ $userSid = $userAccount.Translate([System.Security.Principal.SecurityIdentifier]).Value
+
+ # Remove the app package for the specific user
+ Get-AppxPackage -Name $appPattern -User $userSid | Remove-AppxPackage -User $userSid -ErrorAction Continue
+ }
}
}
catch {
if ($DebugPreference -ne "SilentlyContinue") {
- Write-ToConsole "Unable to remove $app for all users" -ForegroundColor Yellow
+ Write-ToConsole "Something went wrong while trying to remove $app" -ForegroundColor Yellow
Write-Host $psitem.Exception.StackTrace -ForegroundColor Gray
}
}
-
- # Remove provisioned app from OS image, so the app won't be installed for any new users
- try {
- Get-AppxProvisionedPackage -Online | Where-Object { $_.PackageName -like $app } | ForEach-Object { Remove-ProvisionedAppxPackage -Online -AllUsers -PackageName $_.PackageName }
- }
- catch {
- Write-ToConsole "Unable to remove $app from windows image" -ForegroundColor Yellow
- Write-Host $psitem.Exception.StackTrace -ForegroundColor Gray
- }
}
Write-ToConsole ""
@@ -2522,7 +2634,6 @@ function RegImport {
reg load "HKU\Default" $userPath | Out-Null
$regOutput = reg import "$script:RegfilesPath\Sysprep\$path" 2>&1
reg unload "HKU\Default" | Out-Null
-
}
else {
$regOutput = reg import "$script:RegfilesPath\$path" 2>&1
@@ -2888,7 +2999,7 @@ function ExecuteParameter {
# Handle features without RegistryKey or with special logic
switch ($paramKey) {
'RemoveApps' {
- Write-ToConsole "> Removing selected apps..."
+ Write-ToConsole "> Removing selected apps for $(GetFriendlyTargetUserName)..."
$appsList = GenerateAppsList
if ($appsList.Count -eq 0) {
@@ -3297,10 +3408,13 @@ if ($script:Params.ContainsKey("Sysprep")) {
}
}
-# Make sure all requirements for User mode are met, if User is specified
+# Ensure that target user exists, if User or AppRemovalTarget parameter was provided
if ($script:Params.ContainsKey("User")) {
$userPath = GetUserDirectory -userName $script:Params.Item("User")
}
+if ($script:Params.ContainsKey("AppRemovalTarget")) {
+ $userPath = GetUserDirectory -userName $script:Params.Item("AppRemovalTarget")
+}
# Remove LastUsedSettings.json file if it exists and is empty
if ((Test-Path $script:SavedSettingsFilePath) -and ([String]::IsNullOrWhiteSpace((Get-content $script:SavedSettingsFilePath)))) {