Restart one named Windows service on a device, wait until it's running, and bring its dependent services back up
Restart a hung or stopped Windows service on a device by name, confirm it's running again, and restart any services that depend on it. For MSPs and IT teams who restart the same services every week.
| Runs in | On the device (PowerShell, as SYSTEM) |
| Uses | None |
| Suggested approval | None, so only Engineers and Admins can run it. For end users, use a copy limited to a select list of services, with Anyone → Require approval from an engineer group |
| Category | Devices |
Test it with SuperIT before you rely on it
This is an example, not a ready-made action, and it hasn't been tested end to end. Before you use it, work through it with SuperIT: ask SuperIT to review the script against your environment and requirements, and adjust it together. Then run it on a test device, as in Test it. Device actions can't use Test, so every run is real.
Why use an action instead of asking SuperIT
SuperIT can tell you a service is stopped, but it can't restart it in a chat: Restart-Service, Start-Service and Stop-Service are denied by the default command policies. An admin could allow them, but then every chat could stop any service. This action runs as SYSTEM, isn't checked against command policies, refuses services on its block-list and refuses to run on a domain controller, waits for the service to run and brings back the services that depend on it; its authorization policy decides who can run it.
To check whether a service is running, or why it stopped, just ask SuperIT in chat.
When to use it
- "The accounting app says it can't connect to its service."
- "Restart the print spooler on RECEPTION-PC."
- "Windows Search is broken, nothing shows up in the Start menu."
- A ticket investigation finds the backup agent service stopped on SRV-FS01. An engineer then runs the action on SRV-FS01 from a chat.
What the action does
- Reads
expected_hostnameandservice_name.service_namecan be the service name (Spooler) or display name (Print Spooler). Wildcards aren't accepted. - Stops without changing anything if
expected_hostnameisn't this device's computer name. - Refuses to run on a domain controller.
- Refuses the SuperIT RMM agent's own service, core Windows services, and services whose restart cuts off users or the network, such as Remote Desktop Services, the Server service, DNS and Active Directory.
- Refuses a disabled service.
- Records the running services that depend on it, directly or through other services.
- Restarts the service, or starts it if it was stopped, and waits up to 60 seconds for it to run.
- Starts the dependent services again.
- Prints the state before and after, and returns it as a result.
What it doesn't do:
- It doesn't change a service's startup type or account.
- It doesn't restart the SuperIT RMM agent. Restarting the agent from an action would cut off the action itself.
- It doesn't restart services on another computer.
- It doesn't run on domain controllers. Restart services there by hand, or in a separate copy that needs an admin's approval.
Before you begin
- A Windows device with the SuperIT RMM agent, online. Actions run on Windows devices only; the macOS and Linux agents are in Beta and can't run actions. See Requirements.
- Your role. Creating an action in a chat needs the Admin role. Engineers and Admins can build it under Workflows → Actions.
- No credential. See Credentials.
- No authorization policy. With None, only Engineers and Admins can run it, without approval. See Authorization policies and approvals.
Device actions and end users
A device action runs as SYSTEM on the device chosen for the run, or on the action's default device or your team's designated device if none is chosen. SuperIT doesn't check that the device belongs to the person who asked. Never give this action, or a copy of it, an Anyone → No approval needed rule.
Build it with SuperIT
Create an action called "Restart a Windows service".
Runtime: PowerShell, running on the device as SYSTEM (execution target: device).
Inputs: expected_hostname (string, required), service_name (string, required) - service name or display name, no wildcards.
Stop before changing anything unless expected_hostname matches $env:COMPUTERNAME. Refuse to run on a domain controller (Win32_OperatingSystem ProductType 2).
Refuse SuperIT* services, core services (RpcSs, RpcEptMapper, DcomLaunch, LSM, SamSs, Winmgmt, EventLog, PlugPlay, Power, BrokerInfrastructure) and TermService, LanmanServer, LanmanWorkstation, Netlogon, NTDS, DNS, Kdc. Refuse disabled services.
Restart it (or start it if stopped), wait up to 60 seconds for Running, then start the dependent services (direct and indirect) that were running before.
If the restart fails, still try to start those dependent services, then fail the run with the restart error and the name of any dependent service that did not come back.
Print before and after state. Windows PowerShell 5.1 compatible. No authorization policy.
SuperIT confirms the brief, drafts the script, validates it and creates the action as a draft. Review the script, then publish it under Workflows → Actions.
Or build it yourself
Create the action under Workflows → Actions with the PowerShell runtime and these inputs, then set it to run on a device.
| Name | Type | Required | Example |
|---|---|---|---|
expected_hostname |
string | Yes | SRV-FS01 |
service_name |
string | Yes | Spooler |
On a device, the script runs in Windows PowerShell as SYSTEM with $ErrorActionPreference = "Stop" already set. Inputs arrive as strings in $ctx.inputs. Invoke-ServiceRestart at the top lists the steps in order; each step is a small function below it, and the last line runs it. $result is set as $script:result, because a plain $result = inside a function stays local and never reaches the Result tab.
# Restarts (or starts) one Windows service and the running services that depend on it,
# refusing blocked and disabled services, and prints the state before and after.
function Invoke-ServiceRestart {
param($ExpectedHostname, $ServiceName)
Assert-TargetDevice -ExpectedHostname $ExpectedHostname
Assert-NotDomainController
$name = Assert-Inputs -ServiceName $ServiceName
$svc = Get-TargetService -Name $name
$startMode = (Get-CimInstance Win32_Service -Filter "Name='$($svc.Name)'").StartMode
Assert-RestartAllowed -Service $svc -StartMode $startMode
$before = "$($svc.Status)"
$dependents = @(Get-RunningDependent -Service $svc)
Write-Output '=== SERVICE RESTART ==='
Write-Output "Before: $($svc.DisplayName) [$($svc.Name)] is $before (startup: $startMode)"
if ($dependents) { Write-Output "Running dependants: $($dependents -join ', ')" }
# A forced restart can stop dependants before the target fails to come back, so try to bring them up either way.
$errors = @()
try { Restart-TargetService -Service $svc } catch { $errors += "$_" }
try { Start-Dependent -Names $dependents } catch { $errors += "$_" }
if ($errors) { throw ($errors -join ' ') }
$svc.Refresh()
Write-Output "After: $($svc.DisplayName) is $($svc.Status)"
# $result is read after the script ends, so set it at script scope, not in this function.
$script:result = @{ service = $svc.Name; display_name = $svc.DisplayName; before = $before; after = "$($svc.Status)"; dependants = $dependents }
}
function Assert-TargetDevice {
param($ExpectedHostname)
$expected = ("$ExpectedHostname".Trim() -split '\.')[0]
if (-not $expected) { throw "expected_hostname is required: enter this device's computer name." }
if ($expected -ne $env:COMPUTERNAME) { throw "This run reached '$env:COMPUTERNAME', not '$expected'. Nothing was changed." }
}
function Assert-NotDomainController {
# ProductType 2 is a domain controller, where most service restarts affect the whole domain.
if ((Get-CimInstance Win32_OperatingSystem).ProductType -eq 2) {
throw "$env:COMPUTERNAME is a domain controller. Restart services there by hand."
}
}
function Assert-Inputs {
param($ServiceName)
$name = "$ServiceName".Trim()
if (-not $name) { throw "service_name is required." }
if ($name -match '[*?\[\]]') { throw "Give one exact service name; wildcards aren't allowed." }
$name
}
function Get-TargetService {
param([string]$Name)
$svc = Get-Service -Name $Name -ErrorAction SilentlyContinue
if (-not $svc) { $svc = Get-Service -DisplayName $Name -ErrorAction SilentlyContinue }
if (-not $svc) { throw "No service called '$Name' on $env:COMPUTERNAME." }
$svc
}
function Assert-RestartAllowed {
param($Service, [string]$StartMode)
$blocked = 'RpcSs','RpcEptMapper','DcomLaunch','LSM','SamSs','Winmgmt','EventLog','PlugPlay','Power','BrokerInfrastructure',
'TermService','LanmanServer','LanmanWorkstation','Netlogon','NTDS','DNS','Kdc'
if ($Service.Name -like 'SuperIT*' -or $blocked -contains $Service.Name) {
throw "Restarting '$($Service.DisplayName)' is blocked by this action."
}
if ($StartMode -eq 'Disabled') { throw "'$($Service.DisplayName)' is disabled. Enable it before restarting it." }
}
function Get-RunningDependent {
param($Service, [hashtable]$Seen = @{})
# Walk the whole chain: Restart-Service -Force also stops services that depend on the dependants.
foreach ($dep in $Service.DependentServices) {
if ($Seen.ContainsKey($dep.Name)) { continue }
$Seen[$dep.Name] = $true
if ($dep.Status -eq 'Running') { $dep.Name }
Get-RunningDependent -Service $dep -Seen $Seen
}
}
function Restart-TargetService {
param($Service)
try {
if ($Service.Status -eq 'Running') { Restart-Service -Name $Service.Name -Force } else { Start-Service -Name $Service.Name }
} catch {
throw "Failed to restart '$($Service.DisplayName)' on $($env:COMPUTERNAME): $_"
}
try {
$Service.WaitForStatus('Running', [TimeSpan]::FromSeconds(60))
} catch {
$Service.Refresh()
throw "'$($Service.DisplayName)' didn't reach Running within 60 seconds. It is $($Service.Status)."
}
}
function Start-Dependent {
param([string[]]$Names)
$failed = @()
foreach ($dep in $Names) {
try {
if ((Get-Service -Name $dep).Status -ne 'Running') {
Start-Service -Name $dep
Write-Output "Started dependant: $dep"
}
} catch {
$failed += $dep
Write-Output "Could not restart dependant $dep`: $_"
}
}
if ($failed) { throw "These dependants did not come back: $($failed -join ', '). Start them by hand." }
}
Invoke-ServiceRestart -ExpectedHostname $ctx.inputs.expected_hostname -ServiceName $ctx.inputs.service_name
Add any other services you never want restarted to $blocked in Assert-RestartAllowed, such as your antivirus, backup agent, SQL Server or Exchange services. Assert-TargetDevice compares the first part of expected_hostname with $env:COMPUTERNAME, so SRV-FS01 and SRV-FS01.contoso.local both match.
Test it
You can't use Test on an action that runs on a device. (On a cloud-sandbox action, Test isn't a dry run either: it runs the script for real and skips approvals.) Every device run is real, so use a test device and a harmless service.
- Publish the action under Workflows → Actions.
- Select Run, enter the test device's computer name in
expected_hostnameandSpooler(or another service that is safe to restart) inservice_name, and under Run target pick that device. Select Run action. - Check Stdout shows the service as
Runningafter the restart. - Run it with
RpcSsand confirm the run fails with "blocked by this action". - Run it with a different name in
expected_hostnameand confirm it fails with "Nothing was changed".
Use it
- In a chat. Select the device and ask "Restart the print spooler on this device". SuperIT asks whether to run on the device and fills in the inputs. Check
expected_hostnameis the device you selected. - From the app. Open the action, select Run, enter the device's name and the service, pick the same device under Run target, and select Run action.
- Not from tickets automatically. Investigations can recommend it when they find a stopped service, but don't switch on Run automatically for this action. A run started from a ticket's Recommended actions panel goes to the action's default device or your team's designated device, not the device named in the ticket. To act on a ticket, open a chat, select the device and run it there.
- For clients. MSPs can publish it to managed teams.
Always pick the device. If no device is chosen, SuperIT runs a device action on the action's default device or your team's designated device, and expected_hostname stops the run if that's the wrong one.
Variations
- Copy for end users. Make
service_namea select input listing only the services you allow, keep the block-list and both device checks, and link a policy with Anyone → Require approval from an engineer group, such assuperit:engineers. Before approving, check which device the run is for. End users see only whether the run succeeded. - Lock it to one service: replace the input with a fixed name, for example "Restart line-of-business app service".
- Collect the last 10 events from the service's provider in the System log before restarting, to keep the evidence.
- See Clear a stuck print queue for the spooler-specific version.