Regular expressions go all the way back to 1956. I think I first saw them in the PERL language; but today in 2015, they are very useful in Powershell, C# and most every language.

Regular Expressions have three main purposes:
1. Validate if text conforms to a pattern
2. Capture (extract) a string (or series of strings) from another string
3. Replace a pattern with a new text.

In this blog, I’ll be discussing #2 on the above list. Here is the function I created.

Function GetCityStateFromKeyword([String] $keyword)
#Write-Host “GetCityStateFromKeyword”
$pattern = “in (.*?)($|.?\d{5}?.?)”
#the first parenthese is for capturing the cityState into the $Matches array
#the second parentheses are needed above to look for $ (which is end of line)
#or zip code following the city/state
$isMatch = $keyword -match $pattern
$returnCityState = “ERROR”
#Write-Host “GetCityStateFromKeyword RegEx `$Matches.Count=$($Matches.Count)”
if ($Matches.Count -gt 0)
$returnCityState = $Matches[1]

return $returnCityState

First, let’s look at some of my sample data:

best deals on appliances in Irving TX 75039 
best deals on appliances in Irving TX 75039 bestbuy 
best deals on appliances in Irving TX
best deals on appliances in Irving TX 75039-1234 
$myKeyword = "best deals on appliances in Irving TX 75039"
$cityState = GetCityStateFromKeyword($myKeyword)
Write-Host "City/State=$cityState" 

My goal was to extract (or capture) the city, state from each of the above lines, or return the string “ERROR” if not found. The result I want in any of the above is simply:

Irving TX 

Now, let’s look in detail at my pattern.

   $pattern = "in (.*?)($|.?\d{5}?.?)"

Parentheses have two purposes in Reg Ex:
1. Specify what to capture
2. Wrap around a list of alternatives, which are delimited by the pipe symbol.

.* means 0 or more characters, and adding the question mark to make it .*? makes it “non-greedy”. If you don’t specify the non-greedy operator (the question mark), then the .* might “suck up” too much text. So, (.*?) will capture my city state.

What does ($|.?\d{5}?.?) mean? Well first, \d stands for digit, and \d{5} means exactly 5 digits, i.e. the zip code. The ? mark unfortunately has two meanings, one as the non-greedy operator, and when seen like this .?, it means 0 or more characters (as compared to .* which means 1 or more characters). So the zip-code is optional, the space before and after the zip-code s optional.

NOTE: My requirement was only for US postal zip codes. I would have to change the logic to handle Canadian zip codes, which I believe are 6 characters and contain alphas.

Now let’s look at this line of code:

 $isMatch = $keyword -match $pattern 

$isMatch will be a boolean. $keyword is my parameter to which the RegEx pattern will be applied. -Match is the Powershell keyword, and we’ve already discussed the value of the $pattern variable above. -Match will return an array in the variable $Match. Each capture from the () provided in the pattern will be stored in an element of the $Matches array. Thus, my city/state is stored in $Matches[1] (array in Powershell are 1-based, not 0-based).

How to Get the Last Line of a File

Have you ever needed to read just the last line of a file (i.e. “the last row”) in Powershell? It’s easier than I thought. If you ask for the last several rows (as shown in the commented-out code in the example below, you will get back an array of rows. If you get just the last row, you will get it in a string variable.

$myFileName = "c:\Users\neal.walters\Documents\test.csv" 

#Example of how to get the last 3 rows 
#$LastThreeRowsArray = (Get-Content $myFileName)[-1 .. -3]

# Get last one row of file into variable 
$lastDataRow = (Get-Content $myFileName)[-1]

#parse the CSV data back into separate variables, one for each column 
$dataArray = $lastDataRow.Split(",") 
$lastRowDate = [DateTime] $dataArray[0]  #convert back to DateTime datatype 
$lastRowStatus = $dataArray[1] 
$lastRowAction = $dataArray[2] 
$lastRowKeyword = $dataArray[3] 

Write-Host "`$lastDataRow=$lastDataRow" 
Write-Host "Date=$lastRowDate" 
Write-Host "Status=$lastRowStatus" 
Write-Host "Action=$lastRowAction"
Write-Host "Keyword=$lastRowKeyword"

$currentDateTime = get-date

$dateTimeDiff = $currentDateTime  - $lastRowDate 
$minutesDiff = $dateTimeDiff.Minutes 
$totalMinutesDiff = $dateTimeDiff.TotalMinutes 
Write-Host "`$currentDateTime =$currentDateTime"
Write-Host "`$dateTimeDiff=$dateTimeDiff"
Write-Host "`$minutesDiff=$minutesDiff "
Write-Host "`$totalMinutesDiff=$totalMinutesDiff "

$maxAcceptableLapseMinutes = 15 
if ($totalMinutesDiff -gt $maxAcceptableLapseMinutes) 
       Write-Host "No video created for more than $maxAcceptableLapseMinutes minutes!" 
       # do so logic here to handle that issue 

Test Data File:

1/5/2015 8:51,Success,CreatedVideo,best appliances in Abilene TX
1/5/2015 8:51,Success,CreatedVideo,best appliances in Irving TX
1/5/2015 8:51,Failure,CreatedVideo,best appliances in Farmers Branch TX
1/5/2015 8:52,Success,CreatedVideo,best appliances in Abilene TX
1/5/2015 8:52,Success,CreatedVideo,best appliances in Boston MA
1/5/2015 8:52,Success,CreatedVideo,best appliances in West Newton MA 
1/5/2015 8:52,Success,CreatedVideo,best appliances in Boca Raton FL 


$lastDataRow=1/5/2015 8:52,Success,CreatedVideo,best appliances in Boca Raton FL 
Date=01/05/2015 08:52:00
Keyword=best appliances in Boca Raton FL 
$currentDateTime =01/05/2015 15:17:51
No video created for more than 15 minutes

Why would anyone want to get the last row of a file? I have a process that creates an mp4 video about every 1 to 1.5 minutes. When you do “Add-Content”, the data is written to the end of the file. (I show how I write data to this file in a previous blog: Logging Data Variables to a CSV File in Powershell) I want to write a separate process to monitor the progress by looking at the last row of the file. If no video is created for example, in 10 minutes, I know something has frozen up, and might need to kill and restart the process that creates the videos.

So the above code also shows how to compute the elapsed time, of the time the program runs to the last date in the CSV file. I ran the program at 3:17 pm, and the last date time in the CSV file was 8:52 this morning. Thus 6 hours, 25 minutes, and some-odd seconds has elapsed.

The above code also illustrates the why you might want to use the .TotalMinutes property instead of the .Minutes property. I want to know if more than 15 minutes have elapsed. Let’s suppose the elapse time was 1 hour and 7 minutes. I cannot just compare the 7 minutes to 15, I need to compare the 60+7 (or 67 minutes) to 15.

I found the base code for this here: If you want to know more about Base64, check out this
Wikipedia Entry

But I’m not a command-line guy, I’m a developer, and I like to have my code in a file where I can edit it, modify it, re-use it, and run it.

Function Base64Encode($textIn) 
    $b  = [System.Text.Encoding]::UTF8.GetBytes("Hello World")
    $encoded = [System.Convert]::ToBase64String($b)
    return $encoded    

Function Base64Decode($textBase64In) 
    $b  = [System.Convert]::FromBase64String($textBase64In)
    $decoded = [System.Text.Encoding]::UTF8.GetString($b)
    return $decoded

#Test Logic 
$base64Encoded = Base64Encode("Hello World") 
Write-Host "`$base64Encoded=$base64Encoded"

$base64Decoded = Base64Decode($base64Encoded) 
Write-Host "`$base64Decoded=$base64Decoded"

Here is the results of running the above code:

What is your password?: Hello World
$base64Decoded=Hello World

Now, how can we use this? Let’s take the original code, comment out everything below the “#Test Logic” comment, and save it as “Base64Functions.ps1”.

I wanted to obfuscate a simple password in a config file. Encoding is definitely NOT encryption. But if someone is standing over your shoulder, or even opens the Config File – it will not be obvious that the password is Base64 and they certainly couldn’t convert it in their head or even memorize it.

I found samples for how to update a standard XML Config File at this link:

XML Config File Before:


XML Config File After:

. \Base64Functions.ps1  #reference the function created earlier 

#prompt the user to interactively enter his/her password 
$password = Read-Host 'What is your password?'   

#encode it
$base64EncodedPassword = Base64Encode($password)
Write-Host "`$base64EncodedPassword=$base64EncodedPassword"

#store it in our config file in the proper place 
$configFilename = "c:\Users\neal.walters\Documents\GmailConfig.xml"
$xmlConfig = [xml](get-content $configFilename)

$obj = $xmlConfig.configuration.appSettings.add| where {$_.Key -eq 'GmailPassword'}
$obj.Value = $base64EncodedPassword

#we change the xml in memory, so now write it back to the disk file. 

Here is the results of running the above code (I typed in “Hello World” in response to the prompt.)

What is your password?: Hello World
$base64Decoded=Hello World

XML Config File After:

In a future blog, I can show you how I use this to send GMail (Google emails) from Powershell.

Powershell has the Add-Content cmdlet to append data to the end of a file. While it also has Export-CSV cmdlet, that seems to be more for dealing with collections where you pipe a collection of objects directly to the CSV (Comma Separated Value) file. What if you have several variables and you want to put them in a CSV file, one row at a time?

In my application, I just needed to log the creation of a Video and it’s status (Success, Failure…) along with the current date/time, the keyword for that video, and the filename of the video. So I created a small 5 line function to the work, and then call it from different places in my application.

I simply create the $newRow, i.e. the row or line of text to be written to the CSV. I handle add the commas myself, to make it a proper CSV.

The advantage of a CSV file is of course that it can be opened with most any spreadsheet tool, such as Microsoft Excel.

function AddToCSV($filename,$result,$action,$keyword,$videoFilename){
    $showDate = get-date
    $newRow = "$showDate,$result,$action,$keyword,$text"
    Add-Content $filename $newRow

#Sample Test Logic

$myFileName = "c:\Users\neal.walters\Documents\test.csv"

AddToCSV $myFileName "Success" "CreatedVideo" "best appliances in Abilene TX" ""
AddToCSV $myFileName "Failure" "CreatedVideo" "best appliances in Irving TX" ""
AddToCSV $myFileName "Success" "CreatedVideo" "best appliances in Farmers Branch TX" ""
AddToCSV $myFileName "Success" "PostVideo" "best appliances in Farmers Branch TX" ""

If you have commas inside of your text fields, you would have to do a little more to do, i.e. surrounding each text field with quotes.

Below is a sample of data similar to the sample above.


I’m building a little app that is driven by an XML configuration file. I need to substitute variables from the XML with a random name or a keyword from another source, so I used $Keyword and $MaleName and $FemaleName.

Look at this code, and see if you can spot the bug. Every single text field that I passed was showing that it contains a characterName (i.e. either $MaleName or $FemaleName.

Function IsTextViolation($text) 
    $isViolation = $false 
    $containsCharacterName = $false 
    if ($text.Contains("$MaleName") -or $text.Contains("$FemaleName")) 
          $containsCharacterName = $true 
     Write-Host "containsCharacterName=$containsCharacterName" 

     if ($text.Contains("$Keyword") -and $containsCharactername) 
           $isViolation = $true 
      return $isViolation 
Write-Host "1 $(IsTextViolation('Test 1 $Keyword') )"
Write-Host "2 $(IsTextViolation('Test 1 $MaleName') )"
Write-Host "3 $(IsTextViolation('Test 1 $FemaleName') )"
Write-Host "4 $(IsTextViolation('$MaleName Test 1 $Keyword') )"
Write-Host "5 $(IsTextViolation('Test 1 $FemaleName $Keyword') )"

So here is the issue… I chose to use $ sign as prefix to my variable, I could have used % or @ or any other symbol, but with Powershell on the brain, I chose the dollar sign. Well, when you put $MaleName in double quotes, it was substituting the value of $MaleName, which is apparently null ($null). And thus the match was always true. I had two solutions, either 1) use single quotes instead of double quotes, or 2) prefix with the escape character (the grave accent mark like this – (“`$MaleName”). I chose the first, and here is the corrected code.

Corrected Code:

Function IsTextViolation($text) 
    $isViolation = $false 
    $containsCharacterName = $false 
    if ($text.Contains('$MaleName') -or $text.Contains('$FemaleName')) 
          $containsCharacterName = $true 
     Write-Host "containsCharacterName=$containsCharacterName" 

     if ($text.Contains('$Keyword') -and $containsCharactername) 
           $isViolation = $true 
      return $isViolation 

Write-Host "1 $(IsTextViolation('Test 1 $Keyword') )"
Write-Host "2 $(IsTextViolation('Test 1 $MaleName') )"
Write-Host "3 $(IsTextViolation('Test 1 $FemaleName') )"
Write-Host "4 $(IsTextViolation('$MaleName Test 1 $Keyword') )"
Write-Host "5 $(IsTextViolation('Test 1 $FemaleName $Keyword') )"

Powershell Code:

# LUNAR DUST - home grown server/service monitor - sends out email when services defined in related .CSV config file are down 
# Author: Neal Walters - Nov 2013 
# (Lunar Dust is a play on words of the monitor name "Solar Wind") 
[string[]] $users = "",'"" # List of users to email your report to (separate by comma)  
$fromemail = ""
$SMTPserver = "" #enter your own SMTP server DNS name / IP address here
$YNTraceSuccess = "N"   #setting to "Y" will create more trace/debug to the ServiceTestTrace.txt file, setting to "N" only shows servers/services that were down
$TraceFilename = "D:\scripts\ServiceMonitorTrace.txt"
$LocalServerName = "MyServerName"    #used to determine if we should check remote server or not on GetService 
set-item trustedhosts $LocalServerName

#Get arguments flexibly, in either order.
#one parms is a "Y" or "N" to indicate to send an email, even when no servers are in error status 
#another optional parm is the filename of the CSV to read, if omitted, a default filename is used.
if ($args.Length -gt 0)
   if ($args[0] -eq "Y" -or $args[0] -eq "N")
       $IsEmailOn = $args[1]
   if ($args[0].Length -gt 4)
          $csvFilename = $args[0]

if ($args.Length -gt 1)
   if ($args[1] -eq "Y" -or $args[1] -eq "N")
       $IsEmailOn = $args[1]
   if ($args[1].Length -gt 4)
          $csvFilename = $args[0]

if ([string]::IsNullOrEmpty($csvFilename))
   $csvFilename = "D:\Scripts\ServerMonitorConfig.csv" 
   Write-Host "Setting csfFileName=$csvFileName" 

Write-Host "csvFilename=$csvFilename"
$csv = Import-Csv $csvFilename -Header @("IsActive","Environment","Category","ServerName","ServiceName","Criticality")

$HTMLMessage="<h2>Server/Service Status</h2><table border='1'><tr><th>Environment</th><th>Category</th><th>ServerName</th><th>ServiceName</th><th>Status</th><th>Process Started Date/Time</th><th>UserName</th></tr>"
$CriticalErrorCount = 0
$ErrorCount = 0 
$ServerCount = 0 

foreach ($line in $csv) {
  if ($line.IsActive -eq "Active")
    $reportStatus = "" 
    $ServerCount = $ServerCount + 1  

	#$Service = (get-service -Name $line.ServiceName -ComputerName $line.ServerName)
	#this is slower than above, but it gives us the processId which we can use to find out what time the service/process started 
	write-host "Verifying: " $line.ServerName $line.ServiceName 
    $myDate = Get-Date
    if ($YNTraceSuccess = "Y")
       Add-Content $TraceFilename "$myDate TRC01 $($line.ServerName) $($line.ServiceName)"
    $error.clear()  #clear any prior errors, otherwise same error may repeat over-and-over in trace 
    if ($LocalServerName -eq $line.ServerName)
           # see if not using -ComputerName on local computer avoids the "service not found" error 
           Add-Content $TraceFilename "$myDate TRCW1 using local computer " 
	       $Service = (get-wmiobject win32_service -filter "name = '$($line.ServiceName)'")
           Add-Content $TraceFilename "$myDate TRCW2 using remote computer $($line.ServerName) not eq $LocalServerName" 
	       $Service = (get-wmiobject win32_service -ComputerName $line.ServerName -filter "name = '$($line.ServiceName)'")

    if ($error -ne $null) 
        Write-Host "----> $($error[0].Exception) " 
        Add-Content $TraceFilename "$myDate TRCE1 $($error[0].Exception)" 

	if ($Service -eq $null) 
	    $reportStatus = "Service Not Found: name = '$($line.ServiceName)'"
		$trColor = "Yellow"
		$ErrorCount = $ErrorCount + 1  
		$CriticalErrorCount = $CriticalErrorCount + 1
		$CreationDate = "NA" 
        Write-Host "----> $reportStatus " 
        Add-Content $TraceFilename "$myDate TRC02 $reportStatus" 
        #Write-Host "Service Exists"
		#$status = $Service.Status 
		#if ($status -eq "Running")  #this was the check when using get-service instead of get-wmiobject win32_service)

		$reportStatus = $Service.State

		if ($Service.Started -eq "True") {
			#$reportStatus = "Up"
			$trColor = "White"
			# when service is running, then we can lookup the ProcessId to get the Userid and CreationDate (Time the service was started) 
			$ServicePID = $Service.ProcessID
			#Write-Host "Process id: $ServicePID" 
			$ProcessInfo = Get-WmiObject -Class Win32_Process -ComputerName $line.ServerName -Filter "ProcessID='$ServicePID'" -ea 0
			$CreationDate = $ProcessInfo | % { $_.ConvertToDateTime( $_.CreationDate )}
		    Write-Host "Down Service.Started=$($Service.Started) " 
            Write-Host "Status=$($Service.Status)  State=$($Service.State)"
			#$reportStatus = $Service.State
			$trColor = "Orange"
		    $ErrorCount = $ErrorCount + 1  
			if ($line.Criticality -eq "Error") 
                #switch from orange to yellow background 
			    $trColor = "Yellow"
				$CriticalErrorCount = $CriticalErrorCount + 1
			#Write-Host "down status=$status"   #result was empty string or null 
			$CreationDate = "NA" 
	#Write-Host "test=$reportStatus"
	$TextMessage += "$($line.Environment) $($line.Category) $($line.ServerName) $($line.ServiceName) $reportStatus $CreationDate $($Service.StartName)`r`n" 

    #build the TR and TD Cells of the HTML Table 
	$HTMLMessage += "<tr bgcolor='$trColor'>"
    $HTMLMessage += "<td>$($line.Environment)</td>"
    $HTMLMessage += "<td>$($line.Category)</td>"
    $HTMLMessage += "<td>$($line.ServerName)</td>"
    $HTMLMessage += "<td>$($line.ServiceName)</td>"
    $HTMLMessage += "<td>$reportStatus</td>"
    $HTMLMessage += "<td>$CreationDate</td>"
    $HTMLMessage += "<td>$($Service.StartName)</td>"
    $HTMLMessage += "</tr>`r`n" 

     Write-Host "Skipping InActive " $line.ServerName $line.ServiceName 
Write-Host '------' 
#$HTMLMessage = "<h3>Critical Server Count=$CriticalErrorCount  Total Error Count=$ErrorCount</h3>" + $HTMLMessage + "</table>" + "<h3>Yellow is critical, Orange is not critical.</h3>" 
$HTMLMessage = "<h3>Server-Count=$ServerCount  Critical-Count=$CriticalErrorCount  Total-Error-Count=$ErrorCount</h3>$HTMLMessage</table><h3>Yellow is critical, Orange is not critical.</h3>" 
$mydate = Get-Date 
Write-Host "Date=$myDate" 
$HTMLMessage = "<h3>$mydate</h3>$HTMLMessage"
Write-Host  $TextMessage 
Write-Host "`r`n Server-Count=$ServerCount   Critical-Count=$CriticalErrorCount  Total-Error-Count=$ErrorCount"
Write-Host "Date=$myDate" 

$emailSubject = "QT Service Email was Requested" 
if ($CriticalErrorCount -gt 0)
   $emailSubject = "Critical QT Server Down Alert: There are $CriticalErrorCount critical services down" 

#always send email when one 1 or more critical errors are found, or when the $IsEmailOn parm is set to "Always" which sends email regardless of error count 
if ($CriticalErrorCount -gt 0 -or $IsEmailOn -eq "Always")
   send-mailmessage -from $fromemail -to $users -subject $emailSubject -BodyAsHTML -body $HTMLMessage -priority High -smtpServer $SMTPserver
   Write-Host "Alert Email Sent with Subject=$emailSubject" 
   Add-Content $TraceFilename "$myDate TRC99 Alert Email Sent with Subject=$emailSubject" 


Create a CSV like this, with a list of the servers to monitor.
Column 1
Column 2 is an arbitrary environment name. Column 3 is a category of server functionality (could potentially be used to route email to the group in charge of those types of servers, e.g. IIS, BizTalk, SQL…)
Column 3 is Server/Machine Name.
Column 4 is the Windows Service name. Use “LanmanServer” just to know if the machine is up and running.
Column 5 indicates whether this should cause a critical error or just a warning (when the service is down).


Schedule a .bat or .cmd file such as follows, pointing to the desired CSV as defined above.

powershell -command "& 'D:\Scripts\ServerMonitor.ps1'" d:\Scripts\ServerMonitorConfig.csv
get-service | where {$ -match "win"}

Normally displays like this:

Note how very long names can get cut off with the ellipses….

Did you know that Powershell has a “grid-view”, similar to SQL?

get-service | where {$ -match "win"} | out-gridview


If you pipe all get-service to the out-gridview, then you can see how you can even filter the data using the “GUI” mode.
Click “Add Crtieria”, then click the checkbox next the desired property name.


You can then enter a match/contains value for that property, and the grid instantly changes to show only the matching data:


Instead of filtering with the “contains” clause, you can click the word “contains”, and choose other methods of filtering.

I was truly amazed when I discovered this feature.



This is basically a follow-up to the previous post How to add a where filter match/contains/like to any powershell cmdlet (such as Get-Service)

Cmdlets are named in the format “verb-noun”, for example get-service, start-service, etc…
Suppose you cannot remember all the verbs associated with a noun. You can run this:

get-command -Noun process


Recall from the prior blog you can do something like this to get all your BizTalk Host Instances (services):

get-service  | where {$ -match "biztalk"}

Now, suppose you wanted to start all the BizTalk Host Instances, you just pipe the results of the previous command to the “start-service” cmdlet. (Note: You may have to run Powershell “as admin” to avoid any security errors.)

get-service  | where {$ -match "biztalk"} | start-service 

There is an interesting optional parm that let’s you check your script. The “-WhatIf” parm can be added to Start-Service or Stop-Service. It will then only shows what would happen if the cmdlet runs. The cmdlet is not run.


PS C:\WINDOWS\system32> get-service | where {$ -match "DTC"} | stop-service -whatif
What if: Performing the operation "Stop-Service" on target "Distributed Transaction Coordinator (MSDTC)".

If you check the status of the service after running the above stop-service, you will see that it didn’t really stop.

Sometimes, when property names contain very long values, the default displays cuts part of the name, as shown with this command and its output.

[“ellipsis” is the term for the … when a name is truncated] as shown in the example below:

get-service | where {$ -match "win"}  | sort-object Status -desc



Red box is only added to bring your attention to the …

Simply pipe the result to the “ft -auto” cmdlet.

get-service | where {$ -match "win"}  | sort-object Status -desc | ft -auto

The full name is now displayed

ft is just the abbreviation for format-table, so you could have typed:

get-service | where {$ -match "win"}  | sort-object Status -desc | format-table -auto

Or as was discussed in my previous blog, you can use the grid view:

get-service | where {$ -match “win”} | out-gridview


get-process  |  {$ -match "win"}

Sample output is shown below. The above means “get all the process objects on this computer” and pipe that collection of objects to a where/filter where we only want to see processNames that contain the letters “host”.

Example output:

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
     75       8      796       3680    46             744 wininit
    152       8     1548       8072    54             776 winlogon

“Match” actually does a RegEx (regular expression match, so you can also do this: Find me all process that contain a “t” followed by exactly three characters, followed by the letters “host”:

PS C:\Users\Neal&gt; get-process  | where {$ -match "t\w{3}host"}

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
-------  ------    -----      ----- -----   ------     -- -----------
    181      17     9696      10796   693            7212 taskhost
    290      51    10992      17588   301     0.33   2860 taskhostex

How does this relate to BizTalk? We often deal with windows services. Suppose you are wondering if there is a DTC task started on the computer, but you don’t remember the exact service name.
Run something like this:

get-service | where {$ -match "dtc"}

Of course, if you remember the exact service name, you can enter a shorter command:

get-service -Name msdtc

On my home PC, both the above return the same result:

Status   Name               DisplayName
------   ----               -----------
Stopped  MSDTC              Distributed Transaction Coordinator

Suppose you wanted to show all processes that are stopped:

get-service | where {$_.Status -eq 'Stopped'}

Or show me only services that contain the “dtc” in the name that are stopped:

get-service | where {$_.Status -eq 'Stopped' -and $_.Name -match 'dtc'}

TIPS: Don’t forget to use -EQ instead of the = or == sign. This is not C#. Also don’t forget to put the – in front of -and, -eq, and -match.

To know what properties you can query with the -eq and the -match filter, do the following:

get-service | get-member

or the abbreviation for get-member “gm” is often used

get-service | gm

In this case, you are piping the output of the “get-service” cmdlet to the “get-member” cmdlet, which displays returns all the property, methods, and events.


Just to complete the thought, you can see just the properties by doing either of the following:

get-service | get-member  | where-object  {$_.MemberType -eq "Property"}

get-service | get-member -MemberType Properties


Just for the sake of completeness, you might also want to sort your data by some particular property. Just pipe what we learned above to the “sort-object” and tell it which property (and optionally which order) to sort.

get-service | where {$ -match "dtc"} | sort-object Status
get-service | where {$ -match "dtc"} | sort-object Status -desc