Find Required Services with PowerShell

Here’s a little PowerShell tidbit to get the status of all the required services. That is, the services that other services depend upon. When using Get-Service, this is the RequiredServices property which will be a collection of service objects.


get-service | where {$_.status -eq "running"} | select -expand RequiredServices

I’m intentionally omitting and results so that you can try these command out for yourself. Next we need to filter out the duplicates. You might think this would work:


get-service | where {$_.status -eq "running"} | select -expand RequiredServices | select Name -unique | sort name

And it does, but the selection is case-sensitive and you’ll see that some names are a mix of cases. If you just want a list of names, then this will work:


get-service | where {$_.status -eq "running"} | select -expand RequiredServices | select DisplayName -unique | sort Displayname

But I want to also see the status of the required services so I can see if any are not running. I need to use the service name because some of the required services are kernel level and Get-Service won’t retrieve them by their displayname. So the challenge comes back to the case issue with the service name. The answer of course is to make them all the same case.


get-service | where {$_.status -eq "running"} | select -expand RequiredServices | foreach {$_.name.tolower()} | sort | get-unique | get-service

I turn each service name into lower case, sort because I like organized results, get the unique names and then pipe each name back to get-service. If I wanted to I could pipe this to Where-Object to only get stopped services or display other information for these required services.

This is a pretty cool example of using the PowerShell pipeline because I’m starting and ending with Get-Service and processing objects through the pipeline to meet my objective, without any scripting or text parsing.

Ping a Service with PowerShell

The other day I came across a PowerShell question on StackOverflow about testing if a service was running on a group of machines.This sparked an idea for a tool to “ping” a service, in much the same way we ping a computer to see if it is up and running, or at least reachable. It is not difficult to use Get-Service, or even WMI, to connect to a service on a remote machine. But I thought it might be nice to have a few extras so I came up with Ping-Service.

I suppose I could have called it Test-Service, but I liked the idea of pinging a service. Let me layout the core part then I’ll touch on a few parts.


Function Ping-Service {

[cmdletbinding()]

Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter the name of a service")]
[ValidateNotNullorEmpty()]
[string]$Name,

[Parameter(Position=1,ValuefromPipeline=$True)]
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,

[switch]$Quiet
)

Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"
Write-Verbose "Pinging service $name"
}

Process {
Foreach ($computer in $computername) {
Write-Verbose "Testing computer $computer"

#get service from each computer
$measure=Measure-Command {$service=Get-Service -Name $Name -ComputerName $Computer -ErrorAction SilentlyContinue -ErrorVariable ev}
$end=Get-Date

if ($Quiet -and $service.status -eq "Running") {
#if quiet only return True if service is running
Write-Output $True
}
elseif ($Quiet) {
#status is something other than Running
Write-Output $False
}
elseif ($ev) {
$msg="Error with {0} service on {1}. {2}" -f $Name,$Computer,$ev[0].Exception.message
Write-Verbose $msg
Remove-Variable ev

New-Object -TypeName PSObject -Property @{
Time=(Get-Date -displayhint Time);
Computername=$Computer;
Name=$name;
Displayname=$null;
Status="Unknown";
Response=$measure.TotalMilliseconds;
Reply=$False
} | Select Time,Computername,Name,Displayname,Status,Response,Reply
}
else {
#otherwise, return a custom object
$service | Select @{Name="Time";Expression={(Get-Date -displayhint Time)}},
@{Name="Computername";Expression={$_.Machinename}},
Name,Displayname,Status,
@{Name="Response";Expression={$measure.TotalMilliseconds}},
@{Name="Reply";Expression={
if ($_.Status -eq "Running") {$True} else {$False}
}}
}
} #foreach
}

End {
Write-Verbose "Ending $($myinvocation.mycommand)"
}

} #function

The function takes service and computer names as parameters. You have specify a service name but the computername defaults to the local computer. By default the function writes a custom object to the pipeline which I’ll get to in a moment. But you can also use -Quiet which will return True if the service is running and false for anything else, including errors connecting to the service or computer. This allows you to use the command in an If statement.


PS C:\> if (ping-service wuauserv -comp Quark -quiet) {"ok"} else {"down"}

You can pipe computernames to the function but you have to test for the same service on each. I figured this was the most likely usage scenario. I decided to use Get-Service instead of Get-WMIObject. True, WMI can return a bit more information, but I’ve always found WMI has a bit more overhead and Get-Service runs a little quicker.


$service=Get-Service -Name $Name -ComputerName $Computer -ErrorAction SilentlyContinue -ErrorVariable ev

Normally, I would do something like is in a Try/Catch block. But instead I want the function to keep going which is why I set the erroraction parameter to SilentlyContinue. But, if an exception occurred, I can store it in $ev. Now I have something I can test for later in the function.


elseif ($ev) {
$msg="Error with {0} service on {1}. {2}" -f $Name,$Computer,$ev[0].Exception.message
Write-Verbose $msg

Assuming I get a response and I didn’t specify -Quiet, then a custom object is written to the pipeline with service information, a time span that shows how long the Get-Service command took to complete and a boolean indicating if there was a “reply”.


Time : 2/7/2012 10:32:05 AM
Computername : jdhit-dc01
Name : wuauserv
DisplayName : Automatic Updates
Status : Running
Response : 4.4942
Reply : True

The function allows me to run expressions like this:


PS C:\> "serenity","jdhit-dc01","quark" | ping-service wuauserv | Where {!$_.Reply} | Select Computername,Status,Reply

Computername Status Reply
------------ ------ -----
quark Unknown False

Download Ping-Service and let me know what you think.

ByValue, I Think He’s Got It

Recently I responded to an email from a student seeking clarification about the difference between ByValue and ByProperty when it comes to parameter binding. This is what makes pipelined expressions work in Windows PowerShell. When you look at cmdlet help, you’ll see that some parameters accept pipeline binding, which is what you are looking for. Often this means you don’t need to resort to a ForEach-Object construct. Here’s an example.

ByValue means if I see something I’ll use it. ByProperty means I’m looking for a specific object property. For example, Get-Service uses the Name property to find a service.

This parameter accepts both types of binding. This means you can do this:

This is an example of byValue. Get-Service sees something in the pipeline and assumes it is a service name. However, for Get-Service this would also work.

$all[0] is a service object with name property which when piped to Get-Service, finds the property and binds to it. Here’s another example that shows binding by property name can be from any object, not just a service object.

This is a one line expression that leverages the pipeline and uses some helpful (I hope) techniques. The first part using Get-Content retrieves the list of computer names from the text file. Because the text file might have blank lines and some computers might be offline, each name is piped to Where-Object which will only pass on names that exist (skipping blanks) and that can be pinged. To speed things up I’m only sending 2 pings. Now the fun part. I could use ForEach-Object and pass $_ as the value for -Computername. But according to help, this parameter accepts binding by property name.

So I’ll take the computername value coming from Where-Object and use a hash table with Select-Object to define a new “property” name, called Computername. I also take the liberty of trimming off any leading or trailing spaces, just in case. Now I have an object with a property called Computername that is piped to Get-Service which binds on the computername property. The rest is merely formatting.

Look for opportunities to bind by parameter, which means reading cmdlet help which is a good habit to have regardless.