If you need to restrict access to Windows services you need to be careful how you handle this as you can hide the service and stop is from starting, so be careful which services you choose, however if you have a requirement for this then continue with this guide.
This approach is already being used for many services, including for example Advanced Threat Protection (or ATP) as you can see below as an administrator I have no service controls they are all "greyed out"
Right, so lets take the Windows Update service as an example, this is a service that can be edited by an administrator as you can see below, the options to Start, Stop are available to the user in this example.
WARNING : Ensure you know what you are doing before you change ACL's on services as this can break the service irreversibly, also ensure you have a back out plan before randomly settings ACL security permissions!
Script : RestrictService.ps1
# PowerShell script to modify Windows Update service permissions
# This script restricts control of the Windows Update service to specific user only
# while maintaining visibility for all users
#Requires -RunAsAdministrator
# Service name to modify
$serviceName = "wuauserv"
# User who will have control permissions
$controlUser = "bear\windowsupdate.admin"
# Check if service exists
$service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($null -eq $service) {
Write-Error "Windows Update service (wuauserv) was not found."
exit 1
}
Write-Host "Modifying permissions for Windows Update service ($($service.DisplayName))..."
# Get the original SDDL string
$originalSDDL = $null
try {
$cmd = "sc.exe sdshow $serviceName"
$originalSDDL = Invoke-Expression $cmd
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrEmpty($originalSDDL)) {
Write-Error "Failed to retrieve current security descriptor. Exit code: $LASTEXITCODE"
exit 1
}
Write-Host "Retrieved current security descriptor: $originalSDDL"
}
catch {
Write-Error "An error occurred: $_"
exit 1
}
# Split the SDDL into DACL and SACL parts
$DACL = ""
$SACL = ""
if ($originalSDDL -match "^(D:[^S]*)S:(.*)") {
$DACL = $matches[1]
$SACL = "S:" + $matches[2]
}
elseif ($originalSDDL -match "^(D:.*)") {
$DACL = $matches[1]
$SACL = "S:"
}
else {
Write-Error "Could not parse the security descriptor."
exit 1
}
Write-Host "Parsed DACL: $DACL"
Write-Host "Parsed SACL: $SACL"
# Convert the username to SID
try {
$ntAccount = New-Object System.Security.Principal.NTAccount($controlUser)
$userSID = $ntAccount.Translate([System.Security.Principal.SecurityIdentifier]).Value
Write-Host "User $controlUser has SID: $userSID"
}
catch {
Write-Error "Could not find user $controlUser. Please ensure the username is correct."
exit 1
}
# Create a new DACL with the right permissions
# Preserving format but changing permissions:
# CCLCSWRPWPDTLOCRRC = Full control
# CCLCSWLOCRRC = Read-only with display
$newDACL = "D:(A;;CCLCSWLOCRRC;;;SY)(A;;CCLCSWLOCRRC;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$userSID)"
# Combine the new DACL with the original SACL
$newSDDL = "$newDACL$SACL"
Write-Host "Created new security descriptor: $newSDDL"
# Apply the new security descriptor
try {
$cmd = "sc.exe sdset $serviceName `"$newSDDL`""
Write-Host "Executing: $cmd"
$result = Invoke-Expression $cmd
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to set security descriptor. Exit code: $LASTEXITCODE"
exit 1
}
Write-Host "Security descriptor applied successfully: $result"
}
catch {
Write-Error "Failed to set security descriptor: $_"
exit 1
}
# Verify the service permissions were set correctly
try {
$verifySDDL = Invoke-Expression "sc.exe sdshow $serviceName"
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to verify security descriptor. Exit code: $LASTEXITCODE"
exit 1
}
Write-Host "New security descriptor verified: $verifySDDL"
# Verify the service is still accessible
$visibleService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
if ($null -eq $visibleService) {
Write-Warning "Service may not be visible in management console. Please verify manually."
}
else {
Write-Host "Service visibility confirmed: $($visibleService.DisplayName) is visible."
}
Write-Host "`nPermissions for Windows Update service have been modified successfully."
Write-Host "- Only $controlUser can start, stop, or edit the service."
Write-Host "- SYSTEM and other users have read-only access."
Write-Host "- The service should remain visible to all users."
}
catch {
Write-Error "An error occurred during verification: $_"
exit 1
}
Now when we check the service, unless you are the specified user then the options to manage the service have vanished as if my magic.
This means only that one user can control the status of that service, which is handy to know.
How do I reset the service back to "default" permissions?
That is also very simple you can use this default permission marker to set everything back to "normal" operations:
# Set default security for the Windows Update service using the full path to sc.exe
& "$env:SystemRoot\System32\sc.exe" sdset wuauserv "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWLOCRRC;;;SU)"
However remember that not "any user" can perform these actions, they will get Access Denied as below as you have restricted the service earlier, remember!