f47359440f
Force .NET garbage collection before reg.exe unload to release open RegistryKey handles left by PowerShell registry cmdlets. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
413 lines
14 KiB
PowerShell
413 lines
14 KiB
PowerShell
# Requires running as administrator
|
|
# Removes selected built-in Appx packages without winget.
|
|
# Installed packages are removed for existing users when supported.
|
|
# Provisioned packages are removed so they are not installed for new users.
|
|
|
|
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
param(
|
|
[switch]$IncludeOneDrive
|
|
)
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
|
|
$IsAdministrator = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator
|
|
)
|
|
|
|
if (-not $IsAdministrator) {
|
|
Write-Error "This script must be run from an administrator PowerShell session."
|
|
exit 1
|
|
}
|
|
|
|
$PackageNamePatterns = @(
|
|
"MicrosoftWindows.Client.WebExperience",
|
|
"Microsoft.OutlookForWindows",
|
|
"Microsoft.WindowsFeedbackHub",
|
|
"Microsoft.XboxApp",
|
|
"Microsoft.GamingApp",
|
|
"Microsoft.Xbox.TCUI",
|
|
"Microsoft.XboxGameOverlay",
|
|
"Microsoft.XboxGamingOverlay",
|
|
"Microsoft.XboxIdentityProvider",
|
|
"Microsoft.XboxSpeechToTextOverlay",
|
|
"Microsoft.PowerAutomateDesktop",
|
|
"Microsoft.MicrosoftStickyNotes",
|
|
"Microsoft.BingWeather",
|
|
"MSTeams",
|
|
"MicrosoftTeams",
|
|
"Microsoft.Todos",
|
|
"Microsoft.BingSearch",
|
|
"Microsoft.BingNews",
|
|
"Clipchamp.Clipchamp"
|
|
)
|
|
|
|
$KnownPackageFamilyNames = @(
|
|
"MicrosoftWindows.Client.WebExperience_cw5n1h2txyewy",
|
|
"Microsoft.OutlookForWindows_8wekyb3d8bbwe",
|
|
"Microsoft.WindowsFeedbackHub_8wekyb3d8bbwe",
|
|
"Microsoft.XboxApp_8wekyb3d8bbwe",
|
|
"Microsoft.GamingApp_8wekyb3d8bbwe",
|
|
"Microsoft.Xbox.TCUI_8wekyb3d8bbwe",
|
|
"Microsoft.XboxGameOverlay_8wekyb3d8bbwe",
|
|
"Microsoft.XboxGamingOverlay_8wekyb3d8bbwe",
|
|
"Microsoft.XboxIdentityProvider_8wekyb3d8bbwe",
|
|
"Microsoft.XboxSpeechToTextOverlay_8wekyb3d8bbwe",
|
|
"Microsoft.PowerAutomateDesktop_8wekyb3d8bbwe",
|
|
"Microsoft.MicrosoftStickyNotes_8wekyb3d8bbwe",
|
|
"Microsoft.BingWeather_8wekyb3d8bbwe",
|
|
"MSTeams_8wekyb3d8bbwe",
|
|
"MicrosoftTeams_8wekyb3d8bbwe",
|
|
"Microsoft.Todos_8wekyb3d8bbwe",
|
|
"Microsoft.BingSearch_8wekyb3d8bbwe",
|
|
"Microsoft.BingNews_8wekyb3d8bbwe",
|
|
"Clipchamp.Clipchamp_yxz26nhyzhsrt"
|
|
)
|
|
|
|
$Script:PackageFamilyNamesToBlock = @()
|
|
|
|
function Add-PackageFamilyNameToBlockList {
|
|
param(
|
|
[string]$PackageFamilyName
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($PackageFamilyName)) {
|
|
return
|
|
}
|
|
|
|
if ($Script:PackageFamilyNamesToBlock -notcontains $PackageFamilyName) {
|
|
$Script:PackageFamilyNamesToBlock += $PackageFamilyName
|
|
}
|
|
}
|
|
|
|
function Get-PackageFamilyNameFromProvisionedPackage {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
$Package
|
|
)
|
|
|
|
$Parts = $Package.PackageName -split "_"
|
|
|
|
if ($Parts.Count -lt 2) {
|
|
return $null
|
|
}
|
|
|
|
$PublisherId = $Parts[-1]
|
|
return "$($Package.DisplayName)_$PublisherId"
|
|
}
|
|
|
|
function Invoke-InstalledAppxPackageRemoval {
|
|
param(
|
|
[string]$Pattern
|
|
)
|
|
|
|
$Packages = Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -like "*$Pattern*" -or $_.PackageFamilyName -like "*$Pattern*" }
|
|
|
|
if (-not $Packages) {
|
|
Write-Host "No installed Appx package found for: $Pattern" -ForegroundColor DarkGray
|
|
return
|
|
}
|
|
|
|
foreach ($Package in $Packages) {
|
|
Add-PackageFamilyNameToBlockList -PackageFamilyName $Package.PackageFamilyName
|
|
|
|
$Target = "$($Package.Name) [$($Package.PackageFullName)]"
|
|
Write-Host "Removing installed package: $Target" -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($Target, "Remove installed Appx package")) {
|
|
try {
|
|
Remove-AppxPackage -Package $Package.PackageFullName -AllUsers -ErrorAction Stop
|
|
Write-Host "Removed installed package: $($Package.Name)" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Warning "Failed to remove installed package $($Package.Name): $_"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Invoke-ProvisionedAppxPackageRemoval {
|
|
param(
|
|
[string]$Pattern
|
|
)
|
|
|
|
$ProvisionedPackages = Get-AppxProvisionedPackage -Online |
|
|
Where-Object {
|
|
$_.DisplayName -like "*$Pattern*" -or
|
|
$_.PackageName -like "*$Pattern*"
|
|
}
|
|
|
|
if (-not $ProvisionedPackages) {
|
|
Write-Host "No provisioned Appx package found for: $Pattern" -ForegroundColor DarkGray
|
|
return
|
|
}
|
|
|
|
foreach ($Package in $ProvisionedPackages) {
|
|
Add-PackageFamilyNameToBlockList -PackageFamilyName (Get-PackageFamilyNameFromProvisionedPackage -Package $Package)
|
|
|
|
$Target = "$($Package.DisplayName) [$($Package.PackageName)]"
|
|
Write-Host "Removing provisioned package: $Target" -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($Target, "Remove provisioned Appx package for future users")) {
|
|
try {
|
|
Remove-AppxProvisionedPackage -Online -PackageName $Package.PackageName -ErrorAction Stop | Out-Null
|
|
Write-Host "Removed provisioned package: $($Package.DisplayName)" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Warning "Failed to remove provisioned package $($Package.DisplayName): $_"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Set-DWordValue {
|
|
param(
|
|
[string]$Path,
|
|
[string]$Name,
|
|
[int]$Value
|
|
)
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
New-Item -Path $Path -Force | Out-Null
|
|
}
|
|
|
|
New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType DWord -Force | Out-Null
|
|
}
|
|
|
|
function Remove-RegistryValue {
|
|
param(
|
|
[string]$Path,
|
|
[string]$Name
|
|
)
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
return
|
|
}
|
|
|
|
$Property = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
|
|
|
|
if ($null -eq $Property) {
|
|
return
|
|
}
|
|
|
|
Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
function Set-ConsumerExperiencePolicies {
|
|
$CloudContentPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent"
|
|
|
|
Write-Host "Disabling Microsoft consumer experiences policy..." -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($CloudContentPath, "Set CloudContent policies")) {
|
|
Set-DWordValue -Path $CloudContentPath -Name "DisableWindowsConsumerFeatures" -Value 1
|
|
Set-DWordValue -Path $CloudContentPath -Name "DisableCloudOptimizedContent" -Value 1
|
|
Write-Host "Microsoft consumer experience policies configured." -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
function Set-OneDrivePolicies {
|
|
$WindowsOneDrivePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive"
|
|
$OneDrivePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\OneDrive"
|
|
|
|
Write-Host "Disabling OneDrive for all users..." -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($WindowsOneDrivePolicyPath, "Set OneDrive disable policy")) {
|
|
Set-DWordValue -Path $WindowsOneDrivePolicyPath -Name "DisableFileSyncNGSC" -Value 1
|
|
Set-DWordValue -Path $OneDrivePolicyPath -Name "PreventNetworkTrafficPreUserSignIn" -Value 1
|
|
Write-Host "OneDrive policies configured." -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
function Set-ContentDeliveryManagerDefaults {
|
|
param(
|
|
[string]$RegistryRoot
|
|
)
|
|
|
|
$ContentDeliveryPath = Join-Path $RegistryRoot "SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager"
|
|
$ValuesToDisable = @(
|
|
"ContentDeliveryAllowed",
|
|
"FeatureManagementEnabled",
|
|
"OemPreInstalledAppsEnabled",
|
|
"PreInstalledAppsEnabled",
|
|
"PreInstalledAppsEverEnabled",
|
|
"SilentInstalledAppsEnabled",
|
|
"SubscribedContent-310093Enabled",
|
|
"SubscribedContent-338387Enabled",
|
|
"SubscribedContent-338388Enabled",
|
|
"SubscribedContent-338389Enabled",
|
|
"SubscribedContent-338393Enabled",
|
|
"SubscribedContent-353694Enabled",
|
|
"SubscribedContent-353696Enabled",
|
|
"SystemPaneSuggestionsEnabled"
|
|
)
|
|
|
|
Write-Host "Configuring ContentDeliveryManager defaults in: $RegistryRoot" -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($RegistryRoot, "Disable ContentDeliveryManager app suggestions")) {
|
|
foreach ($ValueName in $ValuesToDisable) {
|
|
Set-DWordValue -Path $ContentDeliveryPath -Name $ValueName -Value 0
|
|
}
|
|
Write-Host "ContentDeliveryManager defaults configured." -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
function Remove-OneDriveStartupFromRegistryRoot {
|
|
param(
|
|
[string]$RegistryRoot
|
|
)
|
|
|
|
$RunPath = Join-Path $RegistryRoot "SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
|
|
$RunOncePath = Join-Path $RegistryRoot "SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
|
|
|
|
Write-Host "Removing OneDrive startup entries in: $RegistryRoot" -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($RegistryRoot, "Remove OneDrive startup registry entries")) {
|
|
Remove-RegistryValue -Path $RunPath -Name "OneDrive"
|
|
Remove-RegistryValue -Path $RunPath -Name "OneDriveSetup"
|
|
Remove-RegistryValue -Path $RunOncePath -Name "OneDrive"
|
|
Remove-RegistryValue -Path $RunOncePath -Name "OneDriveSetup"
|
|
Write-Host "OneDrive startup entries removed where present." -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
function Remove-OneDriveShortcutsFromDefaultProfile {
|
|
$ShortcutPaths = @(
|
|
(Join-Path $env:SystemDrive "Users\Default\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"),
|
|
(Join-Path $env:ProgramData "Microsoft\Windows\Start Menu\Programs\OneDrive.lnk")
|
|
)
|
|
|
|
foreach ($ShortcutPath in $ShortcutPaths) {
|
|
if (-not (Test-Path $ShortcutPath)) {
|
|
continue
|
|
}
|
|
|
|
Write-Host "Removing OneDrive shortcut: $ShortcutPath" -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($ShortcutPath, "Remove OneDrive shortcut")) {
|
|
Remove-Item -Path $ShortcutPath -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
}
|
|
|
|
function Set-DefaultUserProfileDefaults {
|
|
$DefaultUserHive = "Registry::HKEY_USERS\DefaultUser"
|
|
$DefaultUserDat = Join-Path $env:SystemDrive "Users\Default\NTUSER.DAT"
|
|
$HiveWasLoaded = $false
|
|
|
|
if (-not (Test-Path $DefaultUserDat)) {
|
|
Write-Warning "Default user hive not found: $DefaultUserDat"
|
|
return
|
|
}
|
|
|
|
if (-not (Test-Path $DefaultUserHive)) {
|
|
Write-Host "Loading default user registry hive..." -ForegroundColor Cyan
|
|
& reg.exe load "HKU\DefaultUser" $DefaultUserDat | Out-Null
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Warning "Failed to load default user registry hive."
|
|
return
|
|
}
|
|
|
|
$HiveWasLoaded = $true
|
|
}
|
|
|
|
try {
|
|
Set-ContentDeliveryManagerDefaults -RegistryRoot $DefaultUserHive
|
|
Remove-OneDriveStartupFromRegistryRoot -RegistryRoot $DefaultUserHive
|
|
}
|
|
finally {
|
|
if ($HiveWasLoaded) {
|
|
Write-Host "Unloading default user registry hive..." -ForegroundColor Cyan
|
|
[GC]::Collect()
|
|
[GC]::WaitForPendingFinalizers()
|
|
& reg.exe unload "HKU\DefaultUser" | Out-Null
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Warning "Failed to unload default user registry hive. Close registry tools and retry."
|
|
}
|
|
}
|
|
}
|
|
|
|
Remove-OneDriveShortcutsFromDefaultProfile
|
|
}
|
|
|
|
function Set-AppxDeprovisionedRegistryMarkers {
|
|
param(
|
|
[string[]]$PackageFamilyNames
|
|
)
|
|
|
|
$DeprovisionedPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Appx\AppxAllUserStore\Deprovisioned"
|
|
$PolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Appx\RemoveDefaultMicrosoftStorePackages"
|
|
|
|
if (-not $PackageFamilyNames) {
|
|
return
|
|
}
|
|
|
|
Write-Host "Writing Appx deprovisioning registry markers..." -ForegroundColor Cyan
|
|
|
|
foreach ($PackageFamilyName in ($PackageFamilyNames | Sort-Object -Unique)) {
|
|
if ($PSCmdlet.ShouldProcess($PackageFamilyName, "Block Appx provisioning for future users")) {
|
|
New-Item -Path (Join-Path $DeprovisionedPath $PackageFamilyName) -Force | Out-Null
|
|
Set-DWordValue -Path (Join-Path $PolicyPath $PackageFamilyName) -Name "RemovePackage" -Value 1
|
|
Write-Host "Blocked future provisioning: $PackageFamilyName" -ForegroundColor Green
|
|
}
|
|
}
|
|
}
|
|
|
|
function Invoke-OneDriveRemoval {
|
|
$Installers = @(
|
|
"$env:SystemRoot\System32\OneDriveSetup.exe",
|
|
"$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
|
|
)
|
|
|
|
foreach ($Installer in $Installers) {
|
|
if (-not (Test-Path $Installer)) {
|
|
continue
|
|
}
|
|
|
|
Write-Host "Removing OneDrive with: $Installer" -ForegroundColor Cyan
|
|
|
|
if ($PSCmdlet.ShouldProcess($Installer, "Uninstall OneDrive")) {
|
|
try {
|
|
Start-Process -FilePath $Installer -ArgumentList "/uninstall" -Wait -NoNewWindow
|
|
Write-Host "OneDrive uninstall command completed." -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Warning "Failed to uninstall OneDrive with $Installer`: $_"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Host "Removing selected built-in Appx applications..." -ForegroundColor Cyan
|
|
Write-Host "This can affect existing users and the default package set for new users." -ForegroundColor Yellow
|
|
Write-Host ""
|
|
|
|
foreach ($PackageFamilyName in $KnownPackageFamilyNames) {
|
|
Add-PackageFamilyNameToBlockList -PackageFamilyName $PackageFamilyName
|
|
}
|
|
|
|
Set-ConsumerExperiencePolicies
|
|
Set-OneDrivePolicies
|
|
Set-ContentDeliveryManagerDefaults -RegistryRoot "HKCU:"
|
|
Remove-OneDriveStartupFromRegistryRoot -RegistryRoot "HKCU:"
|
|
Set-DefaultUserProfileDefaults
|
|
|
|
foreach ($Pattern in $PackageNamePatterns) {
|
|
Invoke-InstalledAppxPackageRemoval -Pattern $Pattern
|
|
Invoke-ProvisionedAppxPackageRemoval -Pattern $Pattern
|
|
Write-Host ""
|
|
}
|
|
|
|
Set-AppxDeprovisionedRegistryMarkers -PackageFamilyNames $Script:PackageFamilyNamesToBlock
|
|
|
|
if ($IncludeOneDrive) {
|
|
Invoke-OneDriveRemoval
|
|
}
|
|
else {
|
|
Write-Host "OneDrive was not removed. Re-run with -IncludeOneDrive to call the built-in OneDrive uninstaller." -ForegroundColor Yellow
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Done. Restart Windows before creating new user profiles." -ForegroundColor Green
|