
close
close
We have been on a journey of PowerShell exploration. Over the last few articles, we’ve gone from a few lines of PowerShell code that you could type at the prompt to a reusable PowerShell function. Be sure to get caught up if you are joining us partway through the journey.
Remember the end result isn’t as important as what you learn along the way. The last version I showed of the Get-MyUptime command is pretty complete, but there are a few more final touches that we can apply. Let me show you the next version and then I’ll explain.
<# .Synopsis Get computer uptime. .Description This command will query the Win32_OperatingSystem class using Get-CimInstance and write an uptime object to the pipeline. .Parameter Computername The name of the computer to query. This parameter has an alias of CN. The computer must be running PowerShell 3.0 or later. .Parameter Test use Test-WSMan to verify computer can be reached and is running v3 or later of the Windows Management Framework stack. .Example PS C:\> get-myuptime chi-dc01 Computername : CHI-DC01 LastRebootTime : 11/15/2014 12:02:22 AM Days : 23 Hours : 17 Minutes : 23 Seconds : 14 Default output for a single computer. .Example PS C:\> get-myuptime chi-dc01,chi-fp02 | format-table Computername LastRebootTime Days Hours Minutes Seconds ------------ -------------- ---- ----- ------- ------- CHI-DC01 11/15/2014 12:02:22 AM 23 17 23 44 CHI-FP02 12/1/2014 8:40:08 AM 7 8 45 58 Formatted results for multiple computers. You can also pipe computer names into this command. .Notes Last Updated: December 8, 2014 Version : 1.0 Learn more about PowerShell:Essential PowerShell Learning Resources.Link Get-Ciminstance Get-WMIObject .Link https://petri.com/powershell #> [cmdletbinding()] Param( [Parameter(Position=0,ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullorEmpty()] [Alias("cn","name")] [String[]]$Computername = $env:Computername, [Switch]$Test ) Begin { Write-Verbose "Starting $($MyInvocation.Mycommand)" #define a private function to be used in this command Function IsWsManAvailable { [cmdletbinding()] Param([string]$Computername) Write-Verbose "Testing WSMan for $computername" Try { $text = (Test-WSMan $computername -ErrorAction Stop).lastchild.innertext Write-Verbose $Text if ($text.Split(":")[-1] -as [double] -ge 3) { $True } else { $False } } Catch { #failed to run Test-WSMan most likely due to name resolution or offline $False } } } #begin Process { Foreach ($computer in $computername) { Write-Verbose "Processing $($computer.toUpper())" if ($Test -AND (IsWsManAvailable -Computername $computer)) { $OK = $True } elseif ($Test -AND -Not (IsWsManAvailable -Computername $computer)){ $OK = $False Write-Warning "$($Computer.toUpper()) is not accessible" } else { #no testing so assume OK to proceed $OK = $True } #get uptime of OK if ($OK) { Write-Verbose "Getting uptime from $($computer.toupper())" Try { $Reboot = Get-CimInstance -classname Win32_OperatingSystem -ComputerName $computer -ErrorAction Stop | Select-Object -property CSName,LastBootUpTime } Catch { Write-Warning "Failed to get CIM instance from $($computer.toupper())" Write-Warning $_.exception.message } if ($Reboot) { Write-Verbose "Calculating timespan from $($reboot.LastBootUpTime)" #create a timespan object and pipe to Select-Object New-TimeSpan -Start $reboot.LastBootUpTime -End (Get-Date) | Select-Object @{Name = "Computername"; Expression = {$Reboot.CSName}}, @{Name = "LastRebootTime"; Expression = {$Reboot.LastBootUpTime}},Days,Hours,Minutes,Seconds } #if $reboot } #if OK #reset variable so it doesn't accidentally get re-used, especially when using the ISE #ignore any errors if the variable doesn't exit Remove-Variable -Name Reboot -ErrorAction SilentlyContinue } #foreach } #process End { Write-Verbose "Ending $($MyInvocation.Mycommand)" } #end } #end function
The first major change is that I inserted comment-based help. Even though it is a comment, PowerShell will process it. Here’s an outline of the commonly used sections.
advertisment
.Synopsis .Description .Parameter XXX .Example .Notes .Link #>
At minimum I would suggest using Synopsis, Description, and one example. Be careful with your formatting and spelling, as there is a period before each entry. Some people put the heading in upper case to make it easier to read. As long as it is spelled properly, PowerShell doesn’t care. Unfortunately, if there is a typo in one of the headings or syntax error, then PowerShell won’t display an error, nor will it show your help content. The headings are the same that you see in cmdlet help. When you write the description, you don’t have to worry about line breaks. I tend to write the description in the ISE until I reach the logical end of a paragraph. It will be a long series of lines in the ISE, but when PowerShell displays the help, then it will automatically format line length for me.
PowerShell will detect and automatically display your parameters and syntax. Additionally, you can include a parameter part to your help. In my function, I have a parameter help definition for Computername.
You can also have as many examples as you'd like. You don't need to number them, just add example sections. For example, I include the PS prompt in my example. It isn't required, but if you only insert your command, the help system will generate a prompt of C:\PS>, which looks like the PS directory to me. Personally, I prefer my example prompt to look like my console: PS C:\>. The link section is for related cmdlets. You can have as many cmdlet names as you want under a single Link heading. If you want to provide a link to online help for something, put that in a separate link. When you are finished, the person running your command can ask for help like any other command.They can even use help's Show-Window parameter.Related links for Windows PowerShell cmdlets. (Image Credit: Jeff Hicks)
Next, let's look at a few other changes I made to make this even more resilient and easy to use. The heart of the function is the Get-CimInstance command. I am using Try/Catch statement to properly handle errors. One of the drawbacks with my previous version that it wasn't always easy to tell which error message went with which computer name. So instead of simply writing the error object, I like to use Write-Warning.Help for Get-MyUptime cmdlet. (Image Credit: Jeff Hicks)
Catch { Write-Warning "Failed to get CIM instance from $($computer.toupper())" Write-Warning $_.exception.message }
The first line will let me know what computer failed, and the second will display the relevant error message. I rarely need the complete exception object. But if I did, I can still find it in the global $error variable.
My other major change is to add a parameter and some logic to test the computer before I attempt to use Get-CimInstance. You can think of this process as pre-error handling. Again, the method in which I’m using it is more important than the end result. I could have used Test-Connection to ping the computer, but Get-CimInstance really needs the remote computer to be running PowerShell 3.0 or later. I can get that information using Test-WSMan.
Using Test-WSMan in Windows PowerShell. (Image Credit: Jeff Hicks)
Function IsWsManAvailable { [cmdletbinding()] Param([string]$Computername) Write-Verbose "Testing WSMan for $computername" Try { $text = (Test-WSMan $computername -ErrorAction Stop).lastchild.innertext Write-Verbose $Text if ($text.Split(":")[-1] -as [double] -ge 3) { $True } else { $False } } Catch { #failed to run Test-WSMan most likely due to name resolution or offline $False } }
I’m not using a standard naming convention because this is a function that I only intend to use within Get-MyUptime. It has to be defined before I can call it so I insert it into the begin block. This defines the function before any pipelined names are processed. Within the process block, I’ve written logic to determine if the computer has the proper version, assuming the person running the script used the –Test parameter.
if ($Test -AND (IsWsManAvailable -Computername $computer)) { $OK = $True } elseif ($Test -AND -Not (IsWsManAvailable -Computername $computer)){ $OK = $False Write-Warning "$($Computer.toUpper()) is not accessible" } else { #no testing so assume OK to proceed $OK = $True }
It is possible that a computer could test OK for WSMan, but still fail with Get-CimInstance, which is why I still have error handling with Try/Catch. Let’s put it all to the test.
advertisment
$c = "chi-dc01","chi-dc02","chi-dc04","foo","chi-fp02","chi-core01" get-myuptime $c | sort Computername | format-table
Warning in Windows PowerShell. (Image Credit: Jeff Hicks)
get-myuptime $c -test | sort days,computername | format-table -group Days -property Computername,LastRebootTime, @{Name="Uptime";Expression={new-timespan -Days $_.Days -hours $_.Hours -Minutes $_.Minutes}}
In this example I also decided to group the results by the number of days, and I turned the uptime back into a string.
Grouping results by number of days in Windows PowerShell. (Image Credit: Jeff Hicks)
start-job { get-adcomputer -filter * | select name | Get-MyUptime -test} -InitializationScript {. c:\scripts\get-myuptime.ps1} -Name Uptime receive-job uptime –keep | Select * -exclude RunspaceID | Out-GridView -title "Domain Uptime"
When I receive the job I’ll get warnings for computers that failed the test, but the grid view will have my results.
Domain Uptime. (Image Credit: Jeff Hicks)
More from Jeff Hicks
advertisment
Petri Newsletters
Whether it’s Security or Cloud Computing, we have the know-how for you. Sign up for our newsletters here.
advertisment
More in PowerShell
Microsoft’s New PowerShell Crescendo Tool Facilitates Native Command-Line Wraps
Mar 21, 2022 | Rabia Noureen
Most popular on petri
Log in to save content to your profile.
Article saved!
Access saved content from your profile page. View Saved
Join The Conversation
Create a free account today to participate in forum conversations, comment on posts and more.
Copyright ©2019 BWW Media Group