Generic filters
Exact matches only
Filter by Custom Post Type

Contribute to the PowerShell/DevOps Community - and Win $$$!

Thanks to some generous grants, The DevOps Collective has some spare cash - and we figured what better way to use it than to hold a contest for contributing to the community? So that’s what we’re doing. If you’ve ever thought, “yeah, someday I’ll do something to help the community,” well that day has come - and there’s $1,000 on the line! All you need to do is think of a PowerShell or DevOps related topic that you think needs explaining. Better “documentation,” so to speak. Perhaps a problem you’ve had to solve that you think others might benefit from, or an aspect of the technology that people just don’t understand. All you have to do is write an ebook on that topic, much like the many free ebooks we offer at

Here’s How it Works:

Start writing your book. We’ll provide some topic ideas below. It’s possible that more than one person will pick the same topic - and that’s fine. Different perspectives are valuable!

Complete your book by the end of November, 2016. That’s a long time off - so don’t procrastinate and put it off until the last minute! If you write about 1,500 words a month, you’ll be done in time. And that’s only 50 words per day!

ZIP up your contribution in accordance with the rules below, and send it to the “admin” email alias here at By December 2016, we’ll publish all the entries, and begin soliciting user feedback. If you submit earlier, we’ll publish earlier - so you’ll have more time to garner good feedback on your book! Books will publish to GitHub, and as the author we’ll make you a Collaborator. That means you’ll be able to make additions or corrections at any time after your book goes live.

We’ll collect reader feedback and votes for 60 days. At the end, the contribution with the top reader score will win $1,000 (for non-US participants, we will send this via PayPal and let them convert to your local currency; within the US, we’ll send a check). Everyone who has a completed book published will receive a small gift, as well as recognition.

Our top feedback recipients will also receive an introduction to a "real" book publisher, if they choose. If you enjoyed the writing progress and want to produce a "dead trees" book, you'll be well on your way.

The Rules:

1. Your book must total at least 8,000 words when completed. That’s about a dozen pages in a standard Word template, without images. You cannot submit a partial book, although you can later make expansions or corrections as needed.

2. You must write in Markdown. All files must be plain-text, with an “.md” filename extension.

3. You must include a book.txt file that lists your chapter filenames in order.

4. Chapter files must start with a top-level “# Heading” that is the chapter name.

5. Images must be in an /images subfolder, and must be included in chapters by means of a proper Markdown image insertion.

6. Code blocks must be neatly formatted, and properly set off using proper Markdown syntax.

7. Your book’s subject must be on Windows PowerShell or DevOps topics.

8. You must be willing to release your book under a Creative Commons "Attribution-NonCommercial-NoDerivatives 4.0 International” license, and be willing to allow readers to make a donation to the nonprofit DevOps Collective, in addition to downloading your book for free. We'll be publishing your book alongside our other ones, and treating your ebook the same way we do ours.

9. You're allowed to co-author.

10. Our main audience is English-speaking, but you're welcome to write your ebook in another language if you prefer.

Topic Ideas:

These are just some of the ebook needs we’ve run across…

  • Using Regular Expressions in PowerShell (including capture groups)
  • Building a Continuous Integration Pipeline (with whatever tools you like)
  • Working with XML Data in PowerShell (including finding values, updating data, etc)
  • Bootstrapping a DevOps Culture in Your Organization
  • Creating a Simple GUI by Using WPF

There are lots more needs. Why, just browse the forums and look for commonly asked questions - those're great ideas for an ebook!

And don't think that you need to be an expert writer. Most people prefer reading something that's less formal and more conversational - just write like you'd speak out loud. You can even write a "stream of consciousness" piece - decide to solve a problem, and then write about every hurdle and success you have along the way. Those experiential pieces can really help someone else understand "the process" better. Write something you'd like to read! Even (or especially) entry-level material is more than welcome, as there are always new people who are just getting started.

Want Help?

No problem. Over the next several weeks, we'll be posting inspirational and practical articles from published writers to help you get started, to help you keep going, and to help you make the best book you can.

Just Say Yes!

C'mon, you know you've always wanted to contribute, and now's a great time to give back to your community! Start dreaming up ideas, polish up your Markdown editor, and get started!

2016-January Scripting Games Wrap-Up

Here's the official answer to our January 2016 Scripting Games puzzle, provided by Adam Bertram (the puzzle's author).

Official Answer

This month's puzzle made you do some work. Not only did it make you write some PowerShell but it also probably forced you to do some background research on the Interwebz as well. Being able to determine how long a computer has been on for isn't immediately obvious. The puzzle was meant to give you a real-world scenario which would force you to first investigate how that's even possible before you write your first piece of code. A great scripter doesn't just know how to write code; they also know where to look for answers relating to the problem they're trying to automate as well!

Here's the full solution.  If you have any questions, don't hesitate to contact me on Twitter @adbertram.

#Requires -Version 4

function Get-Uptime
		This function queries a local or remote computer to find the time it was started up and calculates how long it has
		been online.	
		This function uses a computer's event log to search for the event ID of 6005 in the System log to find the time it was
		started up. Once it finds this ID, it then takes this time and gets the difference between the start time and the current
		time to calculate how long the computer has been online for.
		This function also includes a status output to show you if the computer(s) were queried successfully or not and also
		includes a MightNeedPathed property. This is set to True if the computer was determined to be up for longer than 30 days.
		Due to Microsoft's monthly patching cycle and being that a server is typically rebooted during this patching cycle, it's
		likely if a computer has been up for longer than 30 days it might need some patches applied.
		If the computer is offline, it will report as OFFLINE in the Status property. If it cannot query the computer for some
		reason (the event cannot be found perhaps), it will display ERROR in the Status property.
		PS> Get-UpTime
		This example will query the local computer to find the time it was started, calculate the difference and display the uptime
		in various formats.
		PS> Get-UpTime -ComputerName SERVER1
		This example will query the computer SERVER1 to find the time it was started, calculate the difference and display the uptime
		in various formats. If SERVER1 is offline, it will display OFFLINE in the Status property. If the function cannot
		find the start time it will display ERROR in the Status field.
	.PARAMETER ComputerName
		The name of the computer you'd like to run this function against. By default, it will run against the local computer.
		You may pass multiple names to this delimited by a comma.  You cannot include wildcards. You may also pass computer names
		to this property from the pipeline.
		String. You can pass computer name via the $ComputerName parameter to Get-Uptime.
		[string[]]$ComputerName = $env:COMPUTERNAME
	begin ## Use a begin block to only process this code one time if names passed from the pipeline
		$today = Get-Date ## Get the current date/time in the begin block to prevent executing Get-Date numerous times
		foreach ($computer in $ComputerName) ## ComputerName is a string collection so we must be able to process each object
			try ## Wrap all code in a try/catch block to catch exceptions and to control code execution
				## Build the soon-to-be object to output so all the properties are already here to populate
				$output = [Ordered]@{
					'ComputerName' = $computer
					'StartTime' = $null
					'Uptime (Months)' = $null
					'Uptime (Days)' = $null
					'Status' = $null
					'MightNeedPatched' = $false
				## Test to ensure the computer is online. If not, set the Status to OFFLINE and throw an exception with terminates
				## the rest of the code.
				if (-not (Test-Connection -ComputerName $computer -Count 1 -Quiet))
					$output.Status = 'OFFLINE'
					throw "The computer [$($computer)] is offline."
				## Bild the hashtable for the -FilterHashTable parameter. We're querying the System event log for event ID 6005
				$filterHt = @{
					'LogName' = 'System'
					'ID' = 6005
				## Find the first event (which will always be the most recent)
				$startEvent = Get-WinEvent -ComputerName $computer -FilterHashtable $filterHt | select -First 1
				## Set the status to be ERROR and throw an exception if we can't find the start event for some reason.
				if (-not $startEvent)
					$output.Status = 'ERROR'
					throw "Unable to determine uptime for computer [$($computer)]"
				## If no error, status is OK
				$output.Status = 'OK'
				## Set the StartTime property to a datetime type so that it can be sorted if we're runnning this on multiple computers.
				$output.StartTime = [dateTime]$startEvent.TimeCreated
				## Use a timespan object to get the difference between now and when the computer was started.
				$daysUp = [math]::Round((New-TimeSpan -Start $startEvent.TimeCreated -End $today).TotalDays, 2)
				$output.'Uptime (Days)' = $daysUp
				## If it's been up for longer than 30 days, set the MightNeedPatched property to $true.
				if ($daysUp -gt 30)
					$output.'MightNeedPatched' = $true
				## Write a warning to the console with the message thrown
				Write-Warning $_.Exception.Message
				## Regardless of an exception thrown or not, always output a PSCustomObject to show computer results.


PowerShell Conference Europe

PowerShell Conference EU combines the former “Deutsche PowerShell Konferenz” and the spiritual successor of “PowerShell Summit Europe” into one great big 3-day PowerShell event for Admins and DevOps in Europe and takes placeApril 20-22 in Hannover/Germany. With more than 40 international speakers including PowerShell inventor Jeffrey Snover, and more than 60 sessions, you are cordially invited to join this massive European PowerShell event. The agenda is up on, and registration is open. Seats and hotel capacity are limited so don’t wait and register! is partnering with and, and together we want to ensure that you have a comprehensive local PowerShell conference in your region of the world.

2015-December Scripting Games Wrap-Up

Happy New Year! Here's the official answer to our December 2015 Games, where Board Member challenged you to add some code to your holiday spirit. Want to share your own puzzle for a future Games? Drop a line to admin@ via email!

Official Answer

You started with this here string.


$list = @"
1 Partridge in a pear tree
2 Turtle Doves
3 French Hens
4 Calling Birds
5 Golden Rings
6 Geese a laying
7 Swans a swimming
8 Maids a milking
9 Ladies dancing
10 Lords a leaping
11 Pipers piping
12 Drummers drumming


  1. Split $list into a collection of entries, as you typed them, and sort the results by length.image001


2. As a bonus, see if you can sort the length without the number.image002


3. Turn each line into a custom object with a properties for Count and Item.

$gifts = $list -split "`n" | foreach {
$split =  $_ -split " ",2
[pscustomobject]@{Count=$split[0];Item = $split[1].trim()}



4. Using your custom objects, what is the total number of all bird-related items?

$gifts | where {$_.item -match "partridge|doves|hens|birds|geese|swans"} |
measure-object -Property count -sum | Select Sum



5. What is the total count of all items?

$gifts | measure-object -Property count -sum | select Sum



What is the cumulative or literal count? I used an algorithm I found at that I turned into a PowerShell expression:

$daily = 0
$total = 0
for ($i = 1 ;$i -le 12 ; $i++) {
#get the daily total for each day cumulatively
#add the daily total to the grand total



Hope you had fun! Happy Holidays and hoping you have a healthy and prosperous PowerShell New Year.


2015-November Scripting Games Wrap-Up

The November puzzle was all about cleaning up someone else's code, and came from community member Tim Curwick. Want to share your own puzzle for a future Games? Drop a line to admin@ via email!

Official Answer

Sometimes things break. Then we have to dive into the code and figure out what it does. Figuring out someone else’s code (or code you wrote long ago and long forgot) is a very important skill.

And sometimes, when you review old code, you find ample opportunities for improvement. Over time, scripts change, their environment changes, and PowerShell itself changes. Sometimes parts of a script have become irrelevant or inefficient or just weren’t very well done because you didn’t quite know what you were doing back then.

That was our challenge this month. We have a script that is part of a larger ecosystem that I had to understand. And then with that understanding, I had the opportunity to improve it. Because this script is part of a larger system, we can’t change in the input or the output, but we can change anything in between.

So let’s take a look.

if($VMNameStr.indexof(",") -gt 0)
$Trace="Found Comma..."
$VMs=$VMNameStr -split "," | %{$_.trim()}
$trace+="Length = $($VMs.length)"
$trace+=$VMs -is [array]
for($i=0;$i -lt $VMs.length;$i++){
if($VMs[$i] -gt ""){
set-variable -Name ("vmname" + $i) -value $VMs[$i]

The $Trace variable is set, but never used. It appears to have been used at one time for some debug logging, but no longer serves any function. The Set-Variable line creates variables that are never used. These lines can be eliminated. (A few puzze entries preserved the creation of some of the unused variables. In fairness, if the script were dot sourced, these variables might be relevant to the calling script. But none of those entries (as of this writing) were consistent in this choice; they arbitrarily preserved some but not all of the variables.)

The only thing the script sends to the output stream is $VMNames. $VMNames is first defined as an array. If the input parameter $VMNameStr is a comma delimited list, string(s) are added to the array. If $VMNameStr is not a comma delimited list, $VMNames is redefined as the original input string.

In the latter case, I would probably consider it a bug to not trim that string, but if nothing but white space were input, the output would change from white space to an empty string if I trimmed it, and I don’t know if that would break the error handling in the calling script, or some other script in the system. So I am going to preserve that "bug" for compatibility.

If the input has a comma, it is split at the commas, and each resulting string is trimmed. Each trimmed result that is a non-empty string is added to the array. (A common mistake amongst the entries was to filter out the empty strings before trimming them. This would fail to filter out strings with just white space.)

The original scripter checked for commas in the input string using the .IndexOf() method. We are going to use .Contains() as a more concise, faster, more readable option.

If ( $VMNameStr.Contains( "," ) ) {

To split a string, sometimes the -Split operator is preferable, but since we are going to be chaining a bunch of string methods, we’ll use the .Split() string method.

If ( $VMNameStr.Contains( "," ) ) { $VMNameStr.Split(',')

In newer versions of PowerShell, we can then trim the white space from all of the elements of the array simultaneously.

If ( $VMNameStr.Contains( "," ) ) { $VMNameStr.Split(',').Trim()

And then we filter out any empty results with PowerShell’s .Where{} method. The .Where method is always faster than Where-Object, because there is no pipeline overhead, and it is more concise and readable without resorting to aliases and positional parameters. If a string is converted to Boolean, non-empty strings convert to $True and empty strings convert to $False, so we don’t have to compare the string to anything to determine if they are empty or not.

If ( $VMNameStr.Contains( "," ) ) { $VMNameStr.Split(',').Trim().Where{ $_ } }

And if the input string does not contain commas, we just return the input string.

If ( $VMNameStr.Contains( "," ) ) { $VMNameStr.Split(',').Trim().Where{ $_ } } Else { $VMNameStr }

Then we add a comment to make it easier to figure out next time, and the final result looks like this.

For PowerShell 4.0 and up.

Param( [string]$VMNameStr )
#  If the input string is comma delimited
#    Split it into an array, trim the results, and filter out empty results
#  Note: if the input string in not comma delimited, it will be returned as is, untrimmed
If ( $VMNameStr.Contains( "," ) ) { $VMNameStr.Split(',').Trim().Where{ $_ } } Else { $VMNameStr }

For PowerShell 2.0 or 3.0 compatibility, we use ForEach-Object and Where-Object commands instead of .ForEach() and .Where{} methods.

Param( [string]$VMNameStr )
# If the input string is comma delimited
# Split it into an array, trim the results, and filter out empty results
# Note: if the input string in not comma delimited, it will be returned as is, untrimmed
If ( $VMNameStr.Contains( "," ) ) { $VMNameStr.Split(',') | ForEach { $_.Trim() } | Where { $_ } } Else { $VMNameStr }

Community Highlights

If you haven't browsed through some of the submissions from November, you definitely ought to. A lot of folks took real care to clean up the code and explain what they did - it's an incredible useful exercise, since modding someone else's code is perhaps one of the more difficult and frequent exercises you'll do in the real world.

User brianburke did what is perhaps the most thorough writeup, cleaning up and reformatting the original as well as providing a corrected bit of code. User Jake Ballard may be the "winner" from the month's Games, as he also provided a complete "testing" script to run against the corrected code - great idea! User Keithlr2 also has a notable entry, where he discusses the assumptions he made and the tests he wrote for the code. Again, that's such a good way to see the process involved here, that he may also be a "winner" this month.

2015-October Scripting Games Wrap-Up

The October puzzler was a tough one, especially if you're not used to dragging information from the Web in XML form. Adam Bertram provides our Celebrity Entry.

Celebrity Entry

Note: Adam's entry is helpfully posted in his GitHub repo.

As Don mentioned in the challenge, putting the functionality of finding items in a RSS feed should be put into a function. Why? It's because, chances are, you're probably going to want to do this for more than one RSS feed. The moment you think to yourself "Hmm..getting the feed for FOX News is nice but maybe I'd want to get it for CNN as well" would be the time you'd need to build a function out of it.

Every time I begin writing a function I brainstorm what attributes of that function might change over time. When you want to find items in an RSS feed what are the things that might be different? I'm probably going to want to get more than one RSS feed, right? Well, what are all the attributes that go into that? In our case, it was just a single attribute; the URL or URI (uniform resource identifier) to be specific. That became my first parameter. When I create parameters I'll always default to putting some kind of validation attribute on them. It's always better to limit input as tightly as possible and catch potential errors ahead of time. For a URL, the perfect validation attribute is the ValidatePattern attribute. Because I'm not the best at building regex patterns from scratch I looked up a good URL match found one on the web. I then tested it with a few different URLs using the -match operator and found that it was working just as I would have hoped so I used that one.

After I got the URI parameter in there, I began to work on the actual functionality to get the RSS feed items. The first cmdlet that came to mind was Invoke-WebRequest. Although not good for downloading large files and content (it can be pretty slow sometimes). It is, by far, the easiest and most straightforward way to download HTTP content and, technically, a RSS feed is just a propertly formatted HTML page to greatly simplify it. I found a RSS feed and pointed Invoke-WebRequest to it and start checking out the result. I soon found it contained all of the data I was looking for but I couldn't find the actual items anywhere.

Then I noticed that the data was in XML format. This gave me an idea. I then changed the type to [xml] which parsed out the XML and allowed me to finally find the items hidden in the property. This got me all of the items I was looking for. At first, I was just casting the $result variable directly to [xml] when I thought "What if the HTTP response didn't succeed?". I needed some error handling in there so instead of doing [xml]$result I decided to first check for the HTTP status code on the return. A successful HTTP status code is 200 so I'm checking to ensure I got that before proceeding. If I didn't, I'm then throwing an exception out to my catch block which stops the function's execution.

After I can confirm that the status was successful only then do I cast $result to [xml]. Again, as an error handling step, I'm then ensuring that $ actually has anything in there and if so, I'm continuing.

I've now got all my items bundled up in a collection. I'm now able to iterate over them with a foreach loop as I'm doing. I could technically just send this out of the function but it's going to be in an XML element object. I prefer to create my own objects and tend to always use the [pscustomobject] type accelerator. By creating your own objects to send to the pipeline allows you more control. In my example here, I'm creating an $output hashtable for every item. I'm then choosing various attribute of each item that I would like to show. You can see that properties had multiple items in them such as the comments property. I had to break that up to show the link and the count separately. Also, since there might be multiple categories associated with an item I had to convert those into a string from an array by joining them together with a comma. When I've created the hashtable as I see fit the only thing left to do is create the object and send it out to the pipeline.

At this point, the function is pretty good but it still didn't have many configuration options so I decided to add some filtering options to it similar to Get-EventLog. It's always a good idea to try to think about similarities in your function to the default cmdlets. If you can find one that's similar, I'll use the exact same parameter names just to keep things simple. Get-EventLog has a Newest and Oldest parameter which does exactly what my function's parameters are doing. I could have called them GetTheNewest and changed up the functionality slightly but it would be confusing.

I decided to add the Newest, Oldest and Author parameters last. This allows the user to filter by parameter instead of using Where-Object to filter. Again, using as much parameter validation as possible I ensured both the Newest and Oldest parameters were both integers since this is how Get-EventLog is. I'm also using ValidateRange here in case someone tries to put a negative number or some crazy big number in there. Since the Author parameter could be just about any string I have no validation in there.

You'll also notice that I implemented some parameter sets. I didn't want someone trying to get the newest 10 items AND the oldest 2 items at the same time. This prevented them from using both in tandem.

You'll see that adding these parameters added quite a bit of code. Depending on the combination of these that were used depended on the parameters being used for the Select-Object, Where-Object and Sort-Object cmdlets. You'll notice that I didn't repeat code. It's always a good idea to create the default set of parameter as early as possible that apply to everything and then incrementally add to them as you go down. There were some instances where I technically didn't need the Where-Object cmdlet, for example, but to prevent code duplication I simply put an expression in there that would always evaluate to $true. You'll also noticed that I used $PSBoundParameters.ContainsKey() instead of just using the variable name. This is another best practice I use to be as specific as possible. This allows me to easily look down through the code and see that I want to know if this variable came from a parameter or not; not just from some variable created in the function somewhere.

Finally, the last piece to mention is line 68 where I'm using a Select-Object property. This was one of the last pieces I put in. Since I wanted the ability to output only the newest or the oldest X number of items I had to be able to sort them. To do this, I was going to do it by PubDate. However, PubDate was in a string format that couldn't be sorted by [datetime]. By replacing PubDate with PublishDate allowed me to sort the items.

This was fun!  I left my errors here so you can see that even those of us who pretty much do PowerShell for a living are just normal folks who typo from time to time, and rely on the tools we have built into the console to succeed. 

Official Answer


Adam's answer is actually really close to the official answer, so we're letting Adam's answer stand as official!

2015-September Scripting Games Wrap-Up

The September puzzler wasn't intended to break your brain - but it was intended to highlight an extremely important pipeline technique. Let's begin with our Celebrity Entry, from Stephen Owen.

Celebrity Entry

Alright guys, buckle yourselfs (selfs?  Selves?  I'm not sure which one it is..and this is supposed to be a stream of consciousness thing, so I'll just leave it as is)

So, we've got a CSV, with either computernames or IP addresses.  The fact that RPC is already open and all of my communcations works..and it's the same domain?  There goes like half of what I normally have to fix, right there! 

This one shouldn't be too bad.  We need to iterate through each computer in the list, using either it's IP address or name--whatever we've got listed, grab some info, then return back the OS version and the machine name.  Not too bad!

Let's get coding!

I've got a few VMs, all joined to my local domain FoxDeploy, (FoxDeploy, named after the wonderful PowerShell Blog, which I happen to write.  Quick, go there right now and follow me!), and I've already got an account I can use, so I'll just test this in my own environment.

Alright, I've fired up PowerShell and have a connection to Hyper-V.  I'll only perform this puzzle/test  on VMs that are online.

Inline image 17

There are all of my VMs.  Now let me filter down to only those that are Running:

Inline image 16

So far so good!  I know I need to have a column called MachineName, so I'll just select the Name column and rename it to MachineName using a calculated property (it's one of these funky things, like when you select a property but do some editing to it in real time.  For example

Get-Process | select Name,@{Name='Full Name';Expression={$_.Description}}  

Don't just read it, try it!  See, we selected the Name property as it was, but also made a new property/column called 'Full Name', and then populated it with the .Description property for each object.  Anyway, the whole point of this was that I'm using a Calculated Property.  With me?  Great!

To change the name of the column from Name to machinename, I'll just do this: Get-VM | ? State -ne Off | Select @{N='MachineName';Exp={$_.Name}} | convertto-csv

 Inline image 15

Oops, should have used Export-CSV, not ConvertTo.  I literally always make that mistake.  Still, I can see that this is looking pretty much like what I need it to look like, I'll preserve this one-liner and just add out-file to the end.

 Inline image 14

Protip : you can use the start command to open webpages, or open whatever file in their default application.  The more you can do from the command line, the more like a wizard you'll seem to mere mortals.  Don't forget!

For one last thing, I'll go ahead and open it in Excel and add my own IP address,

 Inline image 13

Ugh, I forgot to use the -NoTypeInformation field when I exported this file.  No biggie, it shouldn't hamper me in this case.  

 Inline image 12

OK, I've got my input file.  Now, the task was to find the OS name.  I don't off the top of my head know where to find that, other than the SystemInfo.exe DOS command.  However, stuff like OS Captions and other frilly and sometimes useful data normally live in WMI, so I'll start by stabbing around to find it in WMI first.

My normal approach to WMI is to first use the Get-CimClass cmdlet as a searching tool, to find classes that might have good info.  If I find a juicy looking one, I'll then use Get-CimInstance to look inside the class and see what I find.

If you're confused about why I'm talking CIM now when I was saying WMI earlier, just picture WMI and CIM as two different gateways to the same juicy goodness database that is the WMI Repository.  Two doors - One database.  Got it? 

Get-CimClass is cool becasuse you can do a wildcard search, so I'll start by looking for things that have the word OS in them.  

Get-CimClass *os*

 Inline image 11

Uh, that was way too much.  How about Operating System instead?

Get-CimClass *Operatingsys*

Inline image 10 

Oh this is much better.  Normally, if you look at WMI/CIM and see classes that are otherwise the same but for the letters CIM/Win32 in the beginning, you can safely assume they'll be duplicates.  

To recap,  first I found the Class that might have juicy stuff using Get-CimClass.  Now I look at Get-CimInstance to actually pull the instances of that class and see what it contains.  CIM_OperatingSystem has some properties called Caption,Description etc, so I'll start there:

Get-CimInstance CIM_OperatingSystem

 Inline image 9

This looks good but there is probably more.  When in doubt, FL*  it out.  ( I pronounce that as FLº it out, acting like it's a little 'o' character.  Cool?  No?  Not cool…oh well)

Get-CimInstance CIM_OperatingSystem | FL * 

Inline image 8

Ding ding ding, we have a winner!  So, I've found the info I want, which is the Caption property of the CIM_OperatingSystem Class.  

Now to get some stuff from other PCs. Let's start simple with importing the machines:

 Inline image 7

Alright, now, let me try on one of these PCs.  Out of habit, Pretty much always type GWMI, which is shorthand for Get-WMIObject

Get-WmiObject Win32_OperatingSystem -ComputerName VM01 | select Caption

Inline image 6

I'm pretty sure there is a field somewhere in OperatingSystem that gives you the computer name too, so I'll just find it with a lazy wildcard for *name*

Get-WmiObject Win32_OperatingSystem -ComputerName VM01 | select caption,*name*

Inline image 5

Got it, CSName!  Almost done, now, I just need to nest an Import-CSV in here with parenthesis for the ComputerName Property

Get-WmiObject Win32_OperatingSystem -ComputerName (import-csv .\MachineNames.csv) | select caption,CSname

Inline image 4

As Jason Helmick would say 'blood in the water!'.  If we drilled down into any of these errors, we'd see that it choked trying to find our computers, a sure sign that I'm not giving PowerShell the input that it wants.  The problem is probably the mismatched properties, I'm giving PowerShell a $_.MachineName, but it wants a $_.ComputerName.  The easiest way around this is to just Pipe my Import-CSV into a Select MachineName, using the -Expand property to toss out the header/property name.

Get-WmiObject Win32_OperatingSystem -ComputerName (import-csv .\MachineNames.csv | select -expand MachineName) | select caption,Csname

Inline image 3

Winner winner, chicken dinner!  My last step is to export this as a CSC and change the column names AGAIN, this time to MachineName and OSVersion.  More calculated properties!

PS L:\> Get-WmiObject Win32_OperatingSystem -ComputerName (import-csv .\MachineNames.csv |
select -expand MachineName) | select @{N='MachineName';Exp={$_.CSName}},`

 Inline image 2

Now, to dump it into a CSV file.

PS L:\> Get-WmiObject Win32_OperatingSystem -ComputerName (import-csv .\MachineNames.csv |
select -expand MachineName) | select @{N='MachineName';Exp={$_.CSName}},`
@{N='OSVersion';Exp={$_.Caption}} | Export-Csv .\Output.csv

 Inline image 1

This was fun!  I left my errors here so you can see that even those of us who pretty much do PowerShell for a living are just normal folks who typo from time to time, and rely on the tools we have built into the console to succeed. 

Official Answer

While there's no one right way to accomplish this task, our puzzle author obviously has an answer in mind. Here it is:

Import-CSV Input.CSV |
Select-Object -Property @{n='ComputerName';e={$_.MachineName}} |
Get-CimInstance -Class Win32_OperatingSystem |
Select-Object -Property @{n='MACHINENAME';e={$_.CSName}},
 @{n='OSVERSION';e={$_.Caption}} |
Export-CSV Output.CSV

The only real difference is that this example assumes Get-CimInstance will accept its -ComputerName parameter from the pipeline ByPropertyName. By renaming the CSV file's column (by using Select-Object), we can make that work. This will run somewhat differently than Stephen's example, because computer names will be able to stream through the pipeline one at a time. In Stephen's example, the computer names will all be read in one batch, and passed to Get-CimInstance in one batch.

Your Answers

Kirill Pashkov has what is probably the best attempt at shortening the command line as much as possible. He dug out more command aliases than even our puzzle author realized existed!

Many, many, many of you - Joe Kirby, looking at you - came up with substantially the same thing as the official answer, some in short form, some in long form. Good work! Others took very close approaches.

Jeff Bunting came up with an answer that is worth looking at. In running some tests with really really really big CSV files, Jeff's solution ran slower, in part we think due to his use of ForEach-Object, rather than Select-Object. That's just a guess, of course, and it still arrives at the correct result, but it's always interesting to see the directions other folks take.

And Flynn Bundy, thanks for the nice comments - we will keep it up, but only because you asked! And, interesting take on the challenge - adding in the Test-Connection is a cool extra.

Rob Campbell had a really interesting approach, managing to eschew curly brackets entirely by using some fairly esoteric syntax. Worth a look if you want to see how weird PowerShell can get when you try!

This challenge had some of the most, and the best, variations yet - we hope you all keep playing!

PowerShell Summit Europe 2015 - Last-Minute Attendee Details

The Summit is almost upon us!

As a quick note, please plan to monitor the @PSHDonsBoss and @concentratedDon Twitter feeds leading up to the Summit. That'll be the best way to get any last-minute changes or tips. Because our organizers are operating in a foreign land, their Twitter accounts are about the most sure-fire way we have to post breaking news.

But, for now, here's what you need to know:

  • Our unofficial Sunday meet-up has become more official! Thanks to a generous grant from Enfo, , and the organizational skills of Niklas Goude, we'll be meeting from 5pm to 7pm. We will also have name badges for pick-up, saving you some time Monday morning. We'll be at Enfo's office at Upplandsgatan 7 in downtown Stockholm, there'll be beverages and snacks, and we hope to see you.
  • Plan to be in one of our two session rooms by 8:30am on Monday. Jason Helmick and Don Jones will have some last-minute announcements for you. Also, please note that neither session room can accommodate all of us at once - plan to arrive at your selected sessions early to ensure a seat. We apologize if the room fills for a session you really wanted to attend, but we must observe the fire code capacities of the rooms, and may not be able to permit standing room. We appreciate your cooperation and understanding.
  • We will serve a light continental breakfast each morning from 8am, and lunch at noon. Expect coffee throughout the day. 
  • Monday evening, the first round is on us at Icebaran exciting experience just a few blocks from the hotel.
  • Tuesday evening, we'll head over to Gamla Stan for an evening out. We'll pass out directions and bar suggestions, and we recommend having a metro pass for the short trip.
  • Wednesday afternoon's VERIFIED EFFECTIVE exam is sold out. Candidates DO NOT need to bring a computer - we'll provide everything. Candidates MUST have their EventBrite ticket (printed, EventBrite app, or email confirmation on your phone) to enter. Enter at least 5 minutes BEFORE the scheduled start time. From 5m to start time, we'll offer remaining available space to standby candidates. 
  • Please leave your backpack and other bulky belongings in your hotel room, at least until we suss out the situation and see how much extra room exists. We want everyone to be comfortable. We will NOT have power in the room, and you are NOT permitted to drape power cords across any walkways, so consider leaving laptops in your hotel room. Also, we will not be locking the session rooms during lunch, so any electronics will have to be lugged down to lunch and back - best not to bring them unless they're light.
  • Please help us remind speakers to PRESS THE BUTTON! at the start and end of their session, so we can capture the goodness. Session recordings may take a few weeks to get uploaded, but we'll do our best.
  • Jeffrey Snover will be delivering his "State of the Shell" address, but will have to leave early due to unexpected work commitments. Another Microsoft product team member will take over Jeffrey's later session. We appreciate your understanding.
  • June Blender of SAPIEN Technologies will be presenting a short help-writing workshop outside the main schedule. We'll announce details on Monday at 8:30am in both session rooms.
  • As always, we want you to ASK QUESTIONS and MAKE COMMENTS during presentations. Some rules:
    • Speak up! so the room mic can capture your voice for posterity.
    • Ask speakers to repeat or summarize questions.
    • Be courteous and professional.
    • If you want to follow-up with the speaker, please allow them to disconnect their laptop, and then move outside the session room so the next session may begin.

Save travels and we'll see in you Stockholm very soon!

2015-August Scripting Games Wrap-Up

The August puzzler was intended to highlight the usefulness of REST APIs, and the relative ease with which you can wrap PowerShell commands around them. Our Celebrity Entry for this month will be from Warren Frame, followed by the Official Entry from the puzzle's author, Don Jones.

Celebrity Entry

A short while back, I was plugging away on a fun PowerShell project, when I received a pleasant surprise - an invitation to write an article on the August Scripting Games Puzzle! For one reason or another, I’ve always missed out on the opportunity to participate in the Scripting Games, now I had no excuse.

I took notes as I read through the puzzle and began thinking and writing my solution; this article will be a stream-of-conscious on how I worked through this problem. Let’s start with the puzzle, paraphrased:

The Puzzle

Display data from in a table format, with longitude, latitude, continent_code, and timezone. The challenges were to solve this as a one-liner, as an advanced function, and to include notes on another handy web service you’ve used.

My first thought: Web APIs? Nice! I use these every day, and they’re quite important nowadays, with so many solutions offering a web API.

My second thought: Web APIs? Damn! This means I need to read through some documentation and figure out the intricacies of yet-another-REST-API.

Web APIs

Nowadays, everything but the kitchen sink comes with a web API, perhaps a RESTful API or a WSDL based web service. EMC Isilon, Citrix NetScaler, Microsoft Exchange, Thycotic Secret Server, Infoblox, StackExchange, and more. You name it, it probably has a web API.

This means learning how to call a web API from PowerShell can be incredibly valuable, allowing you to integrate and build tools and automation for these various technologies… But there’s a catch.

These APIs don’t have common conventions and standards to the extent that PowerShell does. They don’t have functions like Get-Help or Get-Member to tell you more about them; instead, they’re usually augmented with pages upon pages of documentation. Hopefully the vendor documented everything, and you find what you need.

Let’s look at some tools we can use for to solve this puzzle.

Web Services and PowerShell

We have a URL, and the puzzle mentioned JSON; there’s a good chance this is a REST API. Stephen Fox discussed some basic ways to differentiate these here. We have a number of tools at our disposal, including:

  • New-WebServiceProxy - Cmdlet used to connect to a traditional web service
  • Invoke-WebRequest - General purpose Cmdlet to send web requests and parse responses
  • Invoke-RESTMethod - Cmdlet to simplify calling REST web services
  • System.Net.WebClient - .NET class for more complex needs

The three Cmdlets were introduced in PowerShell 3.

Let’s step back a moment. The puzzle gave us a specific URL to query. This is very rare. In the wild, you’re going to run into the need to work with a particular technology. You’ll have to search around to see if there’s an API. There will be documentation. Maybe on the vendor’s website. Maybe with the installer bits. In this case, if we go to the root of, we find the documentation and a few examples.

We have the ingredients for a solution: we know the PowerShell Cmdlets we can work with, and we have the documentation. Let’s fire up PowerShell and get to work!

Experimenting: The First Challenge

PowerShell is both a command line and scripting language. This means we can start experimenting with our ingredients right at the command line, even if our intent is to write a script or advanced function. Let’s try it out:


PS C:\> $Response = Invoke-WebRequest -Uri
PS C:\> $Response

Okay! That gave us back a bunch of stuff that we don’t need, but if we look at the Content property, we see some information we need for our output. Let’s expand this:

PS C:\> $Response.Content 

This looks like the data we need! It looks like a string though, who has time for parsing text? It turns out this is JSON, similar to XML, but much lighter weight. Starting in PowerShell 3, we have handy functions for converting JSON to objects and vice versa:

PS C:\> $Response.Content | ConvertFrom-Json 

That’s more like it! Thankfully, we have a shortcut that handles this under the hood: Invoke-RESTMethod.

PS C:\> Invoke-RESTMethod -Uri

Even simpler! Okay, let’s get nitpicky and format it for the challenge:


PS C:\> Invoke-RESTMethod -Uri |
Select-Object -Property longitude, latitude, continent_code, timezone 

First challenge down; let’s move on to something more useful: advanced functions.

Build Your Own Cmdlet: The Second Challenge

We won’t actually write and compile a Cmdlet, but we’ll write an advanced function, which looks and acts just like a Cmdlet. Our goal is to write Get-GeoInformation to provide the same output as above.

What’s an advanced function? Long story short, it’s just a function with the Cmdletbinding attribute. You can learn more about these in Learn PowerShell Toolmaking in a Month of Lunches, in this quick hit on best practices for PowerShell functions, or in about_Functions_Advanced.

No need to re-invent the wheel here, we can start with the built in Cmdlet (advanced function) snippet from the PowerShell ISE (version 3 or later). Alternatively, we can copy and paste from an advanced function we’ve already written.

We provide some help for the end user, and offer features like pipeline input and accepting arrays of IP addresses.

Two important notes to highlight from this code:

  • We want to take multiple IP addresses from the IPAddress parameter, so we need to loop over them. On the other hand, if no IPAddress is specified, we still need the code to run. We handle this by creating an IPAddress array in the Begin block, and loop over this if no IPAddress is specified.
  • We accept pipeline input like a normal Cmdlet. We do this by tagging the IPAddress parameter with ValueFromPipeline, and we have our code in the Process block, where the current pipeline item will be $IPAddress.

We can run Get-Help to see several ways to run our new command:

Get-Help Get-GeoInformation -Examples 

Let’s try it out.

Solving the second challenge:

PS C:\> Get-GeoInformation | Select-Object -Property longitude, latitude, continent_code, timezone 


Notice that we don’t restrict the output from the function. If we format or limit the output inside the function, we limit what we can do downstream. We could apply special formatting that doesn’t break this rule, but we omit this for simplicity.

That’s it! Creating re-usable tools as advanced function leaves you with more portable and readable code, and they’re fairly straightforward to write. Instead of copying and pasting snippets of code, consider writing these advanced functions, re-using them, and even sharing them with your co-workers and the PowerShell community

Could we do more?

The Third Challenge and Beyond

Back in the real world, you’ll find you need more than a single function for many web APIs, which might have a wealth of endpoints you can call. A PowerShell module is a great way to handle these.

For the third challenge, we’ll submit the StackExchange API. Notice how many endpoints there are! Many products in your environment may have similar functionality, allowing you to query and manage a system with web requests.

Perhaps you want to find unanswered PowerShell questions. Can you come up with code that provides the most recent 20 questions tagged PowerShell from stackoverflow, and format the output similar to this?

Stay tuned for a brief write-up on common practices around writing modules. We’ll illustrate this with the StackExchange API in an upcoming article.

With so many web APIs out there, learning the basics can prove quite valuable. Take a look around your environment. Do you have any tedious manual tasks with products that have a web API? Try to build functions or modules for these!

Our time is up - two important closing notes:

  • Does your vendor provide a web API, but no PowerShell module? Remind them that an official PowerShell module would be very helpful.
  • Share your work with the community! If you start writing a module for product X and publish it on GitHub, you might find folks willing to contribute and improve your module. If we all write our own modules and keep them private, no one wins.


Official Entry

Whenever I'm given a REST API to work with, I often try to just hit the URL in my web browser. So I just ran over to The result looked a lot like JSON - JavaScript Object Notation - and I'll freely admin that being able to distinguish between JSON and XML helped me a lot. Those are the two formats most web services will return.

Knowing that, I ran a quick Get-Help *JSON* in the shell and was rewarded with ConvertFrom-JSON.

Invoke-WebRequest | ConvertFrom-JSON

And that's essentially the answer. The JSON gets converted into PowerShell objects, and since there are only four properties it'll display in a table by default. Admittedly, to get the exact output from the puzzle, I had to add Format-Table -AutoSize to the end.

Turning this into an advanced function is equally straightforward, since you're really just wrapping some structure around the working answer.

function Get-GeoInformation {
    Invoke-WebRequest | ConvertFrom-JSON

So, there it is. Yeah - I admitted comment-based help here, mainly to save space, but you hopefully get the idea! No Format-Table here, because functions shouldn't output pre-formatted text - doing so makes the data useless for anything else.

Your Reactions

First, please note that you need to post your code as a Gist, not just in a GitHub repo. The comment system will only retrieve actual Gists. Thanks.

Christian Sandaled came up with a much prettier advanced function, taking the time to include comment-based help, and allowing you to input any arbitrary IP address. That was definitely above and beyond, and it's a really well-written function, too. Jeff Bunting came up with something similar, also going so far as to allow for arbitrary IP address lookup. Good job, both of you! Others jumped in with similar solutions, adding error handling, and so on - definitely in the right spirit. Some folks - Adi, looking at you, friend - missed only slightly, by using a nonstandard function name. But still, great work.

The PowerShell Bear (grr) doubled down on difficulty by writing a PowerShell v2-compatible solution, which took Invoke-WebRequest off the table. Instead, the Bear used the underlying .NET Framework classes to do the job. He also had to manually unwind the JSON, as ConvertFrom-JSON was also unavailable in v2. Wow!

I didn't actually see any "bad" entries this month, which was extremely gratifying! It shows how much variation and individuality answers can have, while still adhering to good practices and form. And there's more than a few interesting new web services you'll discover in the comments, too!


Final PowerShell Summit NA 2015 Inventory Available

An additional 35 seats have been opened for PowerShell Summit NA 2015, as of 10am PT 2015-01-11. This will be the final block of inventory available for the event, which will host around 140 total people.

If you click over and there are no registration options, it's because it sold out.

Skip to toolbar