Recently, I was asked to setup a scheduled task or job to restart specific services on certain servers each night and while that’s simple, how do you know for sure the services were indeed restarted? One way is to determine how long a service has been running or when they were started. The dilemma is the necessary information isn’t available using the Get-Service cmdlet or with CIM or WMI using the Get-CimInstance or Get-WmiObject cmdlets with the Win32_Service class.
The good news is that every Windows service that’s running has an underlying process and the start time of a process can be determined with either the Get-Process cmdlet or the WMI Win32_Process class. All you have to do us match up the services to the corresponding process.
Get-Service doesn’t include the process id so I had to resort to using WMI to retrieve the service information so the process id can be retrieved. A PowerShell one-liner can be used to retrieve the information I was looking for.
1 2 3 4 5 6 7 8 | Get-CimInstance -ClassName Win32_Service -PipelineVariable Service | ForEach-Object { Get-Process -Id $_.ProcessId | Select-Object -Property @{label='Status';expression={$Service.State}}, @{label='Name';expression={$Service.Name}}, @{label='DisplayName';expression={$Service.DisplayName}}, StartTime } |
That one-liner could simply be run inside of Invoke-Command to retrieve the same information from remote systems, but I decided to turn it into a function instead. My first iteration was simple:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | function Get-MrService { [CmdletBinding()] param ( [string]$Name ) $Params = @{} if ($PSBoundParameters.Name) { $Params.Filter = "Name = '$Name'" } $Services = Get-CimInstance -ClassName Win32_Service @Params foreach ($Service in $Services) { $Process = Get-Process -Id $Service.ProcessId [pscustomobject]@{ Status = $Service.State Name = $Service.Name DisplayName = $Service.DisplayName StartTime = $Process.StartTime } } } |
When adding remoting within the function itself, I ran into a problem where the services were retrieved from the remote system, but it was trying to match them up to local processes so a little rework was required.
In the end, I decided to use CIM sessions for remote connectivity so the part of the function which previously used the Get-Process cmdlet was changed to use Get-CimInstance with the Win32_Process class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | #Requires -Version 3.0 function Get-MrService { <# .SYNOPSIS Gets the services on a local or remote computer. .DESCRIPTION The Get-MrService function gets objects that represent the services on a local computer or on a remote computer, including running and stopped services. You can direct this function to get only particular services by specifying the service name of the services. .PARAMETER Name Specifies the service names of services to be retrieved. Wildcards are permitted. By default, this function gets all of the services on the computer. .PARAMETER CimSession Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. .EXAMPLE Get-MrService -Name bits, w32time .EXAMPLE Get-MrService -CimSession (New-CimSession -ComputerName Server01, Server02) -Name Win* .INPUTS None .OUTPUTS PSCustomObject .NOTES Author: Mike F Robbins Website: http://mikefrobbins.com Twitter: @mikefrobbins #> [CmdletBinding()] param ( [ValidateNotNullOrEmpty()] [string[]]$Name = '*', [Microsoft.Management.Infrastructure.CimSession[]]$CimSession ) $ServiceParams = @{} if ($PSBoundParameters.CimSession) { $ServiceParams.CimSession = $CimSession } foreach ($n in $Name) { if ($n -match '\*') { $n = $n -replace '\*', '%' } $Services = Get-CimInstance -ClassName Win32_Service -Filter "Name like '$n'" @ServiceParams foreach ($Service in $Services) { if ($Service.ProcessId -ne 0) { $ProcessParams = @{} if ($PSBoundParameters.CimSession) { $ProcessParams.CimSession = $CimSession | Where-Object ComputerName -eq $Service.SystemName } $Process = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = '$($Service.ProcessId)'" @ProcessParams } else { $Process = '' } [pscustomobject]@{ ComputerName = $Service.SystemName Status = $Service.State Name = $Service.Name DisplayName = $Service.DisplayName StartTime = $Process.CreationDate } } } } |
I also ran into a problem where all of the systems were being queried for the process id of every service instead of just the ones running on that particular system. I narrowed down which CIM sessions were being queried which resolved that problem.
1 2 3 4 5 6 7 8 | Get-MrService -Name msdtc, bits, w32time, winrm | Format-Table -AutoSize $CimSession = New-MrCimSession -ComputerName dc01, sql08, sql14 Get-MrService -CimSession $CimSession -Name msdtc, bits, w32time, winrm | Sort-Object -Property ComputerName, Name | Format-Table -AutoSize |
Although this function requires PowerShell 3.0 or higher on the system it’s being run from, since I chose to use CIM sessions for remote connectivity instead of simply wrapping the commands inside of PowerShell remoting, I’m able to target machines using either WSMan or DCom. For more information on that topic, see my “Targeting Down Level Clients with the Get-CimInstance PowerShell Cmdlet” blog article.
The previous example takes advantage of my New-MrCimSession function which automatically determines if a system is able to use WSMan otherwise it automatically falls back to using DCom. More information about that function can be found in my “PowerShell Function to Create CimSessions to Remote Computers with Fallback to Dcom” blog article.
The Get-MrService function shown in this blog article can be downloaded from my PowerShell repository on GitHub.
µ
Hi mike, do you handle for services that do not stop and need to be killed? I was just tasked with something similar and always like to see how people handle certain situations.
I often times run into that exact scenario and most admins would tell you the server is going to have to be rebooted, but the underlying process can usually be killed which will stop the service. I have a blog article that goes into more detail: PowerShell Function to Stop a Windows Service with a Status of Stopping.
outstanding Sir