Created
April 8, 2026 19:27
-
-
Save ph20/c835d191b1c20944f1d11b6891850473 to your computer and use it in GitHub Desktop.
Minimizes Windows telemetry on Windows 10 Enterprise LTSC / LTSC N.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -Version 5.1 | |
| #requires -RunAsAdministrator | |
| <# | |
| .SYNOPSIS | |
| Minimizes Windows telemetry on Windows 10 Enterprise LTSC / LTSC N. | |
| .DESCRIPTION | |
| Applies supported policy-based settings first, disables common telemetry-related | |
| scheduled tasks, and optionally disables telemetry services in aggressive mode. | |
| Safe defaults: | |
| - Uses policy keys where possible | |
| - Disables common CEIP / telemetry tasks | |
| - Leaves DiagTrack and dmwappushservice unchanged unless explicitly requested | |
| Aggressive mode: | |
| - Disables DiagTrack | |
| - Disables dmwappushservice | |
| .NOTES | |
| Run this script from 64-bit Windows PowerShell as Administrator. | |
| A reboot is recommended after the script finishes. | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [switch]$Aggressive, | |
| [switch]$DisableDiagTrackService, | |
| [switch]$DisableDmwAppPushService, | |
| [string]$LogPath = "$env:ProgramData\Disable-WindowsTelemetry-LTSC.log" | |
| ) | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) { | |
| throw 'Run this script in 64-bit Windows PowerShell.' | |
| } | |
| $DisableDiagTrackService = $DisableDiagTrackService -or $Aggressive | |
| $DisableDmwAppPushService = $DisableDmwAppPushService -or $Aggressive | |
| $script:LogPath = [System.IO.Path]::GetFullPath($LogPath) | |
| $logDirectory = Split-Path -Path $script:LogPath -Parent | |
| if ($logDirectory -and -not (Test-Path -LiteralPath $logDirectory)) { | |
| New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null | |
| } | |
| function Write-Log { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Message, | |
| [ValidateSet('INFO', 'WARN', 'ERROR')] | |
| [string]$Level = 'INFO' | |
| ) | |
| $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' | |
| $line = '[{0}] [{1}] {2}' -f $timestamp, $Level, $Message | |
| Write-Host $line | |
| Add-Content -LiteralPath $script:LogPath -Value $line -Encoding UTF8 | |
| } | |
| function Ensure-RegistryKey { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Path | |
| ) | |
| if (-not (Test-Path -LiteralPath $Path)) { | |
| New-Item -Path $Path -Force | Out-Null | |
| } | |
| } | |
| function Set-DwordValue { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Path, | |
| [Parameter(Mandatory = $true)] | |
| [string]$Name, | |
| [Parameter(Mandatory = $true)] | |
| [int]$Value | |
| ) | |
| Ensure-RegistryKey -Path $Path | |
| $currentValue = $null | |
| try { | |
| $currentValue = (Get-ItemProperty -LiteralPath $Path -Name $Name -ErrorAction Stop).$Name | |
| } catch { | |
| $currentValue = $null | |
| } | |
| if ($currentValue -ne $Value) { | |
| New-ItemProperty -LiteralPath $Path -Name $Name -PropertyType DWord -Value $Value -Force | Out-Null | |
| Write-Log "Set $Path\$Name = $Value" | |
| } else { | |
| Write-Log "Already set: $Path\$Name = $Value" | |
| } | |
| } | |
| function Get-DwordValue { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Path, | |
| [Parameter(Mandatory = $true)] | |
| [string]$Name | |
| ) | |
| try { | |
| return (Get-ItemProperty -LiteralPath $Path -Name $Name -ErrorAction Stop).$Name | |
| } catch { | |
| return $null | |
| } | |
| } | |
| function Get-ServiceStartMode { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Name | |
| ) | |
| try { | |
| return (Get-CimInstance -ClassName Win32_Service -Filter "Name='$Name'" -ErrorAction Stop).StartMode | |
| } catch { | |
| return $null | |
| } | |
| } | |
| function Disable-ServiceSafe { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$Name | |
| ) | |
| try { | |
| $service = Get-Service -Name $Name -ErrorAction Stop | |
| } catch { | |
| Write-Log "Service not found: $Name" | |
| return | |
| } | |
| try { | |
| if ($service.Status -ne 'Stopped') { | |
| Stop-Service -Name $Name -Force -ErrorAction Stop | |
| Write-Log "Stopped service: $Name" | |
| } | |
| } catch { | |
| Write-Log ("Could not stop service {0}: {1}" -f $Name, $_.Exception.Message) 'WARN' | |
| } | |
| try { | |
| Set-Service -Name $Name -StartupType Disabled -ErrorAction Stop | |
| Write-Log "Disabled service: $Name" | |
| } catch { | |
| try { | |
| & sc.exe config $Name start= disabled | Out-Null | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Log "Disabled service with sc.exe: $Name" | |
| } else { | |
| Write-Log "sc.exe could not disable service $Name" 'WARN' | |
| } | |
| } catch { | |
| Write-Log ("Could not disable service {0}: {1}" -f $Name, $_.Exception.Message) 'WARN' | |
| } | |
| } | |
| } | |
| function Disable-TaskSafe { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$TaskName | |
| ) | |
| try { | |
| & schtasks.exe /Query /TN $TaskName | Out-Null | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Log "Task not found: $TaskName" | |
| return | |
| } | |
| } catch { | |
| Write-Log "Task not found: $TaskName" | |
| return | |
| } | |
| try { | |
| & schtasks.exe /Change /TN $TaskName /Disable | Out-Null | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Log "Disabled task: $TaskName" | |
| } else { | |
| Write-Log "Could not disable task: $TaskName" 'WARN' | |
| } | |
| } catch { | |
| Write-Log ("Could not disable task {0}: {1}" -f $TaskName, $_.Exception.Message) 'WARN' | |
| } | |
| } | |
| function Set-DwordValueForLoadedUsersAndDefaultProfile { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string]$RelativePath, | |
| [Parameter(Mandatory = $true)] | |
| [string]$Name, | |
| [Parameter(Mandatory = $true)] | |
| [int]$Value | |
| ) | |
| $userSidPattern = '^S-1-5-21-\d+-\d+-\d+-\d+$' | |
| $loadedUserSids = Get-ChildItem -Path Registry::HKEY_USERS -ErrorAction SilentlyContinue | | |
| Where-Object { $_.PSChildName -match $userSidPattern } | | |
| Select-Object -ExpandProperty PSChildName | |
| foreach ($sid in $loadedUserSids) { | |
| $path = "Registry::HKEY_USERS\$sid\$RelativePath" | |
| Set-DwordValue -Path $path -Name $Name -Value $Value | |
| } | |
| $defaultHiveFile = Join-Path $env:SystemDrive 'Users\Default\NTUSER.DAT' | |
| $defaultMountName = 'HKU\DefaultTelemetryProfile' | |
| $defaultMountPath = 'Registry::HKEY_USERS\DefaultTelemetryProfile' | |
| if (Test-Path -LiteralPath $defaultHiveFile) { | |
| $mountedHere = $false | |
| try { | |
| if (-not (Test-Path -LiteralPath $defaultMountPath)) { | |
| & reg.exe load $defaultMountName $defaultHiveFile | Out-Null | |
| if ($LASTEXITCODE -eq 0) { | |
| $mountedHere = $true | |
| Write-Log 'Loaded default user hive.' | |
| } else { | |
| Write-Log 'Could not load default user hive.' 'WARN' | |
| } | |
| } | |
| if (Test-Path -LiteralPath $defaultMountPath) { | |
| $defaultPath = "$defaultMountPath\$RelativePath" | |
| Set-DwordValue -Path $defaultPath -Name $Name -Value $Value | |
| } | |
| } finally { | |
| if ($mountedHere) { | |
| & reg.exe unload $defaultMountName | Out-Null | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Log 'Unloaded default user hive.' | |
| } else { | |
| Write-Log 'Could not unload default user hive cleanly.' 'WARN' | |
| } | |
| } | |
| } | |
| } else { | |
| Write-Log 'Default user hive not found.' 'WARN' | |
| } | |
| } | |
| try { | |
| Write-Log 'Starting telemetry hardening script.' | |
| $currentVersion = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | |
| $productName = $currentVersion.ProductName | |
| $editionId = $currentVersion.EditionID | |
| $releaseId = $null | |
| if ($currentVersion.PSObject.Properties.Name -contains 'DisplayVersion') { | |
| $releaseId = $currentVersion.DisplayVersion | |
| } elseif ($currentVersion.PSObject.Properties.Name -contains 'ReleaseId') { | |
| $releaseId = $currentVersion.ReleaseId | |
| } | |
| Write-Log "Detected OS: $productName | EditionID: $editionId | Release: $releaseId" | |
| if ($editionId -notmatch 'Enterprise|Education|Server') { | |
| Write-Log 'AllowTelemetry=0 is not fully supported on this edition. Enterprise, Education, or Server is recommended.' 'WARN' | |
| } | |
| Write-Log 'Applying machine-wide policy settings.' | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name 'AllowTelemetry' -Value 0 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name 'DoNotShowFeedbackNotifications' -Value 1 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableWindowsConsumerFeatures' -Value 1 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\SQMClient\Windows' -Name 'CEIPEnable' -Value 0 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo' -Name 'DisabledByGroupPolicy' -Value 1 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\InputPersonalization' -Name 'AllowInputPersonalization' -Value 0 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE' -Name 'DisablePrivacyExperience' -Value 1 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' -Name 'EnableActivityFeed' -Value 0 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' -Name 'PublishUserActivities' -Value 0 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' -Name 'UploadUserActivities' -Value 0 | |
| Set-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy' -Name 'LetAppsRunInBackground' -Value 2 | |
| Write-Log 'Applying user-scoped settings to loaded profiles and the default profile.' | |
| Set-DwordValueForLoadedUsersAndDefaultProfile -RelativePath 'SOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableTailoredExperiencesWithDiagnosticData' -Value 1 | |
| Set-DwordValueForLoadedUsersAndDefaultProfile -RelativePath 'SOFTWARE\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' -Name 'HasAccepted' -Value 0 | |
| Write-Log 'Disabling scheduled tasks related to telemetry and CEIP where present.' | |
| $tasks = @( | |
| '\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser', | |
| '\Microsoft\Windows\Application Experience\ProgramDataUpdater', | |
| '\Microsoft\Windows\Application Experience\StartupAppTask', | |
| '\Microsoft\Windows\Autochk\Proxy', | |
| '\Microsoft\Windows\Customer Experience Improvement Program\Consolidator', | |
| '\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask', | |
| '\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip', | |
| '\Microsoft\Windows\DiskDiagnostic\Microsoft-Windows-DiskDiagnosticDataCollector', | |
| '\Microsoft\Windows\Feedback\Siuf\DmClient', | |
| '\Microsoft\Windows\Feedback\Siuf\DmClientOnScenarioDownload' | |
| ) | |
| foreach ($task in $tasks) { | |
| Disable-TaskSafe -TaskName $task | |
| } | |
| if ($DisableDiagTrackService) { | |
| Write-Log 'Disabling DiagTrack service.' | |
| Disable-ServiceSafe -Name 'DiagTrack' | |
| } else { | |
| Write-Log 'DiagTrack was left unchanged. Use -DisableDiagTrackService or -Aggressive to disable it.' | |
| } | |
| if ($DisableDmwAppPushService) { | |
| Write-Log 'Disabling dmwappushservice.' | |
| Disable-ServiceSafe -Name 'dmwappushservice' | |
| } else { | |
| Write-Log 'dmwappushservice was left unchanged. Use -DisableDmwAppPushService or -Aggressive to disable it.' | |
| } | |
| Write-Log 'Verification summary:' | |
| $summary = [ordered]@{ | |
| ProductName = $productName | |
| EditionID = $editionId | |
| Release = $releaseId | |
| AllowTelemetry = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name 'AllowTelemetry' | |
| DoNotShowFeedbackNotifications = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection' -Name 'DoNotShowFeedbackNotifications' | |
| DisableWindowsConsumerFeatures = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableWindowsConsumerFeatures' | |
| CEIPEnable = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\SQMClient\Windows' -Name 'CEIPEnable' | |
| DisabledByGroupPolicy_AdvertisingId = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\AdvertisingInfo' -Name 'DisabledByGroupPolicy' | |
| AllowInputPersonalization = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\InputPersonalization' -Name 'AllowInputPersonalization' | |
| DisablePrivacyExperience = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE' -Name 'DisablePrivacyExperience' | |
| EnableActivityFeed = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' -Name 'EnableActivityFeed' | |
| PublishUserActivities = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' -Name 'PublishUserActivities' | |
| UploadUserActivities = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\System' -Name 'UploadUserActivities' | |
| LetAppsRunInBackground = Get-DwordValue -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy' -Name 'LetAppsRunInBackground' | |
| DisableTailoredExperiences_CurrentUser = Get-DwordValue -Path 'HKCU:\SOFTWARE\Policies\Microsoft\Windows\CloudContent' -Name 'DisableTailoredExperiencesWithDiagnosticData' | |
| OnlineSpeechPrivacyHasAccepted_CurrentUser = Get-DwordValue -Path 'HKCU:\SOFTWARE\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' -Name 'HasAccepted' | |
| DiagTrack_StartMode = Get-ServiceStartMode -Name 'DiagTrack' | |
| DmwAppPushService_StartMode = Get-ServiceStartMode -Name 'dmwappushservice' | |
| } | |
| foreach ($entry in $summary.GetEnumerator()) { | |
| Write-Log (' {0} = {1}' -f $entry.Key, $entry.Value) | |
| } | |
| Write-Log 'Done. A reboot is recommended.' | |
| } catch { | |
| Write-Log "Fatal error: $($_.Exception.Message)" 'ERROR' | |
| throw | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment