powershell
<#
.SYNOPSIS
Disables Windows background services recommended by How-To Geek, with backup and restore.
.DESCRIPTION
Core set (always applied):
Windows Mobile Hotspot, Connected User Experiences and Telemetry,
Windows Error Reporting, SysMain, Delivery Optimization (peer sharing).
Optional sets are enabled with switches. Original startup types are saved to a
JSON backup before any change, and -Restore puts everything back.
.PARAMETER IncludeSearch
Also disable Windows Search (indexing). Start menu and Explorer search become slower.
.PARAMETER IncludeRemoteDesktop
Also disable the three Remote Desktop services. Nobody can RDP into this PC afterwards.
.PARAMETER IncludeBiometrics
Also disable Windows Biometric Service. Breaks Windows Hello face and fingerprint sign-in.
.PARAMETER IncludeExtras
Also disable Bluetooth, Telephony, Fax, Camera Frame Server and Xbox services.
.PARAMETER All
Apply every set above.
.PARAMETER Restore
Restore all startup types recorded in the backup file.
.PARAMETER BackupPath
Location of the JSON backup.
.EXAMPLE
.\Disable-BackgroundServices.ps1 -WhatIf
.EXAMPLE
.\Disable-BackgroundServices.ps1 -IncludeSearch -IncludeRemoteDesktop
.EXAMPLE
.\Disable-BackgroundServices.ps1 -Restore
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[switch]$IncludeSearch,
[switch]$IncludeRemoteDesktop,
[switch]$IncludeBiometrics,
[switch]$IncludeExtras,
[switch]$All,
[switch]$Restore,
[string]$BackupPath = "$env:ProgramData\ServiceTweaks\services-backup.json"
)
$ErrorActionPreference = 'Stop'
# ---- Elevation check ---------------------------------------------------------
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'Run this script from an elevated PowerShell session (Run as administrator).'
}
$svcRoot = 'HKLM:\SYSTEM\CurrentControlSet\Services'
$doKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization'
$doValue = 'DODownloadMode' # 0 = HTTP only, no peer to peer sharing
# ---- Service catalogue -------------------------------------------------------
$groups = [ordered]@{
Core = @(
@{ Name = 'icssvc'; Label = 'Windows Mobile Hotspot Service' }
@{ Name = 'DiagTrack'; Label = 'Connected User Experiences and Telemetry' }
@{ Name = 'WerSvc'; Label = 'Windows Error Reporting Service' }
@{ Name = 'SysMain'; Label = 'SysMain (Superfetch)' }
@{ Name = 'DoSvc'; Label = 'Delivery Optimization' }
)
Search = @(
@{ Name = 'WSearch'; Label = 'Windows Search' }
)
RemoteDesktop = @(
@{ Name = 'TermService'; Label = 'Remote Desktop Services' }
@{ Name = 'SessionEnv'; Label = 'Remote Desktop Configuration' }
@{ Name = 'UmRdpService'; Label = 'Remote Desktop Services UserMode Port Redirector' }
)
Biometrics = @(
@{ Name = 'WbioSrvc'; Label = 'Windows Biometric Service' }
)
Extras = @(
@{ Name = 'bthserv'; Label = 'Bluetooth Support Service' }
@{ Name = 'TapiSrv'; Label = 'Telephony' }
@{ Name = 'Fax'; Label = 'Fax' }
@{ Name = 'FrameServer'; Label = 'Windows Camera Frame Server' }
@{ Name = 'XblAuthManager'; Label = 'Xbox Live Auth Manager' }
@{ Name = 'XblGameSave'; Label = 'Xbox Live Game Save' }
@{ Name = 'XboxNetApiSvc'; Label = 'Xbox Live Networking Service' }
@{ Name = 'XboxGipSvc'; Label = 'Xbox Accessory Management Service' }
)
}
# ---- Helpers -----------------------------------------------------------------
function Get-StartConfig([string]$Name) {
$key = Join-Path $svcRoot $Name
if (-not (Test-Path $key)) { return $null }
$p = Get-ItemProperty -Path $key
[pscustomobject]@{
Start = $p.Start
DelayedAutostart = $p.DelayedAutostart
}
}
function Format-StartType($Start, $Delayed) {
switch ($Start) {
0 { 'Boot' }
1 { 'System' }
2 { if ($Delayed -eq 1) { 'Automatic (Delayed)' } else { 'Automatic' } }
3 { 'Manual' }
4 { 'Disabled' }
default { 'Unknown' }
}
}
function Set-StartType([string]$Name, [int]$Start, $Delayed) {
# Try the Service Control Manager first, fall back to the registry
# (some services, such as DoSvc, refuse SCM changes on recent builds).
$mode = switch ($Start) {
2 { if ($Delayed -eq 1) { 'delayed-auto' } else { 'auto' } }
3 { 'demand' }
4 { 'disabled' }
}
$out = & sc.exe config $Name start= $mode 2>&1
if ($LASTEXITCODE -eq 0) { return 'SCM' }
$key = Join-Path $svcRoot $Name
Set-ItemProperty -Path $key -Name Start -Value $Start -Type DWord
if ($null -ne $Delayed) {
Set-ItemProperty -Path $key -Name DelayedAutostart -Value ([int]$Delayed) -Type DWord
}
return 'Registry'
}
function Read-Backup {
$data = @{}
if (Test-Path $BackupPath) {
(Get-Content $BackupPath -Raw | ConvertFrom-Json).PSObject.Properties |
ForEach-Object { $data[$_.Name] = $_.Value }
}
return $data
}
function Save-Backup($Data) {
$dir = Split-Path $BackupPath -Parent
if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
$Data | ConvertTo-Json -Depth 4 | Set-Content -Path $BackupPath -Encoding UTF8
}
# ---- Restore mode ------------------------------------------------------------
if ($Restore) {
if (-not (Test-Path $BackupPath)) { throw "No backup found at $BackupPath" }
$backup = Read-Backup
$results = foreach ($name in $backup.Keys) {
$b = $backup[$name]
if ($name -eq '__DOPolicy') {
if ($PSCmdlet.ShouldProcess('Delivery Optimization policy', 'Restore')) {
if ($b.Existed) {
Set-ItemProperty -Path $doKey -Name $doValue -Value ([int]$b.Value) -Type DWord
} elseif (Test-Path $doKey) {
Remove-ItemProperty -Path $doKey -Name $doValue -ErrorAction SilentlyContinue
}
}
[pscustomobject]@{ Service = 'DO policy'; Restored = 'Policy reverted'; Result = 'OK' }
continue
}
$target = Format-StartType $b.Start $b.DelayedAutostart
if (-not $PSCmdlet.ShouldProcess($name, "Restore startup type to $target")) { continue }
try {
$via = Set-StartType $name ([int]$b.Start) $b.DelayedAutostart
if ($b.Start -eq 2) { Start-Service -Name $name -ErrorAction SilentlyContinue }
[pscustomobject]@{ Service = $name; Restored = $target; Result = "OK ($via)" }
} catch {
[pscustomobject]@{ Service = $name; Restored = $target; Result = "FAILED: $($_.Exception.Message)" }
}
}
$results | Format-Table -AutoSize
if (-not $WhatIfPreference) {
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
Rename-Item -Path $BackupPath -NewName "services-backup.restored-$stamp.json"
Write-Host 'Restore complete. Reboot recommended.' -ForegroundColor Green
}
return
}
# ---- Build selection ---------------------------------------------------------
$selected = [System.Collections.Generic.List[hashtable]]::new()
$groups.Core | ForEach-Object { $selected.Add($_) }
if ($All -or $IncludeSearch) { $groups.Search | ForEach-Object { $selected.Add($_) } }
if ($All -or $IncludeRemoteDesktop) { $groups.RemoteDesktop | ForEach-Object { $selected.Add($_) } }
if ($All -or $IncludeBiometrics) { $groups.Biometrics | ForEach-Object { $selected.Add($_) } }
if ($All -or $IncludeExtras) { $groups.Extras | ForEach-Object { $selected.Add($_) } }
# ---- Disable -----------------------------------------------------------------
$backup = Read-Backup # merge, so a second run never overwrites the original values
$results = foreach ($svc in $selected) {
$name = $svc.Name
$cfg = Get-StartConfig $name
if ($null -eq $cfg) {
[pscustomobject]@{ Service = $name; Description = $svc.Label; Before = '-'; Result = 'Not present' }
continue
}
$before = Format-StartType $cfg.Start $cfg.DelayedAutostart
if ($cfg.Start -eq 4) {
[pscustomobject]@{ Service = $name; Description = $svc.Label; Before = $before; Result = 'Already disabled' }
continue
}
if (-not $PSCmdlet.ShouldProcess("$($svc.Label) ($name)", 'Stop and disable')) { continue }
if (-not $backup.ContainsKey($name)) { $backup[$name] = $cfg }
try {
Stop-Service -Name $name -Force -ErrorAction SilentlyContinue
$via = Set-StartType $name 4 $null
[pscustomobject]@{ Service = $name; Description = $svc.Label; Before = $before; Result = "Disabled ($via)" }
} catch {
[pscustomobject]@{ Service = $name; Description = $svc.Label; Before = $before; Result = "FAILED: $($_.Exception.Message)" }
}
}
# Delivery Optimization: also set policy to HTTP only, so peer sharing stays off
# even if Windows re-enables the service during a feature update.
if ($PSCmdlet.ShouldProcess('Delivery Optimization policy', "Set $doValue = 0 (no peer sharing)")) {
if (-not $backup.ContainsKey('__DOPolicy')) {
$existing = $null
if (Test-Path $doKey) { $existing = (Get-ItemProperty -Path $doKey -ErrorAction SilentlyContinue).$doValue }
$backup['__DOPolicy'] = @{ Existed = ($null -ne $existing); Value = $existing }
}
if (-not (Test-Path $doKey)) { New-Item -Path $doKey -Force | Out-Null }
Set-ItemProperty -Path $doKey -Name $doValue -Value 0 -Type DWord
}
if (-not $WhatIfPreference) { Save-Backup $backup }
$results | Format-Table -AutoSize
if (-not $WhatIfPreference) {
Write-Host "Backup saved to $BackupPath" -ForegroundColor Cyan
Write-Host 'Done. Reboot recommended. Undo with: .\Disable-BackgroundServices.ps1 -Restore' -ForegroundColor Green
}
0 comments