categories/Services.ps1
|
# Services category: curated, risk-rated optional-service catalog (research.md - originally 36 # entries cross-referenced against multiple independent public sources; 35 remain Service-typed # below since 008 moved DoSvc to a Registry-typed action - see that note further down). Not # already covered by telemetry/oem-suites - FR-037. # # 008: DoSvc (Delivery Optimization) removed from the generic Service-typed catalog above - # confirmed during 006's VM validation that Set-Service is rejected by DoSvc's ACL even under a # full admin token (Microsoft intentionally steers DoSvc configuration through Group Policy, not # the service control API). Replaced below with a Registry-typed action targeting the documented # DODownloadMode Group Policy key instead - see Get-OctaServicesActions/Set-OctaServicesAction. $script:OctaServicesCatalog = @( # Safe - target Disabled, no working-functionality loss if genuinely unused @{ Name = 'MapsBroker'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'WpcMonSvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'PhoneSvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'RetailDemo'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'wisvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'icssvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'XboxGipSvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'XblAuthManager'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'XblGameSave'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'XboxNetApiSvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'Fax'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'WalletService'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'TabletInputService'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'lfsvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'WerSvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'SCardSvr'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'ScDeviceEnum'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'SCPolicySvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'BTAGService'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'bthserv'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'SensorService'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'WbioSrvc'; Target = 'Disabled'; RiskLevel = 'Safe' } @{ Name = 'DevicePickerUserSvc'; Target = 'Disabled'; RiskLevel = 'Safe'; IsUserTemplate = $true } # Moderate - target Manual, real functionality trade-off if actually used @{ Name = 'Spooler'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'DPS'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'WdiServiceHost'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'WdiSystemHost'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'DiagsSvc'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'SessionEnv'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'TermService'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'UmRdpService'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'WorkFolders'; Target = 'Manual'; RiskLevel = 'Moderate' } @{ Name = 'Netlogon'; Target = 'Manual'; RiskLevel = 'Moderate' } # Risky - real, narrow chance of a serious problem @{ Name = 'BDESVC'; Target = 'Disabled'; RiskLevel = 'Risky' } @{ Name = 'AssignedAccessManagerSvc'; Target = 'Disabled'; RiskLevel = 'Risky' } ) function Get-OctaServicesCategory { [CmdletBinding()] param() return [pscustomobject]@{ Id = 'services' DisplayName = 'Services' Description = 'Curated, risk-rated optional Windows services' RequiresElevation = $true ContainsIrreversibleActions = $false GetActionsFunction = 'Get-OctaServicesActions' ApplyActionFunction = 'Set-OctaServicesAction' } } function Get-OctaServicesActions { [CmdletBinding()] param() $actions = @() # ponytail: one bulk Get-CimInstance, matched against the catalog in memory - not one # -Filter query per catalog entry. Measured directly: 35 separate `-Filter "Name='X'"` # calls took ~10.1s total (~290ms each, WQL query overhead per call), while fetching every # service in a single unfiltered call and grouping by name took 0.47s - about 20x faster for # the exact same result. This tripped the project's own "GetActionsFunction completes # quickly" perf test at the 10-second threshold, the same root cause as the earlier # Get-OctaCategory startup-delay fix (many small expensive calls instead of one cheap one). $allServices = @(Get-CimInstance -ClassName Win32_Service -ErrorAction SilentlyContinue) $servicesByName = @{} foreach ($svc in $allServices) { $servicesByName[$svc.Name] = $svc } foreach ($entry in $script:OctaServicesCatalog) { # ponytail: named $matchedServices, not $matches - PowerShell variable names are # case-insensitive, so a bare "$matches" is the SAME variable as the automatic $Matches # the -match operator populates on the line right below it. It happened to work only # because nothing downstream read $Matches's regex capture groups after this point - a # future edit that did (a common PowerShell idiom right after a -match) would have # silently gotten this services array instead. Found via static analysis # (PSAvoidAssignmentToAutomaticVariable), not a live failure. if ($entry.IsUserTemplate) { $matchedServices = @($allServices | Where-Object { $_.Name -match "^$($entry.Name)_" }) } elseif ($servicesByName.ContainsKey($entry.Name)) { $matchedServices = @($servicesByName[$entry.Name]) } else { $matchedServices = @() } foreach ($svc in $matchedServices) { if ($svc.StartMode -eq $entry.Target) { continue } $actions += New-OctaAction -TargetType Service -TargetIdentifier $svc.Name ` -CurrentValue $svc.StartMode -PlannedValue $entry.Target -Reversible $true -RiskLevel $entry.RiskLevel } } # Delivery Optimization via Group Policy (008) - see file header comment for why this isn't # a Set-Service call like everything else above. $doKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization' $doMode = (Get-ItemProperty -Path $doKey -Name 'DODownloadMode' -ErrorAction SilentlyContinue).DODownloadMode if ($doMode -ne 0) { $displayCurrent = if ($null -eq $doMode) { '(not set)' } else { $doMode } $actions += New-OctaAction -TargetType Registry -TargetIdentifier "$doKey|DODownloadMode" ` -CurrentValue $displayCurrent -PlannedValue 0 -Reversible $true -RiskLevel Moderate } return $actions } function Set-OctaServicesAction { [CmdletBinding()] param([Parameter(Mandatory)]$Action) if ($Action.TargetType -eq 'Registry') { $keyPath, $valueName = $Action.TargetIdentifier -split '\|', 2 if (-not (Test-Path $keyPath)) { New-Item -Path $keyPath -Force | Out-Null } Set-ItemProperty -Path $keyPath -Name $valueName -Value $Action.PlannedValue -Type DWord return } Set-Service -Name $Action.TargetIdentifier -StartupType $Action.PlannedValue } |