PowerShell Problem Solver: Use PowerShell to Test if Windows Services are Running

Most of the time with my PowerShell Problem Solver articles I am addressing a problem I’ve come across in forums or through social media channels. But today’s problem is one of my own. I usually keep my computer running for days at a time, but I decided to reboot to start the day fresh. I then tried to run a WMI query using Get-CimInstance but got an error that the computer couldn’t be contacted. After scratching my head for a moment, I decided to check the WinRM service.

The CIM cmdlets use the WSMan protocol to query WMI, which means that PowerShell remoting must be enabled. Once enabled, the assumption is that the WinRM service is running. Although the WinRM service is configured to start automatically, it turns out that my service was not. Starting the service with Start-Service solved the problem, but I thought this is a potentially larger issue: how can I determine services that should be running that are not?
If you are new to PowerShell, you might think to first use Get-Service. You might even be smart and display all the properties.

When we use Get-Service, you can see that there's a problem, which you can see in the following screenshot.
Using the Get-Service cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
Using the Get-Service cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
More accurately, there;s something that you don't see in the screenshot. There's nothing that indicates the service's start mode, e.g. automatic, manual or disabled. The first lesson for today is that if there's a limitation in the underlying .NET Framework class, then there will be a limitation in the related cmdlets. Fortunately, there's almost always an alternative. In this situation, we can also use WMI to discover information about services. We'll use the Win32_Service class. It's as easy as this:
Using the Get-CIMinstance cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
Using the Get-CIMinstance cmdlet in Windows PowerShell. (Image Credit: Jeff Hicks)
As you can see in the output, there are properties to indicate the start mode and also the current state. If you are new to PowerShell and WMI, then you might be tempted to create an expression like this:
Don't do that. Although the command will work and you will get a list of stopped services that are set to automatically start, this is a bad habit to get into. This is an example of late filtering. With this type of filtering, the Where-Object part of the expression can't really run until the first part finishes. Although it isn't that big a deal with services, you might run other queries that could return thousands of objects and you can't filter until that step completes.

Instead, the recommended option is to use a cmdlet's filter parameter whenever possible. You might also see parameters like include or exclude, which also provide a type of filtering. When using a filtering parameter, objects are filtered as they are collected and often at the source. If you were querying for services across 100 machines, the WMI service on each remote server will send you a filtered collection of service objects. This is much more efficient than getting all servers from 100 servers and then filtering at your end.
To filter with WMI, you can use the query parameter. WMI filters look like SQL-type queries. The class name is like a table. WMI queries also use the legacy operators, not the PowerShell operators.
022315 1912 PowerShellP3
You need to quote any string comparisons as I have done. I've also changed my state query to find anything that isn't running. Services technically have other states beyond "Stopped' and "Running" so I want to make sure I don't miss any other potential issues.
Or, you can use the –Filter parameter, which takes a string. The value is essentially the where portion of your WMI query. This command will produce the same results.
That's all I really need to run. Some of the stopped services are OK, but others I might decide to manually start. But I don't want to have to try and type all of that, so I'll spend a few minutes and create a re-usable tool that I can include in my PowerShell profile to make it much easier to find services that should be running but are not.
Function Test-Service {
Test for services that should be running but are not.
This command uses WMI via Get-CimInstance to query for all services that are configured to start automatically but are not running. The default is to get all services that match this filter but you can also specify a service name.
.PARAMETER Computername
The name of the computer to query.
The name of a service to query. You can use the * wildcard.
PS C:\> Test-Service
PSComputerName : WIN81-ENT-01
Name           : sppsvc
Displayname    : Software Protection
State          : Stopped
StartMode      : Auto
PSComputerName : WIN81-ENT-01
Name           : wuauserv
Displayname    : Windows Update
State          : Stopped
StartMode      : Auto
PS C:\> Test-Service -Computername chi-dc01,chi-dc02,chi-core01 | Format-Table -GroupBy PSComputername -property Displayname,Name,State
   PSComputerName: chi-dc01
Displayname                                  Name                           State
-----------                                  ----                           -----
Microsoft .NET Framework NGEN v4.0.30319_X86 clr_optimization_v4.0.30319_32 Stopped
Microsoft .NET Framework NGEN v4.0.30319_X64 clr_optimization_v4.0.30319_64 Stopped
Software Protection                          sppsvc                         Stopped
   PSComputerName: chi-dc02
Displayname          Name    State
-----------          ----    -----
Software Protection  sppsvc  Stopped
VMware Tools Service VMTools Stopped
   PSComputerName: chi-core01
Displayname         Name           State
-----------         ----           -----
Remote Registry     RemoteRegistry Stopped
Software Protection sppsvc         Stopped
NAME        :  Test-Service
VERSION     :  1.0
LAST UPDATED:  2/23/2015
AUTHOR      :  Jeff Hicks (@JeffHicks)
Learn more about PowerShell:
Essential PowerShell Learning Resources
**************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .LINK Get-CimInstance .INPUTS Strings .OUTPUTS Custom CIM Instance #> [cmdletbinding()] Param ( [Parameter(Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullorEmpty()] [Alias("cn","pscomputername")] [string[]]$Computername = $env:Computername, [string]$Name ) Begin { Write-Verbose "Starting $($MyInvocation.Mycommand)" #define a base query $Query = "startmode = 'Auto' AND state <> 'running'" if ($Name -match "\*") { #if $Name contains * wildcards, replace with WMI % wildcard $Name = $Name.Replace("*","%") $query+= " AND name LIKE '$Name'" } elseif ($Name) { #otherwise, if there is a $Name value, append it to the query $query+= " AND name = '$Name'" } Write-Verbose "Filtering: $query" #parameter hashtable to splat against Get-CimInstance #the computername will be set in the Process scriptblock $paramHash = @{ ClassName = 'Win32_Service' ComputerName = $Null Filter = $query ErrorAction = 'Stop' } } #begin Process { foreach ($computer in $Computername) { Write-Verbose "...on $($Computer.ToUpper())" $paramHash.Computername = $computer Try { Get-CimInstance @paramHash | Select PSComputername,Name,Displayname,State,StartMode } #Try Catch { Write-Warning "Failed to get service information from $($computer.toUpper())" Write-Warning $_.exception.message } #catch } #foreach computer } #process End { Write-Verbose "Ending $($MyInvocation.Mycommand)" } #end } #end function #define an alias for this function Set-Alias -Name tsvc -Value Test-Service

Because WMI can query remote computers, I included a Computername parameter. You might also want to check a specific service so there is a parameter to narrow your filter even further. I wanted the option to include a wildcard with –Name so that you can use * in the name. But, WMI queries don’t recognize * as a wildcard, it uses the % symbol. This means, if there is an * in the name value, I need to replace it with a %.

The * is a regular expression character, so I need to escape it in the –Match expression. WMI also uses the LIKE operator when using a wildcard.

There is one drawback to this command. If WinRM isn't running, then it will fail.
An error is returned because WinRM isn't running. (Image Credit: Jeff Hicks)
An error is returned because WinRM isn't running. (Image Credit: Jeff Hicks)
I suppose I could add a test in my function to verify the service is running and start it if it isn't. But then I might be overlooking some other problem. So I'll manually start WinRM and try again.
Much better.
022315 1912 PowerShellP5
My function can query a collection of computer names, and you can pipe names to it as well. If for some reason you need to use Get-WMIObject, the syntax should be identical to Get-CimInstance. You'll simply need to replace commands in the function.
Once you solve a problem, I encourage you to take the time to record your solution in a script or function, because you will most likely run into the problem again. Being able to refer back to a simple command makes your life much easier.