Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET Coming Soon: GET:IT Endpoint Management 1-Day Conference on September 28th at 9:30 AM ET
PowerShell

Create a PowerShell Function to Display System Uptime

Recently I saw a post on Twitter about a PowerShell script to display system uptime. Since I’m always curious to see how people are using PowerShell I followed the link the script, which was posted on the TechNet Script Gallery site. As expected the author used Windows Management Instrumentation (WMI) to query the Win32_OperatingSystem class.

This class includes a property that indicates when the computer last booted. If you use Get-CIMInstance, this value is automatically formatted into as a datetime value. I appreciated that he used a Timespan object to display the uptime. But I did have one comment about his code, and something that I see quite often. His original code may be updated by the time you read this. By the way, the author was very receptive to my suggestions and open to me about using his code as a learning device, as he is still learning PowerShell. Even if you don’t have a need for the end result, the process I want to explore from command to tool should be very useful.

To begin, here’s the original code.

$Reboot = Get-CimInstance Win32_OperatingSystem -ComputerName chi-dc04 | Select-Object CSName,LastBootUpTime 
$date = Get-Date
New-TimeSpan -Start $Reboot.LastBootUpTime -End $Date | 
Select-Object @{Label = "System Name"; Expression = {$Reboot.CSName}},
@{Label = "Last Reboot Time"; Expression = {$Reboot.LastBootUpTime}},Days,Hours,Minutes,Seconds | 
Format-Table -AutoSize

PowerShell Function to Display System Uptime

Sponsored Content

Say Goodbye to Traditional PC Lifecycle Management

Traditional IT tools, including Microsoft SCCM, Ghost Solution Suite, and KACE, often require considerable custom configurations by T3 technicians (an expensive and often elusive IT resource) to enable management of a hybrid onsite + remote workforce. In many cases, even with the best resources, organizations are finding that these on-premise tools simply cannot support remote endpoints consistently and reliably due to infrastructure limitations.

Creating a PowerShell Function

The one major drawback to this example is that it includes formatting via the Format-Table cmdlet. This code can be turned into a useful tool, which is what I intend to show you, but including formatting limits you as you’ll see in a moment. First, let’s turn this into a simple function. I have a set of commands that work just fine from the prompt. I can easily paste them inside a function, turn the computername into a parameter and I’m done!

Function Get-MyUptime {

Param([string]$Computername = $env:COMPUTERNAME)

$Reboot = Get-CimInstance Win32_OperatingSystem -ComputerName $computername | 
Select-Object CSName,LastBootUpTime

$Date = Get-Date

New-TimeSpan -Start $Reboot.LastBootUpTime -End $Date | 
Select-Object @{Label = "System Name"; Expression = {$Reboot.CSName}},
@{Label = "Last Reboot Time"; Expression = {$Reboot.LastBootUpTime}},Days,Hours,Minutes,Seconds | 
Format-Table -AutoSize
   
} #end function

A quick word on names: don’t re-invent the wheel. You should be able to use a common parameter name that you see elsewhere. If your parameter needs to reflect the name of a computer, then use Computername since that is commonly used in other cmdlets. Don’t use something like System or MachineName. Doing so makes your command less intuitive. If you need a parameter like SysName say to support your corporate culture, you can add it as an alias. You’ll see an example of that later. Aren’t sure about your parameter name choice? Ask PowerShell for other cmdlets that might be using it:

get-command -ParameterName computername

The name of your command should use one of the standard verbs. Run the Get-Verb cmdlet to discover them. The noun should be singular and reflective of the “thing” you are working with. To avoid possible naming collisions you might consider using a noun prefix to reflect you or your organization.

With my function, it becomes much easier to get system uptime.

get-myuptime "chi-dc01"
"chi-dc01","chi-dc04","chi-core01" |  foreach {Get-MyUptime $_}

For the most part, this looks OK.

But what happens when you try to do something like sort the output?

"chi-dc01","chi-dc04","chi-core01" | foreach {Get-MyUptime $_} | Sort Days,Hours,Minutes

Remember, the default sort is ascending which isn’t what I got.

Why? Ask PowerShell.

get-myuptime "chi-dc01" | get-member

This is why you never include format cmdlets in our scripts and functions. They write formatting objects to the pipeline. The only thing you can do is pipe to Out-File or Out-Printer. Instead, write your function so that it writes objects to the pipeline. Then if you need to format it, run the command with Format-Table. Maybe tomorrow you need Format-List or to export to a CSV file. If you include formatting inside your function, you’re done. That’s all your command will ever be able to do.

With that in mind here’s my revised function.

Function Get-MyUptime {

Param([string[]]$Computername = $env:COMPUTERNAME)

$Reboots = Get-CimInstance Win32_OperatingSystem -ComputerName $computername | Select-Object CSName,LastBootUpTime
$Date = Get-Date

Foreach ($reboot in $reboots) {
    New-TimeSpan -Start $Reboot.LastBootUpTime -End $Date | 
    Select-Object @{Name = "SystemName"; Expression = {$Reboot.CSName}},
    @{Name = "LastRebootTime"; Expression = {$Reboot.LastBootUpTime}},Days,Hours,Minutes,Seconds 
}
   
} #end function

While I was removing Format-Table I made a few other changes. First, I modified the Computername parameter so it could accept a collection of computer names.

Param([string[]]$Computername = $env:COMPUTERNAME)

The parameter is expecting a string and inserting [] tells PowerShell to expect multiple values, usually separated by commas. Now I can run the command like this:

get-myuptime "chi-dc01","chi-dc04","chi-core01"

This works because the Computername parameter from Get-CimInstance, also takes an array of strings for a computer name. This also means that when this part of the function runs:

$Reboots = Get-CimInstance Win32_OperatingSystem -ComputerName $computername | Select-Object CSName,LastBootUpTime

The $Reboots variable will be an array or collection of CIM instances. This means I will need to process each one individually in order to get the uptime. That’s why I’m using a ForEach enumerator.

Foreach ($reboot in $reboots) {
    New-TimeSpan -Start $Reboot.LastBootUpTime -End $Date | 
    Select-Object @{Name = "SystemName"; Expression = {$Reboot.CSName}},
    @{Name = "LastRebootTime"; Expression = {$Reboot.LastBootUpTime}},Days,Hours,Minutes,Seconds 
}

If you compare these two versions closely you’ll see that I changed the name of the variable holding the Get-CimInstance command from $Reboot to $Reboots. I did this so that I wouldn’t have to revise the timespan code that already worked. I simply wrapped it in a ForEach enumerator and used the same variable name.

I also changed the custom hash table in Select-Object that defines new properties. While you can use Label and Name interchangeably, I prefer to use Name because it helps remind me that I am defining a property name and not a formatting label. That is also why I modified the names to remove the spaces. My function is going to write an object to the pipeline and it is much easier to work with property names without spaces.

My result is a simple object

Need formatting? Use PowerShell.

Now that I have objects, I can sort, filter or whatever I need.

get-myuptime "chi-dc01","chi-dc04","chi-core01" | 
where {$_.days -le 7} | 
Sort LastRebootTime | 
format-table

I want to only see servers that have been up for 7 days or less, sorted on the last reboot time.

Look how far we’ve come from a few lines of PowerShell commands to a re-usable function. And we’re not done yet! There’s much more that I want to show you but that will have to wait for another article. In the meantime, I would encourage you to get your PowerShell functions at least to this level. Think writing objects to the pipeline and leave formatting to PowerShell.

Related Topics:

BECOME A PETRI MEMBER:

Don't have a login but want to join the conversation? Sign up for a Petri Account

Register
Comments (5)

5 responses to “Create a PowerShell Function to Display System Uptime”

  1. LastBootUpTime conversion is based on local machine time zone, thus if querying a remote host in another time zone you’ll need to account for time zone bias (assuming LastRebootTime is expected to be in the same time zone as the host).

  2. I keep forgetting that as I always have everything in the same time zone. One thing you can do is that instead of using Get-Date, use the LocalDateTime property from the Win32_OperatingSystem class.

    $Reboot = Get-CimInstance Win32_OperatingSystem -ComputerName $computername |
    Select-Object CSName,LastBootUpTime,LocalDateTime
    $Date = $reboot.LocalDateTime

    Now all calculations are relative to the local server.

    • Function Get-MyUptime {
      Param([string[]]$Computername)

      $Boot = Get-CimInstance Win32_OperatingSystem -ComputerName $computername | Select-Object CSName,LastBootUpTime
      $ComputerTimeZone = Get-CimInstance -Class win32_timezone -ComputerName $computername
      $LocalTimeZone = Get-CimInstance -Class win32_timezone
      $Date = Get-Date

      $bootenum = $boot.getenumerator()
      $tzenum = $ComputerTimeZone.getenumerator()

      while($bootenum.MoveNext() -and $tzenum.MoveNext()) {
      New-TimeSpan -Start $bootenum.Current.LastBootUpTime -End $Date |
      Select-Object @{Label = “System Name”; Expression = {$bootenum.Current.CSName}},
      @{Name = “Last Boot Time”; Expression = {$bootenum.Current.LastBootUpTime.AddMinutes($tzenum.current.Bias – $LocalTimeZone.Bias)}},
      @{Name = “System Time Zone”; Expression = {$($tzenum.current.Caption)}},Days,Hours,Minutes,Seconds
      }

      }#end function

  3. I’m posting again, in case it didn’t take the first time, the re-direct from Google did not bring me back here.

    Anyway, I have a similar function, except that I look in a particular computer OU for computer names, if the Flag -AllServers is $false, it will only check uptime on the local host:

    Here is my function:

    function Get-MyServersUptime([bool]$AllServers) {
    $hostname=$AllServers
    $myhostname = hostname
    function MUpTime {
    $lastboottime = (Get-WmiObject -Class Win32_OperatingSystem -computername $computer).LastBootUpTime
    $sysuptime = (Get-Date) – [System.Management.ManagementDateTimeconverter]::ToDateTime($lastboottime)
    Write-Host “$computer has been up for: ” $sysuptime.days “days” $sysuptime.hours “hours” $sysuptime.minutes “minutes” $sysuptime.seconds “seconds”
    }
    clear
    if ($hostname) {
    if ($hostname -eq $true ) {
    $VMS = Get-ADComputer -Filter * -SearchBase ‘OU=TS Servers,DC=MyDomain,DC=local’ | %{$_.Name}
    foreach ($computer in $VMS) {
    MUpTime
    }
    } else {
    $computer = $hostname
    MUpTime
    }
    } else {
    $computer = $myhostname
    MUpTime
    }
    }

Leave a Reply

Live Webinar: Active Directory Security: What Needs Immediate Priority!Live on Tuesday, October 12th at 1 PM ET

Attacks on Active Directory are at an all-time high. Companies that are not taking heed are being punished, both monetarily and with loss of production.

In this webinar, you will learn:

  • How to prioritize vulnerability management
  • What attackers are leveraging to breach organizations
  • Where Active Directory security needs immediate attention
  • Overall strategy to secure your environment and keep it secured

Sponsored by: