diff --git a/Manage-BrowserPrivacy.ps1 b/Manage-BrowserPrivacy.ps1 new file mode 100644 index 0000000..ee97bca --- /dev/null +++ b/Manage-BrowserPrivacy.ps1 @@ -0,0 +1,531 @@ +#Requires -Version 5.1 +<# + Manage-BrowserPrivacy.ps1 + + Purpose : View and control privacy / telemetry-related policy settings for + browsers installed on this Windows 11 machine: + Microsoft Edge, Google Chrome, Mozilla Firefox, Brave. + + How : Uses each browser's supported policy registry keys under + HKLM\SOFTWARE\Policies\... . Browsers apply these on next start. + Only detected (installed) browsers are shown, unless -IncludeAll. + + Notes : - Windows PowerShell 5.1 compatible. ASCII-only source. + - HKLM policy changes require Administrator. + - "Enabled" = the data-collection / suggestion feature is ON. + "Disabled" = hardened / OFF. + - "Enable" REMOVES the policy value (true browser default = + value absent), so browsers are not left permanently showing + "Managed by your organization" after a round trip. + - While policies are applied (hardened), browsers WILL show a + "managed" notice on their settings pages. That is how + Chromium/Firefox indicate active policies and is expected. + + Usage : + .\Manage-BrowserPrivacy.ps1 # interactive menu + .\Manage-BrowserPrivacy.ps1 -Report # print status and exit + .\Manage-BrowserPrivacy.ps1 -DisableAll # harden all browsers + .\Manage-BrowserPrivacy.ps1 -EnableAll # restore browser defaults + .\Manage-BrowserPrivacy.ps1 -Csv .\out.csv # export status and exit + .\Manage-BrowserPrivacy.ps1 -Report -IncludeAll # show non-installed too + + DISCLAIMER: Review before use. Test on a non-production machine first. + No warranty. +#> + +[CmdletBinding()] +param( + [switch]$Report, + [switch]$DisableAll, + [switch]$EnableAll, + [switch]$IncludeAll, + [string]$Csv +) + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = 'Stop' + +# --------------------------------------------------------------------------- +# Elevation helpers +# --------------------------------------------------------------------------- +function Test-IsAdmin { + $id = [Security.Principal.WindowsIdentity]::GetCurrent() + $pr = New-Object Security.Principal.WindowsPrincipal($id) + return $pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +$Script:IsAdmin = Test-IsAdmin + +# --------------------------------------------------------------------------- +# Browser detection +# --------------------------------------------------------------------------- +function Get-BrowserInfo { + param([string]$Exe) + foreach ($hive in 'HKLM', 'HKCU') { + $key = ('{0}:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{1}' -f $hive, $Exe) + if (Test-Path -LiteralPath $key) { + $ver = '' + try { + $path = (Get-ItemProperty -LiteralPath $key -ErrorAction Stop).'(default)' + if ($path -and (Test-Path -LiteralPath $path)) { + $ver = (Get-Item -LiteralPath $path).VersionInfo.ProductVersion + } + } catch { $ver = '' } + return [pscustomobject]@{ Installed = $true; Version = $ver } + } + } + return [pscustomobject]@{ Installed = $false; Version = '' } +} + +$Script:Browsers = @{ + 'Edge' = Get-BrowserInfo -Exe 'msedge.exe' + 'Chrome' = Get-BrowserInfo -Exe 'chrome.exe' + 'Firefox' = Get-BrowserInfo -Exe 'firefox.exe' + 'Brave' = Get-BrowserInfo -Exe 'brave.exe' +} + +# --------------------------------------------------------------------------- +# Registry helpers +# --------------------------------------------------------------------------- +function Get-RegValue { + param([string]$FullPath, [string]$Name) + try { + if (-not (Test-Path -LiteralPath $FullPath)) { return $null } + $item = Get-ItemProperty -LiteralPath $FullPath -Name $Name -ErrorAction Stop + return $item.$Name + } catch { + return $null + } +} + +function Set-RegValue { + param([string]$FullPath, [string]$Name, $Value, [string]$Type = 'DWord') + if (-not (Test-Path -LiteralPath $FullPath)) { + New-Item -Path $FullPath -Force | Out-Null + } + New-ItemProperty -LiteralPath $FullPath -Name $Name -Value $Value -PropertyType $Type -Force | Out-Null +} + +function Remove-RegValue { + param([string]$FullPath, [string]$Name) + if (Test-Path -LiteralPath $FullPath) { + Remove-ItemProperty -LiteralPath $FullPath -Name $Name -ErrorAction SilentlyContinue + } +} + +# --------------------------------------------------------------------------- +# Control model (policy DWORD values under HKLM\SOFTWARE\Policies\...) +# All browser policies default to ABSENT, so every control is +# remove-on-enable: Enable deletes the value, Disable writes OffValue. +# --------------------------------------------------------------------------- +function New-PolicyControl { + param( + [string]$Name, [string]$Browser, [string]$PolicyPath, + [string]$ValueName, [int]$OnValue, [int]$OffValue, + [string]$Note = '' + ) + [pscustomobject]@{ + Type = 'Reg' + Name = $Name + Category = $Browser + Note = $Note + FullPath = ('HKLM:\{0}' -f $PolicyPath) + ValueName = $ValueName + OnValue = $OnValue + OffValue = $OffValue + AdminReq = $true + } +} + +function Get-ControlState { + param($Ctrl) + $cur = Get-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName + if ($null -eq $cur) { return 'Enabled (default)' } + $curInt = $null + try { $curInt = [int]$cur } catch { + return ('Enabled (value={0})' -f $cur) + } + if ($curInt -eq $Ctrl.OffValue) { return 'Disabled' } + if ($curInt -eq $Ctrl.OnValue) { return 'Enabled' } + return ('Enabled (value={0})' -f $curInt) +} + +function Set-ControlEnabled { + param($Ctrl) # restore browser default: remove the policy value + Remove-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName +} + +function Set-ControlDisabled { + param($Ctrl) # harden: write the policy value + Set-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName -Value $Ctrl.OffValue -Type 'DWord' +} + +# --------------------------------------------------------------------------- +# Control catalog +# --------------------------------------------------------------------------- +function Get-Controls { + $c = New-Object System.Collections.ArrayList + $edge = 'SOFTWARE\Policies\Microsoft\Edge' + $chr = 'SOFTWARE\Policies\Google\Chrome' + $ffx = 'SOFTWARE\Policies\Mozilla\Firefox' + $brv = 'SOFTWARE\Policies\BraveSoftware\Brave' + + # =========================== Microsoft Edge =========================== + [void]$c.Add( (New-PolicyControl -Name 'Diagnostic Data' -Browser Edge -PolicyPath $edge ` + -ValueName 'DiagnosticData' -OnValue 2 -OffValue 0 ` + -Note '0=off 1=required 2=optional') ) + + [void]$c.Add( (New-PolicyControl -Name 'Personalization Reporting' -Browser Edge -PolicyPath $edge ` + -ValueName 'PersonalizationReportingEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Browsing history used for ads/news personalization') ) + + [void]$c.Add( (New-PolicyControl -Name 'User Feedback' -Browser Edge -PolicyPath $edge ` + -ValueName 'UserFeedbackAllowed' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Search Suggestions' -Browser Edge -PolicyPath $edge ` + -ValueName 'SearchSuggestEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Sends keystrokes to search provider') ) + + [void]$c.Add( (New-PolicyControl -Name 'Bing Provider in Address Bar' -Browser Edge -PolicyPath $edge ` + -ValueName 'AddressBarMicrosoftSearchInBingProviderEnabled' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Shopping Assistant' -Browser Edge -PolicyPath $edge ` + -ValueName 'EdgeShoppingAssistantEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Coupons/price comparison; shares browsing data') ) + + [void]$c.Add( (New-PolicyControl -Name 'Microsoft Rewards' -Browser Edge -PolicyPath $edge ` + -ValueName 'ShowMicrosoftRewards' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Web Widget (search bar)' -Browser Edge -PolicyPath $edge ` + -ValueName 'WebWidgetAllowed' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Spotlight Recommendations' -Browser Edge -PolicyPath $edge ` + -ValueName 'SpotlightExperiencesAndRecommendationsEnabled' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Do Not Track OFF' -Browser Edge -PolicyPath $edge ` + -ValueName 'ConfigureDoNotTrack' -OnValue 0 -OffValue 1 ` + -Note 'Disabled = DNT header IS sent (hardened)') ) + + [void]$c.Add( (New-PolicyControl -Name 'Nav Error Web Service' -Browser Edge -PolicyPath $edge ` + -ValueName 'ResolveNavigationErrorsUseWebService' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Alternate Error Pages' -Browser Edge -PolicyPath $edge ` + -ValueName 'AlternateErrorPagesEnabled' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Network Prediction (prefetch)' -Browser Edge -PolicyPath $edge ` + -ValueName 'NetworkPredictionOptions' -OnValue 0 -OffValue 2 ` + -Note '0=predict always 2=never') ) + + # =========================== Google Chrome ============================ + [void]$c.Add( (New-PolicyControl -Name 'Metrics Reporting (UMA)' -Browser Chrome -PolicyPath $chr ` + -ValueName 'MetricsReportingEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Usage statistics and crash reports to Google') ) + + [void]$c.Add( (New-PolicyControl -Name 'Search Suggestions' -Browser Chrome -PolicyPath $chr ` + -ValueName 'SearchSuggestEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Sends keystrokes to search provider') ) + + [void]$c.Add( (New-PolicyControl -Name 'Safe Browsing Ext. Reporting' -Browser Chrome -PolicyPath $chr ` + -ValueName 'SafeBrowsingExtendedReportingEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Extra page/system data to Google; SB itself stays on') ) + + [void]$c.Add( (New-PolicyControl -Name 'URL-keyed Data Collection' -Browser Chrome -PolicyPath $chr ` + -ValueName 'UrlKeyedAnonymizedDataCollectionEnabled' -OnValue 1 -OffValue 0 ` + -Note 'URLs of visited pages sent to Google') ) + + [void]$c.Add( (New-PolicyControl -Name 'Cloud Spell Check' -Browser Chrome -PolicyPath $chr ` + -ValueName 'SpellCheckServiceEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Typed text sent to Google web service') ) + + [void]$c.Add( (New-PolicyControl -Name 'Alternate Error Pages' -Browser Chrome -PolicyPath $chr ` + -ValueName 'AlternateErrorPagesEnabled' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Network Prediction (prefetch)' -Browser Chrome -PolicyPath $chr ` + -ValueName 'NetworkPredictionOptions' -OnValue 0 -OffValue 2 ` + -Note '0=predict always 2=never') ) + + [void]$c.Add( (New-PolicyControl -Name 'Feedback Surveys' -Browser Chrome -PolicyPath $chr ` + -ValueName 'FeedbackSurveysEnabled' -OnValue 1 -OffValue 0) ) + + [void]$c.Add( (New-PolicyControl -Name 'Privacy Sandbox Prompt' -Browser Chrome -PolicyPath $chr ` + -ValueName 'PrivacySandboxPromptEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Must be Disabled for the three policies below') ) + + [void]$c.Add( (New-PolicyControl -Name 'Privacy Sandbox: Ad Topics' -Browser Chrome -PolicyPath $chr ` + -ValueName 'PrivacySandboxAdTopicsEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Interest-based advertising (Topics API)') ) + + [void]$c.Add( (New-PolicyControl -Name 'Privacy Sandbox: Site Ads' -Browser Chrome -PolicyPath $chr ` + -ValueName 'PrivacySandboxSiteEnabledAdsEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Site-suggested ads (Protected Audience)') ) + + [void]$c.Add( (New-PolicyControl -Name 'Privacy Sandbox: Ad Measure' -Browser Chrome -PolicyPath $chr ` + -ValueName 'PrivacySandboxAdMeasurementEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Attribution / ad measurement API') ) + + # =========================== Mozilla Firefox ========================== + [void]$c.Add( (New-PolicyControl -Name 'Telemetry' -Browser Firefox -PolicyPath $ffx ` + -ValueName 'DisableTelemetry' -OnValue 0 -OffValue 1 ` + -Note 'Usage and technical data to Mozilla') ) + + [void]$c.Add( (New-PolicyControl -Name 'Firefox Studies (Shield)' -Browser Firefox -PolicyPath $ffx ` + -ValueName 'DisableFirefoxStudies' -OnValue 0 -OffValue 1 ` + -Note 'Remote experiments / preference rollouts') ) + + [void]$c.Add( (New-PolicyControl -Name 'Default Browser Agent' -Browser Firefox -PolicyPath $ffx ` + -ValueName 'DisableDefaultBrowserAgent' -OnValue 0 -OffValue 1 ` + -Note 'Scheduled task that pings Mozilla daily') ) + + [void]$c.Add( (New-PolicyControl -Name 'Pocket Integration' -Browser Firefox -PolicyPath $ffx ` + -ValueName 'DisablePocket' -OnValue 0 -OffValue 1 ` + -Note 'Sponsored stories / recommendations') ) + + # =============================== Brave ================================ + # Brave sends little telemetry by default; these harden its bundled + # feature surface area (each phones home to Brave services). + [void]$c.Add( (New-PolicyControl -Name 'Brave Rewards' -Browser Brave -PolicyPath $brv ` + -ValueName 'BraveRewardsDisabled' -OnValue 0 -OffValue 1) ) + + [void]$c.Add( (New-PolicyControl -Name 'Brave Wallet' -Browser Brave -PolicyPath $brv ` + -ValueName 'BraveWalletDisabled' -OnValue 0 -OffValue 1) ) + + [void]$c.Add( (New-PolicyControl -Name 'Brave VPN' -Browser Brave -PolicyPath $brv ` + -ValueName 'BraveVPNDisabled' -OnValue 0 -OffValue 1) ) + + [void]$c.Add( (New-PolicyControl -Name 'Tor Windows' -Browser Brave -PolicyPath $brv ` + -ValueName 'TorDisabled' -OnValue 0 -OffValue 1 ` + -Note 'Private windows with Tor') ) + + [void]$c.Add( (New-PolicyControl -Name 'Search Suggestions' -Browser Brave -PolicyPath $brv ` + -ValueName 'SearchSuggestEnabled' -OnValue 1 -OffValue 0 ` + -Note 'Chromium policy honoured by Brave') ) + + # Filter to installed browsers unless -IncludeAll + if ($IncludeAll) { return $c } + $filtered = New-Object System.Collections.ArrayList + foreach ($ctrl in $c) { + if ($Script:Browsers[$ctrl.Category].Installed) { [void]$filtered.Add($ctrl) } + } + return $filtered +} + +# --------------------------------------------------------------------------- +# Display +# --------------------------------------------------------------------------- +function Get-StateColor { + param([string]$State) + if ($State -like 'Disabled*') { return 'Green' } + if ($State -like 'Enabled*') { return 'Yellow' } + return 'Gray' +} + +function Show-Status { + param($Controls) + + Write-Host '' + Write-Host '==================================================================' -ForegroundColor Cyan + Write-Host ' Browser Privacy / Telemetry Status' -ForegroundColor Cyan + Write-Host (' Host: {0} Admin: {1} {2}' -f $env:COMPUTERNAME, $Script:IsAdmin, (Get-Date)) -ForegroundColor DarkCyan + $det = @() + foreach ($b in @('Edge','Chrome','Firefox','Brave')) { + $info = $Script:Browsers[$b] + if ($info.Installed) { + if ($info.Version) { $det += ('{0} {1}' -f $b, $info.Version) } + else { $det += $b } + } + } + if ($det.Count -eq 0) { $det = @('none detected') } + Write-Host (' Browsers: {0}' -f ($det -join ', ')) -ForegroundColor DarkCyan + Write-Host ' Enabled = collecting/on Disabled = hardened/off' -ForegroundColor DarkCyan + Write-Host '==================================================================' -ForegroundColor Cyan + Write-Host ('{0,-4}{1,-32}{2,-10}{3}' -f '#', 'Setting', 'Browser', 'State') -ForegroundColor White + Write-Host ('{0,-4}{1,-32}{2,-10}{3}' -f '---', '-------', '-------', '-----') -ForegroundColor DarkGray + + $i = 0 + $nEnabled = 0; $nDisabled = 0 + foreach ($ctrl in $Controls) { + $i++ + $state = Get-ControlState -Ctrl $ctrl + if ($state -like 'Enabled*') { $nEnabled++ } + elseif ($state -like 'Disabled*') { $nDisabled++ } + $lock = '' + if ($ctrl.AdminReq -and -not $Script:IsAdmin) { $lock = ' *' } + $flag = '' + if (-not $Script:Browsers[$ctrl.Category].Installed) { $flag = ' (not installed)' } + $line = ('{0,-4}{1,-32}{2,-10}' -f $i, $ctrl.Name, $ctrl.Category) + Write-Host $line -NoNewline + Write-Host ($state + $lock + $flag) -ForegroundColor (Get-StateColor $state) + } + Write-Host ('{0,-4}{1,-32}{2,-10}{3}' -f '---', '-------', '-------', '-----') -ForegroundColor DarkGray + Write-Host (' Summary: {0} enabled, {1} disabled' -f $nEnabled, $nDisabled) -ForegroundColor White + Write-Host ' Policies apply on next browser start. While hardened, browsers' -ForegroundColor DarkCyan + Write-Host ' show a "managed" notice on settings pages - that is expected.' -ForegroundColor DarkCyan + if (-not $Script:IsAdmin) { + Write-Host ' * requires Administrator to change (run elevated)' -ForegroundColor DarkYellow + } + Write-Host '' +} + +function Export-StatusCsv { + param($Controls, [string]$Path) + $rows = foreach ($ctrl in $Controls) { + [pscustomobject]@{ + Name = $ctrl.Name + Browser = $ctrl.Category + Installed = $Script:Browsers[$ctrl.Category].Installed + Policy = $ctrl.ValueName + State = (Get-ControlState -Ctrl $ctrl) + Note = $ctrl.Note + } + } + $rows | Export-Csv -Path $Path -NoTypeInformation -Encoding ASCII + Write-Host ("Status written to {0}" -f $Path) -ForegroundColor Green +} + +# --------------------------------------------------------------------------- +# Apply helpers with guard rails +# --------------------------------------------------------------------------- +function Invoke-ControlAction { + param($Ctrl, [ValidateSet('Enable','Disable')][string]$Action) + + if ($Ctrl.AdminReq -and -not $Script:IsAdmin) { + Write-Host (" SKIP {0}/{1} (needs Administrator)" -f $Ctrl.Category, $Ctrl.Name) -ForegroundColor DarkYellow + return + } + try { + if ($Action -eq 'Enable') { Set-ControlEnabled -Ctrl $Ctrl } + else { Set-ControlDisabled -Ctrl $Ctrl } + $new = Get-ControlState -Ctrl $Ctrl + Write-Host (" OK {0}/{1} -> {2}" -f $Ctrl.Category, $Ctrl.Name, $new) -ForegroundColor Green + } catch { + Write-Host (" FAIL {0}/{1}: {2}" -f $Ctrl.Category, $Ctrl.Name, $_.Exception.Message) -ForegroundColor Red + } +} + +function Invoke-AllAction { + param($Controls, [ValidateSet('Enable','Disable')][string]$Action) + $verb = if ($Action -eq 'Enable') { 'ENABLE (restore browser defaults)' } else { 'DISABLE (harden)' } + Write-Host '' + Write-Host ("Applying {0} to ALL items..." -f $verb) -ForegroundColor Cyan + foreach ($ctrl in $Controls) { Invoke-ControlAction -Ctrl $ctrl -Action $Action } + Write-Host '' + Write-Host 'Restart the affected browsers for policies to take effect.' -ForegroundColor Cyan + Write-Host '' +} + +# --------------------------------------------------------------------------- +# Interactive menu +# --------------------------------------------------------------------------- +function Start-Menu { + param($Controls) + while ($true) { + Show-Status -Controls $Controls + Write-Host 'Commands:' -ForegroundColor White + Write-Host ' toggle item n (Enable<->Disable)' + Write-Host ' e enable item n' + Write-Host ' d disable item n' + Write-Host ' D disable ALL (harden)' + Write-Host ' E enable ALL (restore browser defaults)' + Write-Host ' b apply D to one browser (e.g. b Edge)' + Write-Host ' r refresh view' + Write-Host ' c export status to CSV' + Write-Host ' q quit' + Write-Host '' + $inp = Read-Host 'Select' + if ([string]::IsNullOrWhiteSpace($inp)) { continue } + $inp = $inp.Trim() + + # -CaseSensitive so bare "d"/"e" do NOT match the ALL branches below + switch -Regex -CaseSensitive ($inp) { + '^[Qq]$' { return } + '^[Rr]$' { continue } + '^D$' { + if ((Read-Host 'Harden ALL browser settings? type YES') -ceq 'YES') { + Invoke-AllAction -Controls $Controls -Action Disable + } + Read-Host 'Press Enter'; continue + } + '^E$' { + if ((Read-Host 'Restore ALL browser defaults? type YES') -ceq 'YES') { + Invoke-AllAction -Controls $Controls -Action Enable + } + Read-Host 'Press Enter'; continue + } + '^[Bb]\s+(\w+)$' { + $target = $Matches[1] + $subset = @($Controls | Where-Object { $_.Category -eq $target }) + if ($subset.Count -eq 0) { + Write-Host ('No controls for browser "{0}" (use Edge/Chrome/Firefox/Brave)' -f $target) -ForegroundColor Red + } elseif ((Read-Host ('Harden all {0} settings? type YES' -f $target)) -ceq 'YES') { + Invoke-AllAction -Controls $subset -Action Disable + } + Read-Host 'Press Enter'; continue + } + '^[Cc]\s+(.+)$' { + Export-StatusCsv -Controls $Controls -Path $Matches[1].Trim('"') + Read-Host 'Press Enter'; continue + } + '^[Ee]\s+(\d+)$' { + $n = [int]$Matches[1] + if ($n -ge 1 -and $n -le $Controls.Count) { Invoke-ControlAction -Ctrl $Controls[$n-1] -Action Enable } + else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + '^[Dd]\s+(\d+)$' { + $n = [int]$Matches[1] + if ($n -ge 1 -and $n -le $Controls.Count) { Invoke-ControlAction -Ctrl $Controls[$n-1] -Action Disable } + else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + '^\d+$' { + $n = [int]$inp + if ($n -ge 1 -and $n -le $Controls.Count) { + $ctrl = $Controls[$n-1] + $state = Get-ControlState -Ctrl $ctrl + if ($state -like 'Enabled*') { Invoke-ControlAction -Ctrl $ctrl -Action Disable } + else { Invoke-ControlAction -Ctrl $ctrl -Action Enable } + } else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + default { Write-Host 'Unknown command (d/e need an item number; D/E alone mean ALL)' -ForegroundColor Red; Start-Sleep -Milliseconds 600 } + } + } +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +$controls = Get-Controls + +if (@($controls).Count -eq 0) { + Write-Host 'No supported browsers detected (Edge/Chrome/Firefox/Brave).' -ForegroundColor Red + Write-Host 'Use -IncludeAll to manage policies for browsers not yet installed.' -ForegroundColor DarkYellow + return +} + +if ($DisableAll) { + Invoke-AllAction -Controls $controls -Action Disable + Show-Status -Controls $controls + if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + return +} +if ($EnableAll) { + Invoke-AllAction -Controls $controls -Action Enable + Show-Status -Controls $controls + if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + return +} +if ($Report -or $Csv) { + if ($Report) { Show-Status -Controls $controls } + if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + return +} + +if (-not $Script:IsAdmin) { + Write-Host '' + Write-Host 'NOTE: Not running as Administrator. Policy values live in HKLM,' -ForegroundColor DarkYellow + Write-Host ' so nothing can be changed until you relaunch this script' -ForegroundColor DarkYellow + Write-Host ' in an elevated PowerShell window. Viewing works fine.' -ForegroundColor DarkYellow +} + +Start-Menu -Controls $controls diff --git a/Manage-WindowsTelemetry.ps1 b/Manage-WindowsTelemetry.ps1 new file mode 100644 index 0000000..8d94df3 --- /dev/null +++ b/Manage-WindowsTelemetry.ps1 @@ -0,0 +1,611 @@ +#Requires -Version 5.1 +<# + Manage-WindowsTelemetry.ps1 + + Purpose : View and control common Windows 11 telemetry / diagnostic-data + settings, services and scheduled tasks. + + Scope : Shows what is currently ENABLED (data collecting) vs DISABLED, + and lets the user turn individual items - or everything - on/off. + + Areas : Core diagnostic data (AllowTelemetry), diagnostic log limits, + Windows Error Reporting (full stack: flags, consent, service, + task - the modern successor to Dr. Watson), CEIP/SQM, AppCompat + appraiser, Cloud Content / Tailored Experiences, Activity + History, Advertising ID, Feedback (SIUF), inking/typing and + speech data, search suggestions, DiagTrack + related services, + and telemetry scheduled tasks. + + Notes : - Windows PowerShell 5.1 compatible. ASCII-only source. + - Registry (HKLM) and service changes require Administrator. + - "Enabled" = the telemetry / data-collection behaviour is ON. + "Disabled" = the telemetry / data-collection behaviour is OFF. + - For policy-type values, "Enable" REMOVES the policy value to + restore the true Windows default (absent), so the Settings UI + is not left in a "managed by your organization" state. + + Usage : + .\Manage-WindowsTelemetry.ps1 # interactive menu + .\Manage-WindowsTelemetry.ps1 -Report # print status and exit + .\Manage-WindowsTelemetry.ps1 -DisableAll # turn telemetry OFF + .\Manage-WindowsTelemetry.ps1 -EnableAll # restore Windows default ON + .\Manage-WindowsTelemetry.ps1 -Csv .\out.csv # export status and exit + .\Manage-WindowsTelemetry.ps1 -Report -Csv .\status.csv + + DISCLAIMER: Review before use. Changing telemetry / service settings alters + system behaviour. Run in a test environment first. No warranty. +#> + +[CmdletBinding()] +param( + [switch]$Report, + [switch]$DisableAll, + [switch]$EnableAll, + [string]$Csv +) + +Set-StrictMode -Version 2.0 +$ErrorActionPreference = 'Stop' + +# --------------------------------------------------------------------------- +# Elevation helpers +# --------------------------------------------------------------------------- +function Test-IsAdmin { + $id = [Security.Principal.WindowsIdentity]::GetCurrent() + $pr = New-Object Security.Principal.WindowsPrincipal($id) + return $pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +$Script:IsAdmin = Test-IsAdmin + +# --------------------------------------------------------------------------- +# Registry helpers +# --------------------------------------------------------------------------- +function Resolve-RegPath { + param([string]$Hive, [string]$Path) + return ('{0}:\{1}' -f $Hive, $Path) +} + +function Get-RegValue { + param([string]$FullPath, [string]$Name) + try { + if (-not (Test-Path -LiteralPath $FullPath)) { return $null } + $item = Get-ItemProperty -LiteralPath $FullPath -Name $Name -ErrorAction Stop + return $item.$Name + } catch { + return $null + } +} + +function Set-RegValue { + param([string]$FullPath, [string]$Name, $Value, [string]$Type = 'DWord') + if (-not (Test-Path -LiteralPath $FullPath)) { + New-Item -Path $FullPath -Force | Out-Null + } + New-ItemProperty -LiteralPath $FullPath -Name $Name -Value $Value -PropertyType $Type -Force | Out-Null +} + +function Remove-RegValue { + param([string]$FullPath, [string]$Name) + if (Test-Path -LiteralPath $FullPath) { + Remove-ItemProperty -LiteralPath $FullPath -Name $Name -ErrorAction SilentlyContinue + } +} + +# --------------------------------------------------------------------------- +# Control model +# Each control is a PSCustomObject with a Type that drives Get/Enable/Disable. +# Types: Reg, Service, Task +# +# RemoveOnEnable: for policy-style values whose Windows default is ABSENT. +# Enabling such a control deletes the value instead of writing OnValue, so +# the machine is not left policy-managed after an "enable". +# --------------------------------------------------------------------------- +function New-RegControl { + param( + [string]$Name, [string]$Category, [string]$Hive, [string]$Path, + [string]$ValueName, [int]$OnValue, [int]$OffValue, + [ValidateSet('On','Off')][string]$Default = 'On', + [string]$RegType = 'DWord', [string]$Note = '', + [switch]$RemoveOnEnable + ) + [pscustomobject]@{ + Type = 'Reg' + Name = $Name + Category = $Category + Note = $Note + FullPath = (Resolve-RegPath -Hive $Hive -Path $Path) + ValueName = $ValueName + OnValue = $OnValue + OffValue = $OffValue + Default = $Default + RegType = $RegType + RemoveOnEnable = [bool]$RemoveOnEnable + AdminReq = ($Hive -eq 'HKLM') + } +} + +function New-ServiceControl { + param( + [string]$Name, [string]$ServiceName, + [ValidateSet('Automatic','Manual')][string]$DefaultStartupType = 'Automatic', + [string]$Note = '' + ) + [pscustomobject]@{ + Type = 'Service' + Name = $Name + Category = 'Service' + Note = $Note + ServiceName = $ServiceName + DefaultStartupType = $DefaultStartupType + AdminReq = $true + } +} + +function New-TaskControl { + param([string]$Name, [string[]]$Tasks, [string]$Note = '') + # $Tasks entries are full task paths, e.g. \Microsoft\Windows\Application Experience\ProgramDataUpdater + [pscustomobject]@{ + Type = 'Task' + Name = $Name + Category = 'Scheduled Task' + Note = $Note + Tasks = $Tasks + AdminReq = $true + } +} + +# --------------------------------------------------------------------------- +# State + actions +# --------------------------------------------------------------------------- +function Get-ControlState { + param($Ctrl) + switch ($Ctrl.Type) { + 'Reg' { + $cur = Get-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName + if ($null -eq $cur) { + if ($Ctrl.Default -eq 'On') { return 'Enabled (default)' } + else { return 'Disabled (default)' } + } + $curInt = $null + try { $curInt = [int]$cur } catch { + return ('Enabled (value={0})' -f $cur) # non-numeric data: report, do not throw + } + if ($curInt -eq $Ctrl.OffValue) { return 'Disabled' } + if ($curInt -eq $Ctrl.OnValue) { return 'Enabled' } + return ('Enabled (value={0})' -f $curInt) + } + 'Service' { + $svc = Get-Service -Name $Ctrl.ServiceName -ErrorAction SilentlyContinue + if ($null -eq $svc) { return 'Not present' } + if ($svc.StartType -eq 'Disabled') { return 'Disabled' } + return ('Enabled ({0}, {1})' -f $svc.StartType, $svc.Status) + } + 'Task' { + $states = @() + foreach ($t in $Ctrl.Tasks) { + $leaf = Split-Path $t -Leaf + $path = (Split-Path $t -Parent) + '\' + $task = Get-ScheduledTask -TaskName $leaf -TaskPath $path -ErrorAction SilentlyContinue + if ($task) { $states += $task.State } + } + if ($states.Count -eq 0) { return 'Not present' } + if ($states -contains 'Ready' -or $states -contains 'Running') { return 'Enabled' } + return 'Disabled' + } + } + return 'Unknown' +} + +function Set-ControlEnabled { + param($Ctrl) # Enable telemetry (restore Windows default behaviour) + switch ($Ctrl.Type) { + 'Reg' { + if ($Ctrl.RemoveOnEnable) { + Remove-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName + } else { + Set-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName -Value $Ctrl.OnValue -Type $Ctrl.RegType + } + } + 'Service' { + Set-Service -Name $Ctrl.ServiceName -StartupType $Ctrl.DefaultStartupType -ErrorAction Stop + if ($Ctrl.DefaultStartupType -eq 'Automatic') { + Start-Service -Name $Ctrl.ServiceName -ErrorAction SilentlyContinue + } + } + 'Task' { + foreach ($t in $Ctrl.Tasks) { + $leaf = Split-Path $t -Leaf + $path = (Split-Path $t -Parent) + '\' + Enable-ScheduledTask -TaskName $leaf -TaskPath $path -ErrorAction SilentlyContinue | Out-Null + } + } + } +} + +function Set-ControlDisabled { + param($Ctrl) # Disable telemetry (privacy hardened) + switch ($Ctrl.Type) { + 'Reg' { + Set-RegValue -FullPath $Ctrl.FullPath -Name $Ctrl.ValueName -Value $Ctrl.OffValue -Type $Ctrl.RegType + } + 'Service' { + Stop-Service -Name $Ctrl.ServiceName -Force -ErrorAction SilentlyContinue + Set-Service -Name $Ctrl.ServiceName -StartupType Disabled -ErrorAction Stop + } + 'Task' { + foreach ($t in $Ctrl.Tasks) { + $leaf = Split-Path $t -Leaf + $path = (Split-Path $t -Parent) + '\' + Disable-ScheduledTask -TaskName $leaf -TaskPath $path -ErrorAction SilentlyContinue | Out-Null + } + } + } +} + +# --------------------------------------------------------------------------- +# Control catalog +# --------------------------------------------------------------------------- +function Get-Controls { + $c = New-Object System.Collections.ArrayList + + # --- Core telemetry policy --- + [void]$c.Add( (New-RegControl -Name 'Diagnostic data (AllowTelemetry)' -Category 'Telemetry' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\DataCollection' ` + -ValueName 'AllowTelemetry' -OnValue 3 -OffValue 0 -Default On -RemoveOnEnable ` + -Note '0=Off/Security 1=Required 3=Optional. Home/Pro treat 0 as 1.') ) + + [void]$c.Add( (New-RegControl -Name 'Diagnostic data (non-policy)' -Category 'Telemetry' ` + -Hive HKLM -Path 'SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection' ` + -ValueName 'AllowTelemetry' -OnValue 3 -OffValue 0 -Default On -RemoveOnEnable) ) + + [void]$c.Add( (New-RegControl -Name 'Feedback Notifications' -Category 'Telemetry' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\DataCollection' ` + -ValueName 'DoNotShowFeedbackNotifications' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable ` + -Note 'Disabled hides feedback prompts') ) + + [void]$c.Add( (New-RegControl -Name 'Diagnostic Log Collection' -Category 'Telemetry' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\DataCollection' ` + -ValueName 'LimitDiagnosticLogCollection' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable ` + -Note 'Disabled blocks extra diagnostic log upload') ) + + [void]$c.Add( (New-RegControl -Name 'OneSettings Downloads' -Category 'Telemetry' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\DataCollection' ` + -ValueName 'DisableOneSettingsDownloads' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable ` + -Note 'Remote telemetry configuration channel') ) + + # --- Application compatibility / appraiser telemetry --- + [void]$c.Add( (New-RegControl -Name 'App Impact Telemetry (AITEnable)' -Category 'AppCompat' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\AppCompat' ` + -ValueName 'AITEnable' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable) ) + + [void]$c.Add( (New-RegControl -Name 'Inventory Collector' -Category 'AppCompat' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\AppCompat' ` + -ValueName 'DisableInventory' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable) ) + + # --- Windows Error Reporting (successor to Dr. Watson) --- + [void]$c.Add( (New-RegControl -Name 'Windows Error Reporting' -Category 'Error Reporting' ` + -Hive HKLM -Path 'SOFTWARE\Microsoft\Windows\Windows Error Reporting' ` + -ValueName 'Disabled' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable) ) + + [void]$c.Add( (New-RegControl -Name 'Error Reporting (policy)' -Category 'Error Reporting' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\Windows Error Reporting' ` + -ValueName 'Disabled' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable ` + -Note 'Group Policy variant; overrides the non-policy flag') ) + + [void]$c.Add( (New-RegControl -Name 'WER Additional Data' -Category 'Error Reporting' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\Windows Error Reporting' ` + -ValueName 'DontSendAdditionalData' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable ` + -Note 'Disabled stops second-stage crash data upload') ) + + [void]$c.Add( (New-RegControl -Name 'WER Consent Level' -Category 'Error Reporting' ` + -Hive HKLM -Path 'SOFTWARE\Microsoft\Windows\Windows Error Reporting\Consent' ` + -ValueName 'DefaultConsent' -OnValue 4 -OffValue 1 -Default On -RemoveOnEnable ` + -Note '1=always ask 2=params 3=params+safe 4=send all') ) + + # --- Customer Experience Improvement Program --- + [void]$c.Add( (New-RegControl -Name 'CEIP (SQM Client)' -Category 'CEIP' ` + -Hive HKLM -Path 'SOFTWARE\Microsoft\SQMClient\Windows' ` + -ValueName 'CEIPEnable' -OnValue 1 -OffValue 0 -Default On) ) + + # --- Cloud content / consumer features / ads --- + [void]$c.Add( (New-RegControl -Name 'Windows Consumer Features' -Category 'Cloud Content' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\CloudContent' ` + -ValueName 'DisableWindowsConsumerFeatures' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable) ) + + [void]$c.Add( (New-RegControl -Name 'Tailored Experiences (policy)' -Category 'Cloud Content' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\CloudContent' ` + -ValueName 'DisableTailoredExperiencesWithDiagnosticData' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable) ) + + # --- Activity history / timeline --- + [void]$c.Add( (New-RegControl -Name 'Publish User Activities' -Category 'Activity History' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\System' ` + -ValueName 'PublishUserActivities' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable) ) + + [void]$c.Add( (New-RegControl -Name 'Upload User Activities' -Category 'Activity History' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\System' ` + -ValueName 'UploadUserActivities' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable) ) + + # --- Per-user privacy (HKCU, no admin needed) --- + [void]$c.Add( (New-RegControl -Name 'Advertising ID' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo' ` + -ValueName 'Enabled' -OnValue 1 -OffValue 0 -Default On) ) + + [void]$c.Add( (New-RegControl -Name 'Tailored Experiences (user)' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\Windows\CurrentVersion\Privacy' ` + -ValueName 'TailoredExperiencesWithDiagnosticDataEnabled' -OnValue 1 -OffValue 0 -Default On) ) + + [void]$c.Add( (New-RegControl -Name 'Feedback Frequency (SIUF)' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\Siuf\Rules' ` + -ValueName 'NumberOfSIUFInPeriod' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable ` + -Note 'Disabled = never asked for feedback') ) + + [void]$c.Add( (New-RegControl -Name 'Implicit Ink Collection' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\InputPersonalization' ` + -ValueName 'RestrictImplicitInkCollection' -OnValue 0 -OffValue 1 -Default On ` + -Note 'Inking personalization data') ) + + [void]$c.Add( (New-RegControl -Name 'Implicit Text Collection' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\InputPersonalization' ` + -ValueName 'RestrictImplicitTextCollection' -OnValue 0 -OffValue 1 -Default On ` + -Note 'Typing personalization data') ) + + [void]$c.Add( (New-RegControl -Name 'Typing Insights (TIPC)' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\Input\TIPC' ` + -ValueName 'Enabled' -OnValue 1 -OffValue 0 -Default On ` + -Note 'Typing/ink telemetry channel') ) + + [void]$c.Add( (New-RegControl -Name 'Online Speech Recognition' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy' ` + -ValueName 'HasAccepted' -OnValue 1 -OffValue 0 -Default Off ` + -Note 'Cloud speech; default off until user consents') ) + + [void]$c.Add( (New-RegControl -Name 'Linguistic Data Collection' -Category 'Privacy (User)' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\TextInput' ` + -ValueName 'AllowLinguisticDataCollection' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable) ) + + [void]$c.Add( (New-RegControl -Name 'Search Box Web Suggestions' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Policies\Microsoft\Windows\Explorer' ` + -ValueName 'DisableSearchBoxSuggestions' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable ` + -Note 'Win11 control for web results in Search') ) + + [void]$c.Add( (New-RegControl -Name 'Bing Search (legacy)' -Category 'Privacy (User)' ` + -Hive HKCU -Path 'SOFTWARE\Microsoft\Windows\CurrentVersion\Search' ` + -ValueName 'BingSearchEnabled' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable ` + -Note 'Win10-era value; largely ignored on Win11') ) + + # --- Services --- + [void]$c.Add( (New-ServiceControl -Name 'Connected User Experiences' ` + -ServiceName 'DiagTrack' -DefaultStartupType Automatic ` + -Note 'DiagTrack - primary telemetry service') ) + + [void]$c.Add( (New-ServiceControl -Name 'WAP Push Routing (dmwappush)' ` + -ServiceName 'dmwappushservice' -DefaultStartupType Manual ` + -Note 'Disabling can break provisioning package / MDM enrolment') ) + + [void]$c.Add( (New-ServiceControl -Name 'Error Reporting Svc (WerSvc)' ` + -ServiceName 'WerSvc' -DefaultStartupType Manual ` + -Note 'Runs WER report submission') ) + + # --- Scheduled tasks --- + [void]$c.Add( (New-TaskControl -Name 'Compatibility Appraiser tasks' -Tasks @( + '\Microsoft\Windows\Application Experience\Microsoft Compatibility Appraiser', + '\Microsoft\Windows\Application Experience\ProgramDataUpdater', + '\Microsoft\Windows\Application Experience\StartupAppTask' + )) ) + + [void]$c.Add( (New-TaskControl -Name 'CEIP tasks' -Tasks @( + '\Microsoft\Windows\Customer Experience Improvement Program\Consolidator', + '\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip', + '\Microsoft\Windows\Autochk\Proxy' + ) -Note 'Includes Autochk\Proxy (kernel CEIP)') ) + + [void]$c.Add( (New-TaskControl -Name 'Feedback (SIUF) tasks' -Tasks @( + '\Microsoft\Windows\Feedback\Siuf\DmClient', + '\Microsoft\Windows\Feedback\Siuf\DmClientOnScenarioDownload' + )) ) + + [void]$c.Add( (New-TaskControl -Name 'Error Reporting task' -Tasks @( + '\Microsoft\Windows\Windows Error Reporting\QueueReporting' + )) ) + + [void]$c.Add( (New-TaskControl -Name 'Disk Diagnostic collector' -Tasks @( + '\Microsoft\Windows\DiskDiagnostic\Microsoft-Windows-DiskDiagnosticDataCollector' + )) ) + + return $c +} + +# --------------------------------------------------------------------------- +# Display +# --------------------------------------------------------------------------- +function Get-StateColor { + param([string]$State) + if ($State -like 'Disabled*') { return 'Green' } + if ($State -like 'Enabled*') { return 'Yellow' } + if ($State -like 'Not present*'){ return 'DarkGray' } + return 'Gray' +} + +function Show-Status { + param($Controls) + + Write-Host '' + Write-Host '==================================================================' -ForegroundColor Cyan + Write-Host ' Windows 11 Telemetry / Diagnostic Data Status' -ForegroundColor Cyan + Write-Host (' Host: {0} Admin: {1} {2}' -f $env:COMPUTERNAME, $Script:IsAdmin, (Get-Date)) -ForegroundColor DarkCyan + Write-Host ' Enabled = collecting/on Disabled = hardened/off' -ForegroundColor DarkCyan + Write-Host '==================================================================' -ForegroundColor Cyan + Write-Host ('{0,-4}{1,-34}{2,-18}{3}' -f '#', 'Setting', 'Category', 'State') -ForegroundColor White + Write-Host ('{0,-4}{1,-34}{2,-18}{3}' -f '---', '-------', '--------', '-----') -ForegroundColor DarkGray + + $i = 0 + $nEnabled = 0; $nDisabled = 0; $nAbsent = 0 + foreach ($ctrl in $Controls) { + $i++ + $state = Get-ControlState -Ctrl $ctrl + if ($state -like 'Enabled*') { $nEnabled++ } + elseif ($state -like 'Disabled*') { $nDisabled++ } + elseif ($state -like 'Not present*') { $nAbsent++ } + $lock = '' + if ($ctrl.AdminReq -and -not $Script:IsAdmin) { $lock = ' *' } + $line = ('{0,-4}{1,-34}{2,-18}' -f $i, $ctrl.Name, $ctrl.Category) + Write-Host $line -NoNewline + Write-Host ($state + $lock) -ForegroundColor (Get-StateColor $state) + } + Write-Host ('{0,-4}{1,-34}{2,-18}{3}' -f '---', '-------', '--------', '-----') -ForegroundColor DarkGray + Write-Host (' Summary: {0} enabled, {1} disabled, {2} not present' -f $nEnabled, $nDisabled, $nAbsent) -ForegroundColor White + if (-not $Script:IsAdmin) { + Write-Host ' * requires Administrator to change (run elevated)' -ForegroundColor DarkYellow + } + Write-Host '' +} + +function Export-StatusCsv { + param($Controls, [string]$Path) + $rows = foreach ($ctrl in $Controls) { + [pscustomobject]@{ + Name = $ctrl.Name + Category = $ctrl.Category + Type = $ctrl.Type + State = (Get-ControlState -Ctrl $ctrl) + AdminReq = $ctrl.AdminReq + Note = $ctrl.Note + } + } + $rows | Export-Csv -Path $Path -NoTypeInformation -Encoding ASCII + Write-Host ("Status written to {0}" -f $Path) -ForegroundColor Green +} + +# --------------------------------------------------------------------------- +# Apply helpers with guard rails +# --------------------------------------------------------------------------- +function Invoke-ControlAction { + param($Ctrl, [ValidateSet('Enable','Disable')][string]$Action) + + if ($Ctrl.AdminReq -and -not $Script:IsAdmin) { + Write-Host (" SKIP {0} (needs Administrator)" -f $Ctrl.Name) -ForegroundColor DarkYellow + return + } + if ((Get-ControlState -Ctrl $Ctrl) -eq 'Not present') { + Write-Host (" SKIP {0} (not present on this system)" -f $Ctrl.Name) -ForegroundColor DarkGray + return + } + try { + if ($Action -eq 'Enable') { Set-ControlEnabled -Ctrl $Ctrl } + else { Set-ControlDisabled -Ctrl $Ctrl } + $new = Get-ControlState -Ctrl $Ctrl + Write-Host (" OK {0} -> {1}" -f $Ctrl.Name, $new) -ForegroundColor Green + } catch { + Write-Host (" FAIL {0}: {1}" -f $Ctrl.Name, $_.Exception.Message) -ForegroundColor Red + } +} + +function Invoke-AllAction { + param($Controls, [ValidateSet('Enable','Disable')][string]$Action) + $verb = if ($Action -eq 'Enable') { 'ENABLE (restore Windows default)' } else { 'DISABLE (harden)' } + Write-Host '' + Write-Host ("Applying {0} to ALL items..." -f $verb) -ForegroundColor Cyan + foreach ($ctrl in $Controls) { Invoke-ControlAction -Ctrl $ctrl -Action $Action } + Write-Host '' +} + +# --------------------------------------------------------------------------- +# Interactive menu +# --------------------------------------------------------------------------- +function Start-Menu { + param($Controls) + while ($true) { + Show-Status -Controls $Controls + Write-Host 'Commands:' -ForegroundColor White + Write-Host ' toggle item n (Enable<->Disable)' + Write-Host ' e enable item n' + Write-Host ' d disable item n' + Write-Host ' D disable ALL (harden)' + Write-Host ' E enable ALL (restore Windows default)' + Write-Host ' r refresh view' + Write-Host ' c export status to CSV' + Write-Host ' q quit' + Write-Host '' + $inp = Read-Host 'Select' + if ([string]::IsNullOrWhiteSpace($inp)) { continue } + $inp = $inp.Trim() + + # -CaseSensitive so bare "d"/"e" do NOT match the ALL branches below + switch -Regex -CaseSensitive ($inp) { + '^[Qq]$' { return } + '^[Rr]$' { continue } + '^D$' { + if ((Read-Host 'Disable ALL telemetry? type YES') -ceq 'YES') { + Invoke-AllAction -Controls $Controls -Action Disable + } + Read-Host 'Press Enter'; continue + } + '^E$' { + if ((Read-Host 'Enable ALL (Windows default)? type YES') -ceq 'YES') { + Invoke-AllAction -Controls $Controls -Action Enable + } + Read-Host 'Press Enter'; continue + } + '^[Cc]\s+(.+)$' { + Export-StatusCsv -Controls $Controls -Path $Matches[1].Trim('"') + Read-Host 'Press Enter'; continue + } + '^[Ee]\s+(\d+)$' { + $n = [int]$Matches[1] + if ($n -ge 1 -and $n -le $Controls.Count) { Invoke-ControlAction -Ctrl $Controls[$n-1] -Action Enable } + else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + '^[Dd]\s+(\d+)$' { + $n = [int]$Matches[1] + if ($n -ge 1 -and $n -le $Controls.Count) { Invoke-ControlAction -Ctrl $Controls[$n-1] -Action Disable } + else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + '^\d+$' { + $n = [int]$inp + if ($n -ge 1 -and $n -le $Controls.Count) { + $ctrl = $Controls[$n-1] + $state = Get-ControlState -Ctrl $ctrl + if ($state -like 'Enabled*') { Invoke-ControlAction -Ctrl $ctrl -Action Disable } + else { Invoke-ControlAction -Ctrl $ctrl -Action Enable } + } else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + default { Write-Host 'Unknown command (d/e need an item number; D/E alone mean ALL)' -ForegroundColor Red; Start-Sleep -Milliseconds 600 } + } + } +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +$controls = Get-Controls + +if ($DisableAll) { + Invoke-AllAction -Controls $controls -Action Disable + Show-Status -Controls $controls + if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + return +} +if ($EnableAll) { + Invoke-AllAction -Controls $controls -Action Enable + Show-Status -Controls $controls + if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + return +} +if ($Report -or $Csv) { + if ($Report) { Show-Status -Controls $controls } + if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + return +} + +if (-not $Script:IsAdmin) { + Write-Host '' + Write-Host 'NOTE: Not running as Administrator. HKLM / service / task items' -ForegroundColor DarkYellow + Write-Host ' will show as read-only (*) and cannot be changed until you' -ForegroundColor DarkYellow + Write-Host ' relaunch this script in an elevated PowerShell window.' -ForegroundColor DarkYellow +} + +Start-Menu -Controls $controls diff --git a/README.md b/README.md index f883bdc..266f5cb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,222 @@ -# go_darker -A windows telemetry hardening script, use at own risk +# Windows Telemetry & Browser Privacy Tools + +Two single-file, dependency-free PowerShell 5.1 scripts: + +| Script | Scope | +|---|---| +| `Manage-WindowsTelemetry.ps1` | Windows 11 telemetry / diagnostic-data settings, services and scheduled tasks | +| `Manage-BrowserPrivacy.ps1` | Privacy / telemetry policies for Edge, Chrome, Firefox and Brave | + +Both share the same UX: a color-coded status view (**Enabled** = collecting, +**Disabled** = hardened), interactive per-item toggling, one-shot +`-DisableAll` / `-EnableAll`, `-Report`, and CSV export. Both are ASCII-only, +BOM-free, module-free, and run in stock `powershell.exe` on Windows 11. + +> **Disclaimer:** Changing telemetry, service, task and browser-policy +> settings alters system behaviour. Review the scripts before running them, +> test on a non-production machine first, and consider a restore point. +> No warranty. + +--- + +# 1. Manage-WindowsTelemetry.ps1 + +Views and controls **Windows 11 telemetry / diagnostic-data settings, +services and scheduled tasks** from one place. + +## Quick start + +```powershell +# View status only (read-only, safe anywhere) +.\Manage-WindowsTelemetry.ps1 -Report + +# Interactive menu (run from an ELEVATED PowerShell to change HKLM/services/tasks) +.\Manage-WindowsTelemetry.ps1 + +# One-shot hardening: turn all telemetry OFF +.\Manage-WindowsTelemetry.ps1 -DisableAll + +# Restore Windows default behaviour: turn everything back ON +.\Manage-WindowsTelemetry.ps1 -EnableAll + +# Export current status to CSV (exits after export) +.\Manage-WindowsTelemetry.ps1 -Csv .\telemetry-status.csv + +# Report to screen AND CSV +.\Manage-WindowsTelemetry.ps1 -Report -Csv .\telemetry-status.csv +``` + +If script execution is blocked on your machine: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\Manage-WindowsTelemetry.ps1 -Report +``` + +## Semantics + +| Term | Meaning | +|---|---| +| **Enabled** (yellow) | The telemetry / data-collection behaviour is ON | +| **Enabled (default)** | No override present; Windows out-of-box behaviour (ON) | +| **Disabled** (green) | The behaviour is OFF / hardened | +| **Not present** (gray) | Service or task does not exist on this system | +| `*` suffix | Requires Administrator to change (run elevated) | + +Two design rules worth knowing: + +1. **"Enable" restores the true Windows default.** For policy-style registry + values (where the Windows default is *no value at all*), enabling + **removes** the policy value instead of writing one. This avoids leaving + the machine in a "Some settings are managed by your organization" state + after an enable/disable round trip. +2. **Services are restored to their real default start types** (`DiagTrack` + = Automatic, `dmwappushservice` / `WerSvc` = Manual), not blanket + Automatic. + +## Interactive menu commands + +| Command | Action | +|---|---| +| `` | Toggle item *n* (Enabled <-> Disabled) | +| `e ` / `d ` | Enable / disable item *n* | +| `E` / `D` (uppercase) | Enable / disable **ALL** items (asks for `YES` confirmation) | +| `r` | Refresh the view | +| `c ` | Export status to CSV | +| `q` | Quit | + +The menu is case-sensitive where it matters: a bare lowercase `d`/`e` will +**not** trigger the ALL branches. + +## What is covered + +### Registry (policy and per-user) + +| Area | Items | +|---|---| +| Core telemetry | `AllowTelemetry` (policy + non-policy), feedback notifications, `LimitDiagnosticLogCollection`, `DisableOneSettingsDownloads` | +| App compatibility | Appraiser telemetry (`AITEnable`), Inventory Collector | +| Error Reporting (the modern successor to Dr. Watson) | WER on/off (non-policy + policy), `DontSendAdditionalData`, consent level (`DefaultConsent`) | +| CEIP | `CEIPEnable` (SQM Client) | +| Cloud content | Consumer Features, Tailored Experiences (policy) | +| Activity history | Publish / Upload User Activities | +| Per-user privacy (no admin needed) | Advertising ID, Tailored Experiences, Feedback frequency (SIUF), implicit ink/text collection, Typing Insights (TIPC), online speech recognition, linguistic data collection, Search box web suggestions (Win11) + legacy `BingSearchEnabled` | + +### Services + +| Service | Default start | Note | +|---|---|---| +| `DiagTrack` (Connected User Experiences and Telemetry) | Automatic | Primary telemetry service | +| `dmwappushservice` | Manual | Disabling can break provisioning-package / MDM enrolment | +| `WerSvc` (Windows Error Reporting) | Manual | Runs WER report submission | + +### Scheduled tasks + +- Application Experience: Compatibility Appraiser, ProgramDataUpdater, StartupAppTask +- CEIP: Consolidator, UsbCeip, Autochk\Proxy (kernel CEIP) +- Feedback (SIUF): DmClient, DmClientOnScenarioDownload +- Windows Error Reporting: QueueReporting +- DiskDiagnostic: DiskDiagnosticDataCollector + +## Known limitations + +- **Home/Pro diagnostic-data floor:** `AllowTelemetry = 0` (Security) is only + fully honoured on Enterprise/Education/IoT SKUs. On Home/Pro, Windows + treats 0 as 1 (Required), so a small baseline of required diagnostic data + may still flow even when everything here shows Disabled. +- Some items are refreshed by Windows Update or feature updates; re-run + `-Report` after major updates to verify state. +- `BingSearchEnabled` is a Windows 10-era value kept for completeness; the + effective Windows 11 control is `DisableSearchBoxSuggestions` (also + included). +- Changing WER/crash-reporting settings affects local crash-dump collection + behaviour, which you may want during debugging. + +## CSV output columns + +`Name, Category, Type (Reg/Service/Task), State, AdminReq, Note` + +--- + +# 2. Manage-BrowserPrivacy.ps1 + +Views and hardens **privacy / telemetry-related policy settings** for the +browsers installed on the machine. Browsers are auto-detected (via App Paths +registration); only installed ones are shown unless you pass `-IncludeAll`. + +## Quick start + +```powershell +# View status only (read-only) +.\Manage-BrowserPrivacy.ps1 -Report + +# Interactive menu (ELEVATED PowerShell required to change anything) +.\Manage-BrowserPrivacy.ps1 + +# One-shot: harden every detected browser +.\Manage-BrowserPrivacy.ps1 -DisableAll + +# Restore all browser defaults (removes the policy values) +.\Manage-BrowserPrivacy.ps1 -EnableAll + +# Export status to CSV / include browsers that are not installed +.\Manage-BrowserPrivacy.ps1 -Csv .\browser-status.csv +.\Manage-BrowserPrivacy.ps1 -Report -IncludeAll +``` + +The menu supports the same commands as the Windows tool, plus +`b ` to harden a single browser at once (e.g. `b Edge`). + +## How it works + +All controls are **policy DWORD values** under `HKLM\SOFTWARE\Policies\...` +(the official enterprise policy locations each vendor documents). Because a +browser's true default is *no policy value at all*, **Enable always removes +the value** rather than writing one - so an enable/disable round trip leaves +no permanent "managed" residue. + +Two things to expect: + +- Policies apply on the **next browser start** - restart the browser after + changes. +- While hardened, browsers display a **"Managed by your organization"** + notice on their settings pages. That is Chromium/Firefox correctly + reporting that policies are active, not a malfunction; `-EnableAll` + removes it again. + +## What is covered + +| Browser | Policies | +|---|---| +| **Edge** | Diagnostic data, personalization reporting, user feedback, search suggestions, Bing address-bar provider, Shopping Assistant, Rewards, Web Widget, Spotlight recommendations, Do Not Track, navigation-error web service, alternate error pages, network prediction (prefetch) | +| **Chrome** | Metrics reporting (UMA), search suggestions, Safe Browsing extended reporting, URL-keyed data collection, cloud spell check, alternate error pages, network prediction, feedback surveys, Privacy Sandbox (prompt, Ad Topics, site-suggested ads, ad measurement) | +| **Firefox** | Telemetry, Firefox Studies (Shield), Default Browser Agent (daily Mozilla ping), Pocket | +| **Brave** | Rewards, Wallet, VPN, Tor windows, search suggestions (Chromium policy) | + +Notes: + +- Chrome's Safe Browsing itself is left ON; only the *extended reporting* + (extra data to Google) is hardened. +- The three Chrome Privacy Sandbox ad policies require the Privacy Sandbox + prompt policy to be Disabled as well - the tool includes it. +- Brave sends comparatively little telemetry by default; its entries harden + the bundled feature surface (Rewards/Wallet/VPN/Tor), each of which + contacts Brave services. +- DNS-over-HTTPS policies are intentionally not touched - DoH is a privacy + *gain* in most settings and is better configured deliberately per network. + +## CSV output columns + +`Name, Browser, Installed, Policy, State, Note` + +--- + +# Files + +| File | Purpose | +|---|---| +| `Manage-WindowsTelemetry.ps1` | Windows telemetry view + control | +| `Manage-BrowserPrivacy.ps1` | Browser privacy view + hardening | +| `README.md` | This file | + +Both CSV exports are useful for fleet auditing: run with `-Csv` on multiple +machines and diff or aggregate the results.