Files
lxs/windows/tools/debloat.ps1
T

484 lines
23 KiB
PowerShell

<#
LXS - Debloat and privacy (Windows)
Description: Remove preinstalled apps you pick, and turn off telemetry,
Cortana, ads, tracking tasks, Copilot and Recall.
Safety rules this script follows:
- a restore point is created before the first change of a session
- no app is removed unless you explicitly select it
- the registry/service/task tweaks can be undone from the menu (option 8)
- apps you remove come back only by reinstalling them from the Store
Repo: https://git.hyko.cx/hykocx/lxs
#>
# Load LXS common library (colors, UI helpers, spinner, loggers, guards).
# Prefers the sibling ..\lib\common.ps1 (repo checkout or installed layout) and
# only hits the network when this script is run standalone.
$LxsRawBase = if ($env:LXS_RAW_BASE) { $env:LXS_RAW_BASE } else { 'https://git.hyko.cx/hykocx/lxs/raw/branch/main' }
if (-not $env:LXS_RAW_PLATFORM_BASE) { $env:LXS_RAW_PLATFORM_BASE = "$LxsRawBase/windows" }
$LxsLibPath = if ($PSScriptRoot) { Join-Path $PSScriptRoot '..\lib\common.ps1' } else { $null }
if ($LxsLibPath -and (Test-Path $LxsLibPath)) {
. $LxsLibPath
} else {
try {
$LxsLibSource = Invoke-RestMethod -Uri "$env:LXS_RAW_PLATFORM_BASE/lib/common.ps1" -UseBasicParsing -ErrorAction Stop
} catch {
Write-Error 'Failed to fetch lib/common.ps1'
exit 1
}
. ([scriptblock]::Create($LxsLibSource))
}
$env:LXS_LOG_FILE = Join-Path (Get-LxsTempDir) 'lxs_debloat.log'
if (-not (Assert-LxsWindows)) { exit 1 }
Assert-LxsAdmin -ScriptPath $PSCommandPath -Arguments $args
$script:LxsCheckpointDone = $false
# One restore point per session, created lazily before the first real change.
function Assert-LxsCheckpoint {
if ($script:LxsCheckpointDone) { return $true }
Write-Host ''
Write-LxsInfo 'Creating a restore point before changing anything...'
$ok = New-LxsRestorePoint -Description 'LXS before debloat'
if (-not $ok) {
Write-Host ''
Write-LxsWarn 'The restore point could not be created.'
if (-not (Confirm-LxsAction -Question 'Continue anyway, without a rollback point?')) {
return $false
}
}
$script:LxsCheckpointDone = $true
return $true
}
# ═══════════════════════════════════════════════════════════════════════════
# Preinstalled apps
#
# Only packages on this list are ever offered. Anything not listed here —
# including the Store, App Installer (winget), Terminal, Defender UI, the
# VCLibs/.NET runtime frameworks and every driver package — is left alone,
# because removing those breaks Windows or LXS itself.
# ═══════════════════════════════════════════════════════════════════════════
$LxsRemovableApps = @(
@{ Pattern = 'Microsoft.3DBuilder'; Label = '3D Builder' }
@{ Pattern = 'Microsoft.549981C3F5F10'; Label = 'Cortana' }
@{ Pattern = 'Microsoft.BingFinance'; Label = 'Bing Finance' }
@{ Pattern = 'Microsoft.BingNews'; Label = 'Bing News' }
@{ Pattern = 'Microsoft.BingSports'; Label = 'Bing Sports' }
@{ Pattern = 'Microsoft.BingWeather'; Label = 'Weather' }
@{ Pattern = 'Microsoft.BingSearch'; Label = 'Web Search (Bing)' }
@{ Pattern = 'Microsoft.GetHelp'; Label = 'Get Help' }
@{ Pattern = 'Microsoft.Getstarted'; Label = 'Tips / Get Started' }
@{ Pattern = 'Microsoft.Messaging'; Label = 'Messaging' }
@{ Pattern = 'Microsoft.Microsoft3DViewer'; Label = '3D Viewer' }
@{ Pattern = 'Microsoft.MicrosoftJournal'; Label = 'Journal' }
@{ Pattern = 'Microsoft.MicrosoftOfficeHub'; Label = 'Office Hub' }
@{ Pattern = 'Microsoft.MicrosoftSolitaireCollection'; Label = 'Solitaire Collection' }
@{ Pattern = 'Microsoft.MixedReality.Portal'; Label = 'Mixed Reality Portal' }
@{ Pattern = 'Microsoft.NetworkSpeedTest'; Label = 'Network Speed Test' }
@{ Pattern = 'Microsoft.News'; Label = 'News' }
@{ Pattern = 'Microsoft.Office.OneNote'; Label = 'OneNote (Store version)' }
@{ Pattern = 'Microsoft.Office.Sway'; Label = 'Sway' }
@{ Pattern = 'Microsoft.OneConnect'; Label = 'Mobile Plans' }
@{ Pattern = 'Microsoft.People'; Label = 'People' }
@{ Pattern = 'Microsoft.PowerAutomateDesktop'; Label = 'Power Automate Desktop' }
@{ Pattern = 'Microsoft.Print3D'; Label = 'Print 3D' }
@{ Pattern = 'Microsoft.SkypeApp'; Label = 'Skype' }
@{ Pattern = 'Microsoft.Todos'; Label = 'Microsoft To Do' }
@{ Pattern = 'Microsoft.Wallet'; Label = 'Wallet' }
@{ Pattern = 'Microsoft.WindowsAlarms'; Label = 'Alarms & Clock' }
@{ Pattern = 'Microsoft.WindowsFeedbackHub'; Label = 'Feedback Hub' }
@{ Pattern = 'Microsoft.WindowsMaps'; Label = 'Maps' }
@{ Pattern = 'Microsoft.WindowsSoundRecorder'; Label = 'Sound Recorder' }
@{ Pattern = 'Microsoft.Xbox.TCUI'; Label = 'Xbox TCUI' }
@{ Pattern = 'Microsoft.XboxApp'; Label = 'Xbox Console Companion' }
@{ Pattern = 'Microsoft.XboxGameOverlay'; Label = 'Xbox Game Overlay' }
@{ Pattern = 'Microsoft.XboxGamingOverlay'; Label = 'Xbox Game Bar' }
@{ Pattern = 'Microsoft.XboxIdentityProvider'; Label = 'Xbox Identity Provider' }
@{ Pattern = 'Microsoft.XboxSpeechToTextOverlay'; Label = 'Xbox Speech To Text' }
@{ Pattern = 'Microsoft.YourPhone'; Label = 'Phone Link' }
@{ Pattern = 'Microsoft.ZuneMusic'; Label = 'Media Player / Groove Music' }
@{ Pattern = 'Microsoft.ZuneVideo'; Label = 'Movies & TV' }
@{ Pattern = 'MicrosoftTeams'; Label = 'Teams (personal / chat)' }
@{ Pattern = 'MSTeams'; Label = 'Teams (new client)' }
@{ Pattern = 'Clipchamp.Clipchamp'; Label = 'Clipchamp' }
@{ Pattern = 'Microsoft.Copilot'; Label = 'Copilot app' }
@{ Pattern = 'Microsoft.WindowsFamilySafety'; Label = 'Family Safety' }
@{ Pattern = 'SpotifyAB.SpotifyMusic'; Label = 'Spotify (OEM stub)' }
@{ Pattern = 'Disney.37853FC22B2CE'; Label = 'Disney+ (OEM stub)' }
)
function Get-LxsInstalledBloat {
$found = @()
foreach ($entry in $LxsRemovableApps) {
try {
$pkgs = @(Get-AppxPackage -Name $entry.Pattern -ErrorAction SilentlyContinue)
} catch {
$pkgs = @()
}
if ($pkgs.Count -gt 0) {
$found += [pscustomobject]@{
Label = $entry.Label
Pattern = $entry.Pattern
Name = $pkgs[0].Name
Version = $pkgs[0].Version
}
}
}
return $found
}
function Remove-LxsSelectedApps {
Clear-Host
Show-LxsBoxTop -Title 'REMOVE PREINSTALLED APPS'
Write-Host ''
Write-LxsInfo 'Scanning installed packages...'
$found = @(Get-LxsInstalledBloat)
Write-Host ''
if ($found.Count -eq 0) {
Write-LxsOk 'None of the known preinstalled apps are present.'
return
}
for ($i = 0; $i -lt $found.Count; $i++) {
Write-Host (" $($script:Cyan)[{0,2}]$($script:NC) $($script:White){1,-32}$($script:NC) $($script:Gray){2}$($script:NC)" -f `
($i + 1), $found[$i].Label, $found[$i].Name)
}
Write-Host ''
Write-Host "$($script:Gray)Enter the numbers to remove, separated by commas (e.g. 1,4,7),$($script:NC)"
Write-Host "$($script:Gray)or 'all' for every entry above. Empty cancels — nothing is removed$($script:NC)"
Write-Host "$($script:Gray)unless you name it here.$($script:NC)"
Write-Host ''
$selection = Read-Host -Prompt 'Selection'
if ([string]::IsNullOrWhiteSpace($selection)) {
Write-LxsInfo 'Cancelled — nothing removed.'
return
}
$targets = @()
if ($selection.Trim() -ieq 'all') {
$targets = $found
} else {
foreach ($part in ($selection -split ',')) {
$part = $part.Trim()
if ($part -notmatch '^\d+$') {
Write-LxsErr "Not a number: $part"
return
}
$idx = [int]$part - 1
if ($idx -lt 0 -or $idx -ge $found.Count) {
Write-LxsErr "Out of range: $part"
return
}
$targets += $found[$idx]
}
}
$targets = @($targets | Sort-Object Name -Unique)
Write-Host ''
Write-Host 'These will be removed for the current user:'
foreach ($t in $targets) { Write-Host " - $($t.Label)" }
Write-Host ''
Write-Host "$($script:Gray)They can be reinstalled later from the Microsoft Store.$($script:NC)"
Write-Host ''
if (-not (Confirm-LxsAction -Question "Remove $($targets.Count) app(s)?")) {
Write-LxsInfo 'Cancelled.'
return
}
if (-not (Assert-LxsCheckpoint)) { return }
$alsoProvisioned = Confirm-LxsAction -Question 'Also remove them from the image, so new user accounts do not get them?'
Write-Host ''
foreach ($t in $targets) {
try {
Get-AppxPackage -Name $t.Pattern -ErrorAction Stop | Remove-AppxPackage -ErrorAction Stop
Write-LxsOk "$($t.Label) removed"
} catch {
Write-LxsWarn "$($t.Label): $($_.Exception.Message)"
}
if ($alsoProvisioned) {
try {
$prov = @(Get-AppxProvisionedPackage -Online -ErrorAction Stop |
Where-Object { $_.DisplayName -like $t.Pattern })
foreach ($p in $prov) {
Remove-AppxProvisionedPackage -Online -PackageName $p.PackageName -ErrorAction Stop | Out-Null
Write-Host "$($script:Gray) also removed from the image$($script:NC)"
}
} catch {
Write-LxsWarn "$($t.Label): could not remove the provisioned package."
}
}
}
}
# ═══════════════════════════════════════════════════════════════════════════
# Privacy tweaks
#
# Every entry carries the value to set and the value to restore, so option 8
# can undo exactly what option 2-6 did. Default = $null means "delete the
# value", which is what returns a policy key to its Windows default.
# ═══════════════════════════════════════════════════════════════════════════
$LxsTweaks = @(
# --- telemetry ---
@{ Group = 'telemetry'; Label = 'Diagnostic data set to the minimum';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection'; Name = 'AllowTelemetry'; Value = 0; Default = $null }
@{ Group = 'telemetry'; Label = 'Do not send device names with telemetry';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection'; Name = 'AllowDeviceNameInTelemetry'; Value = 0; Default = $null }
@{ Group = 'telemetry'; Label = 'Advertising ID disabled';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo'; Name = 'Enabled'; Value = 0; Default = 1 }
@{ Group = 'telemetry'; Label = 'App launch tracking disabled';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced'; Name = 'Start_TrackProgs'; Value = 0; Default = 1 }
@{ Group = 'telemetry'; Label = 'Feedback requests disabled';
Path = 'HKCU:\SOFTWARE\Microsoft\Siuf\Rules'; Name = 'NumberOfSIUFInPeriod'; Value = 0; Default = $null }
# --- cortana / search ---
@{ Group = 'cortana'; Label = 'Cortana disabled';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'; Name = 'AllowCortana'; Value = 0; Default = $null }
@{ Group = 'cortana'; Label = 'Web results in Start disabled';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'; Name = 'DisableWebSearch'; Value = 1; Default = $null }
@{ Group = 'cortana'; Label = 'Connected web search disabled';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search'; Name = 'ConnectedSearchUseWeb'; Value = 0; Default = $null }
@{ Group = 'cortana'; Label = 'Search highlights disabled';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\SearchSettings'; Name = 'IsDynamicSearchBoxEnabled'; Value = 0; Default = 1 }
# --- ads and suggestions ---
@{ Group = 'ads'; Label = 'Start menu app suggestions off';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager'; Name = 'SystemPaneSuggestionsEnabled'; Value = 0; Default = 1 }
@{ Group = 'ads'; Label = 'Silent install of promoted apps off';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager'; Name = 'SilentInstalledAppsEnabled'; Value = 0; Default = 1 }
@{ Group = 'ads'; Label = 'Lock screen spotlight ads off';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager'; Name = 'RotatingLockScreenOverlayEnabled'; Value = 0; Default = 1 }
@{ Group = 'ads'; Label = 'Windows tips and tricks off';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager'; Name = 'SubscribedContent-338389Enabled'; Value = 0; Default = 1 }
@{ Group = 'ads'; Label = 'Suggested content in Settings off';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager'; Name = 'SubscribedContent-338393Enabled'; Value = 0; Default = 1 }
@{ Group = 'ads'; Label = 'Welcome experience after updates off';
Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager'; Name = 'SubscribedContent-310093Enabled'; Value = 0; Default = 1 }
# --- copilot / recall ---
@{ Group = 'ai'; Label = 'Windows Copilot turned off';
Path = 'HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot'; Name = 'TurnOffWindowsCopilot'; Value = 1; Default = $null }
@{ Group = 'ai'; Label = 'Windows Copilot turned off (machine-wide)';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot'; Name = 'TurnOffWindowsCopilot'; Value = 1; Default = $null }
@{ Group = 'ai'; Label = 'Recall snapshots disabled';
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI'; Name = 'DisableAIDataAnalysis'; Value = 1; Default = $null }
@{ Group = 'ai'; Label = 'Recall snapshots disabled (per user)';
Path = 'HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI'; Name = 'DisableAIDataAnalysis'; Value = 1; Default = $null }
)
$LxsTrackingTasks = @(
'\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraisal',
'\Microsoft\Windows\Application Experience\ProgramDataUpdater',
'\Microsoft\Windows\Application Experience\StartupAppTask',
'\Microsoft\Windows\Customer Experience Improvement Program\Consolidator',
'\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip',
'\Microsoft\Windows\Autochk\Proxy',
'\Microsoft\Windows\Feedback\Siuf\DmClient',
'\Microsoft\Windows\Feedback\Siuf\DmClientOnScenarioDownload'
)
$LxsTrackingServices = @(
@{ Name = 'DiagTrack'; Label = 'Connected User Experiences and Telemetry'; Default = 'Automatic' },
@{ Name = 'dmwappushservice'; Label = 'WAP Push Message Routing'; Default = 'Manual' }
)
function Invoke-LxsTweakGroup {
param(
[Parameter(Mandatory = $true)][string]$Group,
[Parameter(Mandatory = $true)][string]$Title
)
$entries = @($LxsTweaks | Where-Object { $_.Group -eq $Group })
Clear-Host
Show-LxsBoxTop -Title $Title
Write-Host ''
Write-Host 'The following settings will be changed:'
foreach ($e in $entries) { Write-Host " - $($e.Label)" }
if ($Group -eq 'telemetry') {
Write-Host ' - Telemetry services disabled (DiagTrack, dmwappushservice)'
Write-Host ' - Tracking scheduled tasks disabled'
}
Write-Host ''
Write-Host "$($script:Gray)All of this is reversible from menu option 8.$($script:NC)"
Write-Host ''
if (-not (Confirm-LxsAction -Question 'Apply?')) {
Write-LxsInfo 'Cancelled.'
return
}
if (-not (Assert-LxsCheckpoint)) { return }
Write-Host ''
foreach ($e in $entries) {
if (Set-LxsRegistryValue -Path $e.Path -Name $e.Name -Value $e.Value) {
Write-LxsOk $e.Label
}
}
if ($Group -ne 'telemetry') { return }
Write-Host ''
foreach ($svc in $LxsTrackingServices) {
try {
$s = Get-Service -Name $svc.Name -ErrorAction Stop
if ($s.Status -eq 'Running') { Stop-Service -Name $svc.Name -Force -ErrorAction SilentlyContinue }
Set-Service -Name $svc.Name -StartupType Disabled -ErrorAction Stop
Write-LxsOk "$($svc.Label) service disabled"
} catch {
Write-LxsWarn "$($svc.Label): $($_.Exception.Message)"
}
}
Write-Host ''
$disabled = 0
foreach ($taskPath in $LxsTrackingTasks) {
$leaf = Split-Path $taskPath -Leaf
$parent = (Split-Path $taskPath -Parent) + '\'
try {
Disable-ScheduledTask -TaskName $leaf -TaskPath $parent -ErrorAction Stop | Out-Null
$disabled++
} catch {
# Absent on this edition/build — nothing to disable.
}
}
Write-LxsOk "$disabled tracking scheduled task(s) disabled"
}
function Restore-LxsTweaks {
Clear-Host
Show-LxsBoxTop -Title 'REVERT PRIVACY TWEAKS'
Write-Host ''
Write-Host 'This restores every registry value, service and scheduled task that'
Write-Host 'this script changes, back to the Windows default.'
Write-Host ''
Write-Host "$($script:Gray)Apps you removed are NOT restored — reinstall them from the Store.$($script:NC)"
Write-Host ''
if (-not (Confirm-LxsAction -Question 'Revert the privacy tweaks?')) {
Write-LxsInfo 'Cancelled.'
return
}
Write-Host ''
foreach ($e in $LxsTweaks) {
if ($null -eq $e.Default) {
try {
if (Test-Path $e.Path) {
Remove-ItemProperty -Path $e.Path -Name $e.Name -ErrorAction Stop
}
Write-LxsOk "$($e.Label) — policy removed"
} catch {
# Already absent: that is the default state we wanted.
}
} else {
if (Set-LxsRegistryValue -Path $e.Path -Name $e.Name -Value $e.Default) {
Write-LxsOk "$($e.Label) — restored to default"
}
}
}
Write-Host ''
foreach ($svc in $LxsTrackingServices) {
try {
Set-Service -Name $svc.Name -StartupType $svc.Default -ErrorAction Stop
Write-LxsOk "$($svc.Label) service restored to $($svc.Default)"
} catch {
Write-LxsWarn "$($svc.Label): $($_.Exception.Message)"
}
}
Write-Host ''
$enabled = 0
foreach ($taskPath in $LxsTrackingTasks) {
$leaf = Split-Path $taskPath -Leaf
$parent = (Split-Path $taskPath -Parent) + '\'
try {
Enable-ScheduledTask -TaskName $leaf -TaskPath $parent -ErrorAction Stop | Out-Null
$enabled++
} catch {
# Not present on this build.
}
}
Write-LxsOk "$enabled scheduled task(s) re-enabled"
Write-Host ''
Write-LxsWarn 'Sign out and back in for the per-user settings to take effect.'
}
function Show-LxsDebloatStatus {
Clear-Host
Show-LxsBoxTop -Title 'CURRENT STATE'
Write-Host ''
$installed = @(Get-LxsInstalledBloat)
Write-Host " Known preinstalled apps still present : $($installed.Count) / $($LxsRemovableApps.Count)"
$applied = 0
foreach ($e in $LxsTweaks) {
try {
$current = (Get-ItemProperty -Path $e.Path -Name $e.Name -ErrorAction Stop).($e.Name)
if ($current -eq $e.Value) { $applied++ }
} catch {
# Value absent -> tweak not applied.
}
}
Write-Host " Privacy tweaks applied : $applied / $($LxsTweaks.Count)"
foreach ($svc in $LxsTrackingServices) {
try {
$s = Get-Service -Name $svc.Name -ErrorAction Stop
Write-Host " $($svc.Name.PadRight(38)): $($s.Status) / $($s.StartType)"
} catch {
Write-Host " $($svc.Name.PadRight(38)): not present"
}
}
Write-Host ''
}
function Show-LxsDebloatMenu {
while ($true) {
Clear-Host
Show-LxsBoxTop -Title 'DEBLOAT & PRIVACY' -Right 'ADMIN'
Write-Host ''
Show-LxsMenuItem '1' 'Show current state'
Show-LxsMenuItem '2' 'Remove preinstalled apps' 'you pick each one'
Show-LxsMenuItem '3' 'Disable telemetry' 'policy, services, tasks'
Show-LxsMenuItem '4' 'Disable Cortana & web search'
Show-LxsMenuItem '5' 'Disable ads & suggestions'
Show-LxsMenuItem '6' 'Disable Copilot & Recall'
Show-LxsMenuItem '7' 'Create a restore point' 'before doing anything'
Show-LxsMenuItem '8' 'Revert privacy tweaks' 'undo 3-6'
Show-LxsMenuItem '0' 'Back' '' -Exit
Write-Host ''
Show-LxsBoxBottom
Write-Host ''
$choice = Read-LxsChoice
Write-Host ''
switch ($choice) {
'1' { Show-LxsDebloatStatus; Read-LxsEnter }
'2' { Remove-LxsSelectedApps; Read-LxsEnter }
'3' { Invoke-LxsTweakGroup -Group 'telemetry' -Title 'DISABLE TELEMETRY'; Read-LxsEnter }
'4' { Invoke-LxsTweakGroup -Group 'cortana' -Title 'DISABLE CORTANA & WEB SEARCH'; Read-LxsEnter }
'5' { Invoke-LxsTweakGroup -Group 'ads' -Title 'DISABLE ADS & SUGGESTIONS'; Read-LxsEnter }
'6' { Invoke-LxsTweakGroup -Group 'ai' -Title 'DISABLE COPILOT & RECALL'; Read-LxsEnter }
'7' {
if (New-LxsRestorePoint -Description 'LXS manual checkpoint') { $script:LxsCheckpointDone = $true }
Read-LxsEnter
}
'8' { Restore-LxsTweaks; Read-LxsEnter }
'0' { return }
default { Write-LxsErr 'Invalid protocol. Select 0-8.'; Start-Sleep -Seconds 1 }
}
}
}
Show-LxsDebloatMenu
exit 75