diff --git a/Manage-WindowsTelemetry.ps1 b/Manage-WindowsTelemetry.ps1 index 215e6eb..559f5a5 100644 --- a/Manage-WindowsTelemetry.ps1 +++ b/Manage-WindowsTelemetry.ps1 @@ -29,8 +29,18 @@ restore the true Windows default (absent), so the Settings UI is not left in a "managed by your organization" state. + User mode: + -UserMode restricts the tool to per-user (HKCU) items only - the ones + that need NO administrator rights. Run it as your normal (unelevated) + account for personal privacy hardening: it writes only YOUR user hive, + so it also avoids the "HKCU points at the admin's profile when you + elevate as a different user" pitfall. HKLM policy/service/task items + are hidden in this mode. + Usage : - .\Manage-WindowsTelemetry.ps1 # interactive menu + .\Manage-WindowsTelemetry.ps1 # interactive menu (all items) + .\Manage-WindowsTelemetry.ps1 -UserMode # per-user items only, no admin + .\Manage-WindowsTelemetry.ps1 -UserMode -DisableAll # harden your user profile .\Manage-WindowsTelemetry.ps1 -Report # print status and exit .\Manage-WindowsTelemetry.ps1 -DisableAll # turn telemetry OFF (keeps [SEC] ON) .\Manage-WindowsTelemetry.ps1 -DisableAll -IncludeSecurity # also disable SmartScreen @@ -48,6 +58,7 @@ param( [switch]$DisableAll, [switch]$EnableAll, [switch]$IncludeSecurity, # also disable [SEC] SmartScreen features in bulk actions + [switch]$UserMode, # only per-user (HKCU) items; no admin needed [string]$Csv ) @@ -63,7 +74,8 @@ function Test-IsAdmin { return $pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } -$Script:IsAdmin = Test-IsAdmin +$Script:IsAdmin = Test-IsAdmin +$Script:UserMode = [bool]$UserMode # --------------------------------------------------------------------------- # Registry helpers @@ -531,6 +543,15 @@ function Get-Controls { return $c } +# Restrict to per-user (HKCU, no-admin) items when in user mode. +function Select-ActiveControls { + param($All) + if ($Script:UserMode) { + return @($All | Where-Object { -not $_.AdminReq }) + } + return $All +} + # --------------------------------------------------------------------------- # Display # --------------------------------------------------------------------------- @@ -549,13 +570,18 @@ function Show-Status { 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 + if ($Script:UserMode) { + Write-Host ' MODE: USER (per-user HKCU items only; no admin required)' -ForegroundColor Magenta + } else { + Write-Host ' MODE: FULL (per-user + system-wide items)' -ForegroundColor DarkCyan + } Write-Host ' Enabled = collecting/on Disabled = hardened/off' -ForegroundColor DarkCyan Write-Host '==================================================================' -ForegroundColor Cyan Write-Host ('{0,-4}{1,-36}{2,-18}{3}' -f '#', 'Setting', 'Category', 'State') -ForegroundColor White Write-Host ('{0,-4}{1,-36}{2,-18}{3}' -f '---', '-------', '--------', '-----') -ForegroundColor DarkGray $i = 0 - $nEnabled = 0; $nDisabled = 0; $nAbsent = 0; $nSecOff = 0 + $nEnabled = 0; $nDisabled = 0; $nAbsent = 0; $nSecOff = 0; $nSec = 0; $anyLock = $false foreach ($ctrl in $Controls) { $i++ $state = Get-ControlState -Ctrl $ctrl @@ -564,22 +590,24 @@ function Show-Status { elseif ($state -like 'Not present*') { $nAbsent++ } $sec = '' if ($ctrl.Security) { - $sec = ' [SEC]' + $sec = ' [SEC]'; $nSec++ if ($state -like 'Disabled*') { $nSecOff++ } } $lock = '' - if ($ctrl.AdminReq -and -not $Script:IsAdmin) { $lock = ' *' } + if ($ctrl.AdminReq -and -not $Script:IsAdmin) { $lock = ' *'; $anyLock = $true } $line = ('{0,-4}{1,-36}{2,-18}' -f $i, ($ctrl.Name + $sec), $ctrl.Category) Write-Host $line -NoNewline Write-Host ($state + $lock) -ForegroundColor (Get-StateColor $state) } Write-Host ('{0,-4}{1,-36}{2,-18}{3}' -f '---', '-------', '--------', '-----') -ForegroundColor DarkGray Write-Host (' Summary: {0} enabled, {1} disabled, {2} not present' -f $nEnabled, $nDisabled, $nAbsent) -ForegroundColor White - Write-Host ' [SEC] = anti-malware reputation check. Disabling REDUCES protection' -ForegroundColor DarkYellow + if ($nSec -gt 0) { + Write-Host ' [SEC] = anti-malware reputation check. Disabling REDUCES protection' -ForegroundColor DarkYellow + } if ($nSecOff -gt 0) { Write-Host (' WARNING: {0} security SmartScreen feature(s) are currently OFF' -f $nSecOff) -ForegroundColor Red } - if (-not $Script:IsAdmin) { + if ($anyLock) { Write-Host ' * requires Administrator to change (run elevated)' -ForegroundColor DarkYellow } Write-Host '' @@ -653,16 +681,22 @@ function Invoke-AllAction { # Interactive menu # --------------------------------------------------------------------------- function Start-Menu { - param($Controls) + param($Controls) # full catalog; the active view is filtered by user mode while ($true) { - Show-Status -Controls $Controls + $view = @(Select-ActiveControls -All $Controls) + Show-Status -Controls $view 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; keeps [SEC] SmartScreen ON)' - Write-Host ' E enable ALL (restore Windows default)' + Write-Host ' D disable ALL shown (harden; keeps [SEC] SmartScreen ON)' + Write-Host ' E enable ALL shown (restore Windows default)' Write-Host ' S disable ALL [SEC] SmartScreen features (reduces security)' + if ($Script:UserMode) { + Write-Host ' m switch to FULL mode (also show system-wide items)' + } else { + Write-Host ' m switch to USER mode (per-user items only, no admin)' + } Write-Host ' r refresh view' Write-Host ' c export status to CSV' Write-Host ' q quit' @@ -675,22 +709,23 @@ function Start-Menu { switch -Regex -CaseSensitive ($inp) { '^[Qq]$' { return } '^[Rr]$' { continue } + '^[Mm]$' { $Script:UserMode = -not $Script:UserMode; continue } '^D$' { - if ((Read-Host 'Disable ALL telemetry? type YES') -ceq 'YES') { - Invoke-AllAction -Controls $Controls -Action Disable + if ((Read-Host 'Disable ALL shown telemetry? type YES') -ceq 'YES') { + Invoke-AllAction -Controls $view -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 + if ((Read-Host 'Enable ALL shown (Windows default)? type YES') -ceq 'YES') { + Invoke-AllAction -Controls $view -Action Enable } Read-Host 'Press Enter'; continue } '^S$' { - $secList = @($Controls | Where-Object { $_.Security }) + $secList = @($view | Where-Object { $_.Security }) if ($secList.Count -eq 0) { - Write-Host 'No [SEC] controls in the catalog.' -ForegroundColor DarkYellow + Write-Host 'No [SEC] controls shown (they are system-wide; switch to FULL mode with "m").' -ForegroundColor DarkYellow Read-Host 'Press Enter'; continue } Write-Host '' @@ -703,32 +738,32 @@ function Start-Menu { Read-Host 'Press Enter'; continue } '^[Cc]\s+(.+)$' { - Export-StatusCsv -Controls $Controls -Path $Matches[1].Trim('"') + Export-StatusCsv -Controls $view -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 } + if ($n -ge 1 -and $n -le $view.Count) { Invoke-ControlAction -Ctrl $view[$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 } + if ($n -ge 1 -and $n -le $view.Count) { Invoke-ControlAction -Ctrl $view[$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] + if ($n -ge 1 -and $n -le $view.Count) { + $ctrl = $view[$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/S alone act on ALL)' -ForegroundColor Red; Start-Sleep -Milliseconds 600 } + default { Write-Host 'Unknown command (d/e need an item number; D/E/S/m act on the view)' -ForegroundColor Red; Start-Sleep -Milliseconds 600 } } } } @@ -737,34 +772,43 @@ function Start-Menu { # Main # --------------------------------------------------------------------------- $controls = Get-Controls +$active = @(Select-ActiveControls -All $controls) if ($IncludeSecurity -and -not $DisableAll) { Write-Host 'NOTE: -IncludeSecurity only has an effect together with -DisableAll; ignoring it.' -ForegroundColor DarkYellow } +if ($UserMode) { + Write-Host ('USER MODE: showing {0} per-user item(s) only (no admin needed).' -f $active.Count) -ForegroundColor Magenta +} if ($DisableAll) { - Invoke-AllAction -Controls $controls -Action Disable -WithSecurity:$IncludeSecurity - Show-Status -Controls $controls - if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + Invoke-AllAction -Controls $active -Action Disable -WithSecurity:$IncludeSecurity + Show-Status -Controls $active + if ($Csv) { Export-StatusCsv -Controls $active -Path $Csv } return } if ($EnableAll) { - Invoke-AllAction -Controls $controls -Action Enable - Show-Status -Controls $controls - if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + Invoke-AllAction -Controls $active -Action Enable + Show-Status -Controls $active + if ($Csv) { Export-StatusCsv -Controls $active -Path $Csv } return } if ($Report -or $Csv) { - if ($Report) { Show-Status -Controls $controls } - if ($Csv) { Export-StatusCsv -Controls $controls -Path $Csv } + if ($Report) { Show-Status -Controls $active } + if ($Csv) { Export-StatusCsv -Controls $active -Path $Csv } return } -if (-not $Script:IsAdmin) { +if ($UserMode) { + Write-Host '' + Write-Host 'USER MODE is on: changes apply to YOUR account only and need no' -ForegroundColor Magenta + Write-Host 'elevation. Press "m" in the menu to include system-wide items.' -ForegroundColor Magenta +} elseif (-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 + Write-Host ' show as read-only (*). Use -UserMode (or "m") to work with' -ForegroundColor DarkYellow + Write-Host ' per-user items only, or relaunch elevated for the rest.' -ForegroundColor DarkYellow } +# Start-Menu always receives the full catalog; user mode filters the view. Start-Menu -Controls $controls diff --git a/README.md b/README.md index b240489..1caa839 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,11 @@ services and scheduled tasks** from one place. # Interactive menu (run from an ELEVATED PowerShell to change HKLM/services/tasks) .\Manage-WindowsTelemetry.ps1 +# USER MODE - per-user (HKCU) items only, NO admin needed. Run as yourself. +.\Manage-WindowsTelemetry.ps1 -UserMode +.\Manage-WindowsTelemetry.ps1 -UserMode -DisableAll # harden your profile +.\Manage-WindowsTelemetry.ps1 -UserMode -Report + # One-shot hardening: turn all telemetry OFF (keeps [SEC] SmartScreen ON) .\Manage-WindowsTelemetry.ps1 -DisableAll @@ -111,14 +116,45 @@ Two design rules worth knowing: = Automatic, `dmwappushservice` / `WerSvc` = Manual), not blanket Automatic. +## User mode (`-UserMode`) - recommended for personal use + +For personal privacy/telemetry hardening, run **user mode** as your normal +(unelevated) account: + +```powershell +.\Manage-WindowsTelemetry.ps1 -UserMode +``` + +User mode shows **only the per-user (HKCU) items** - Advertising ID, tailored +experiences, inking/typing, feedback frequency, search suggestions, +Suggestions/Ads, Windows Copilot, and the Store-apps SmartScreen check. All +of these change **your account only** and need **no administrator rights**. + +Why it's the safer choice for a single-user machine: + +- **No elevation, no hive mix-up.** System-wide (HKLM) items require admin, + and the usual way to get admin is to elevate. But if you elevate using a + *different* admin account, `HKCU` inside that elevated session points at the + **admin's** profile - so per-user toggles silently apply to the wrong user. + Running user mode unelevated as yourself writes your own hive, every time. +- **Nothing you can't set from the Settings app** - it's the same per-user + switches, just scripted and repeatable. + +The header shows `MODE: USER` or `MODE: FULL`. In the interactive menu press +**`m`** to switch between them at any time; `-DisableAll` / `-EnableAll` / +`-Report` / `-Csv` all respect the mode. To also harden system-wide items +(diagnostic data, DiagTrack, scheduled tasks, etc.), run **without** +`-UserMode` from an elevated PowerShell. + ## 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; `D` keeps `[SEC]` items ON) | +| `E` / `D` (uppercase) | Enable / disable **ALL** shown items (asks for `YES` confirmation; `D` keeps `[SEC]` items ON) | | `S` | Disable the `[SEC]` SmartScreen features (requires typing `DISABLE-SECURITY`) | +| `m` | Toggle between USER mode (per-user only) and FULL mode (all items) | | `r` | Refresh the view | | `c ` | Export status to CSV | | `q` | Quit |