PowerShell Queries for Failed Services on Remote Machines
Get-WmiObject solves problems that Get-Service can't ... yet
January 22, 2008
PowerShell 1.0's Get-Service cmdlet isn't designed to access remote systems. The Get-StoppedAutomaticService script uses the Get-WmiObject cmdlet to work around this limitation and find failed services throughout a network. |
Monitoring critical services is an everyday task for many system administrators, but the large number of services running on systems makes manual inspection tedious. PowerShell's Get-Service cmdlet doesn't yet allow you to examine services remotely, but you can see remote service information with the Get-WmiObject cmdlet. I've written a simple script to automate examining services for startup problems across an entire network. I'll show you how to use the Get-StoppedAutomaticService script, then discuss how it works and why PowerShell's Get-Service cmdlet isn't helpful for this job.
Using Get-StoppedAutomaticService
Listing 1 shows the Get-StoppedAutomaticService script, which you can download by clicking the Download the Code Here button at the top of the page. Here's the abstract form of the command-line usage for the script:
Get-StoppedAutomaticService ] [[-Exclude] ] [-Credential ] [-Synopsis]
You can get this command-line usage yourself by running the script with the Synopsis parameter as follows"" href="?topic=
Get-StoppedAutomaticService -Synopsis
PowerShell isn't case-sensitive, so you can enter the command name as get-stoppedautomaticservice, for example.
Let's look at some examples of how Get-StoppedAutomaticService works. If you run the script with no arguments, it automatically checks the local system for all services that are set to start automatically (including delayed-start services) and that aren't currently running. The script returns WMI Win32_Service objects for each of these services.
The output you see onscreen looks something like this:
ExitCode : 0Name : ehstartProcessId : 0StartMode : AutoState : StoppedStatus : OK
The only useful element here is the name. I'll discuss improving the output shortly, but first let's look at the rest of the command-line arguments.
To run the script against the computer named Server01, just use Server01 as the argument of the command-line parameter ComputerName:
Get-StoppedAutomaticService -ComputerName:Server01
Note that the parameter name is optional; you can also use just
Get-StoppedAutomaticService Server01
In some cases, you'll get errors when running the script, easily identifiable in PowerShell because they're lines of red text. The two errors you're most likely to encounter typically indicate specific network-related issues.
One of the error messages is Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA). This indicates that the Remote Procedure Call service on the remote machine couldn't be contacted and occurs if the remote machine doesn't have Windows Management Instrumentation (WMI) running, has RPC problems, isn't a Windows system, or—particularly if the error takes a while to show up—that the remote machine isn't online.
The other common error message is Get-WmiObject : Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)). This suggests that your credentials aren't sufficient for accessing the remote system. If you have an account that has sufficient permissions for the query, there's a simple workaround for this problem in the form of PowerShell's Get-Credential command and the script's Credential parameter.
First run the PowerShell Get-Credential command to put the credentials you'll use for remotely accessing machines into a variable. If you're using the variable $cred for the credentials, just type this command at the PowerShell prompt:
$cred = Get-Credential
PowerShell will prompt you to enter credentials into a dialog box like that shown in Figure 1. To supply domain or computer-specific credentials, use the standard form of domainusername or computerusername for the username; if you happen to use identically named local accounts on each PC, you can enter just the username. PowerShell will then retain the credentials as an encrypted SecureString.
To connect to Server01 by using these stored credentials, enter the command
Get-StoppedAutomaticService Server01 -Credential $cred
What if you want to run the Get-StoppedAutomaticService script against an entire collection of computers? One scenario might be that you have a list of computer names in the C:tmpcomputers.txt file and want to run the script for each computer. You can get the contents of a file by using PowerShell's Get-Content command and run the script against each computer in turn, by using ForEach-Object, as follows:
Get-Content c:tmpcomputers.txt "> ForEach-Object ` {Get-StoppedAutomaticService -ComputerName:$_}
If you already have the computer names in a variable $pc, you could do this instead:
$pc | ForEach-Object {Get-StoppedAutomaticService -ComputerName:$_}
You can add custom credentials as well:
$pc | ForEach-Object {Get-StoppedAutomaticService -ComputerName:$_ -Credential $cred}
You can narrow the information the script returns by explicitly excluding some services that you expect to not be running. The Exclude parameter works for the Get-StoppedAutomaticService script like it does for most PowerShell cmdlets. Exclude accepts multiple comma-delimited values and can include wildcards. For example, you could exclude the ehstart service and all services whose names begin with the letter B:
Get-StoppedAutomaticService -Exclude:ehstart,b*
Formatting the Output
PowerShell has output-formatting defaults for most common object types. Sometimes these defaults hide information you really need and provide information that's unnecessary and just clutters the screen. This is the case for the Win32_Service display defaults. As I mentioned earlier, the only useful information in the default display is the service name. If you work with multiple machines, you need information that's not displayed by default, such as the remote computers' name.
You can use PowerShell's Get-Member command as follows to see a list of the Win32_Service object's properties:
Get-WmiObject Win32_Service| Get-Member -MemberType:Properties
Win32_Service's SystemName property is the computer the data came from, so you definitely want to see that. The Name property is the service name, so of course you need that. You might also want the Description, which provides information about the service. You can use PowerShell's Format-Table command to receive this data in table format:
Get-StoppedAutomaticService | Format-Table SystemName,Name,Description -Wrap
Figure 2 shows sample output from a Windows Vista Ultimate system. You can also just tack the formatting command I showed above onto the end of the command line for querying a list of computers, like this:
$computers | ForEach-Object {Get-StoppedAutomaticService -ComputerName:$_} | Format-Table SystemName,Name,Description -Wrap
How the Script Works
Get-StoppedAutomaticService is shown in Listing 1, as I mentioned; let's take a closer look at the details, including some general aspects of how scripts work that you might not know about.
The code at callout A in Listing 1 declares the parameters for the script. Parameters are named arguments used within the script, essentially identical to those used as arguments of functions in scripting languages such as VBScript and JScript. Unlike many scripting languages, PowerShell allows a script to explicitly declare a data type for an argument; [string] means string data, and [string[ means the data is an array of strings. A script can also set a default value for an argument when the user doesn't explicitly provide a value. For example, if the user doesn't name a computer, Get-StoppedAutomaticService automatically sets the -ComputerName parameter to a period (.), which is what WMI uses to refer to the local system.
The code at callout B returns the command-line syntax for the script. Unfortunately, PowerShell's support for automatically generating script help information is still much weaker than that provided by Windows Script Host (WSH). However, PowerShell does make some information available within a script block through a $MyInvocation variable. The script determines its own name by using $MyInvocation.MyCommand.Name; this ensures that if you rename the script, the synopsis correctly shows the new name.
The code at callout C gets an initial set of services by using the Get-WmiObject cmdlet's Query parameter. The if statement simply decides whether to call Get-WmiObject with or without explicit credentials based on whether you supplied credentials on the script's command line. I could have used Get-WmiObject Win32_Service and then filtered the results to display only nonrunning automatic services, but it's a bit faster to have WMI return just the desired set. This approach also helps minimize the amount of data transferred over the network.
The code at callout D demonstrates how you can set up an Exclude filter in a script. The initial if($Exclude) makes PowerShell skip this section if you didn't use -Exclude on the command line for the script. If you did, for each value supplied to -Exclude, PowerShell looks through the current list of services and attempts wildcard matching of the exclusion to the service name. Matching services are dropped from the list, and the possibly reduced list of services will be put back in the $services variable.
In callout E, the script returns the $services entries. Unlike most Windows scripting languages, PowerShell doesn't use any special statement such as a return keyword when returning a value from a script. When PowerShell evaluates an expression such as a variable name, if the output from the expression isn't used, the output is automatically sent to the PowerShell pipeline.
One important final point is what I omitted from this script. Although I talked above about output formatting, I didn't integrate it into the script. I certainly could have; one simple way is to replace the current callout E with the following statement:
$services | Format-Table SystemName,Name,Description -Wrap
However, the omission was intentional. As the script is written, it returns a collection of Win32_Service objects with all of the associated data. You can then manipulate this data elsewhere as you see fit. If I included the formatting statement, the script would return a set of PowerShell-format objects. Extra information such as error control, service path, and account start name would have been stripped from the output objects. The script would have been useful for onscreen display and nothing else.
Why Get-Service Doesn't Work Remotely
As I mentioned at the start, PowerShell's built-in cmdlet for enumerating services, Get-Service, isn't suitable for checking multiple machines for one simple reason: Get-Service looks only at local services. In fact, in PowerShell 1.0, the only significant cmdlet that allows remote access is Get-WmiObject, which is why I used it in Get-StoppedAutomaticService.
Get-Service's lack of remote capabilities might appear to be a glaring oversight, but it's really just a temporary situation that will probably be remedied in PowerShell 2.0. "Remoting" has always been part of the plan for PowerShell, but when Microsoft released PowerShell 1.0, the remoting infrastructure still needed work to make remote running of scripts and commands transparent to users and the same across all cmdlets. Microsoft decided to go ahead and release PowerShell 1.0 after the beta community generally agreed that the existing feature set made it a valuable administrative tool.
The existence of Get-WmiObject played a role in Microsoft's decision that PowerShell 1.0 was ready to ship—it already could perform tasks that other cmdlets should be able to do in the future. WMI includes remote access and the associated security infrastructure, so creating a Get-WmiObject cmdlet that worked nicely for many remote administrative tasks wasn't difficult. For now, it gets the job done. But yes, Get-Service should be able to work remotely—and probably will when the next version of PowerShell is released.
About the Author
You May Also Like