Find last boot up time of remote Windows computers using WMI

From Svendsen Tech PowerShell Wiki
Jump to: navigation, search

Here are a bunch of ways to determine the last boot up time of remote Windows computers, using WMI/CIM (and via PSRemoting).

I added the obvious, basic examples below, and an example function, for those with simpler needs.

I researched the topic a little before writing this article, and I saw various more or less sane ways of determining the last boot-up time, but what I'll be demonstrating here is with WMI, CIM and PSRemoting from PowerShell, which I think should be good approaches.

See this article for how to get computer names from an OU in AD - or the entire AD.


A Few Simple Examples

Here's a simple example against the remote host "winxpssd":

PS C:\> $LastBootUpTime = Get-WmiObject Win32_OperatingSystem -Comp winxpssd | Select -Exp LastBootUpTime
PS C:\> [System.Management.ManagementDateTimeConverter]::ToDateTime($LastBootUpTime)

Wednesday, June 13, 2012 3:10:45 AM

And we see that the XP workstation was last booted on Wednesday, June 13th, 2012.

Remember that you can add "-ComputerName server1, server2, server3" to gwmi/Get-WmiObject in all these examples.

You can also do it like this, since the WMI object will have a ConvertToDateTime method:

PS C:\> $wmi = gwmi win32_operatingsystem
PS C:\> $wmi.ConvertToDateTime($wmi.LastBootUpTime)

Friday, August 22, 2014 4:39:23 AM


PS C:\>

Or this cute one-liner approach:

PS C:\> gwmi win32_operatingsystem | %{ $_.ConvertToDateTime($_.LastBootUpTime) }

Thursday, October 22, 2015 11:16:37

Or why not even a "dummy" WMI object:

PS C:\> ([wmi]'').ConvertToDateTime((gwmi win32_operatingsystem).LastBootUpTime)

Wednesday, January 13, 2016 12:56:51

WMI via PowerShell remoting:

PS C:\temp> $res3 = Invoke-Command -Cn vista64esxi, 2008r2esxi `
  -Command { (gwmi win32_operatingsystem).lastbootuptime }

PS C:\temp> $res3 | foreach { ([wmi]'').ConvertToDateTime($_) }

Thursday, February 11, 2016 3:13:19 AM
Wednesday, February 10, 2016 6:41:00 AM

With PSv3 (Windows Management Framwork 3.0) and up, you can also use the Get-CimInstance, Get-CimClass and Get-CimSession cmdlets. Here's a basic example with Get-CimInstance, that works almost equivalently to Get-WmiObject. Get-Cim* cmdlets use WSMAN, meaning they should work without WMI access, so long as PSRemoting is set up. Notice how the dates are nicely formatted for you without any effort (progress, yay).

PS C:\> $BootTimes = Get-CimInstance -Cn server2012, win2012r2 -Class Win32_OperatingSystem |
    Select PSComputerName, LastBootUpTime

PS C:\> $BootTimes | Format-Table -AutoSize

PSComputerName LastBootUpTime       
-------------- --------------       
server2012     6/15/2016 5:46:32 PM 
win2012r2      6/18/2016 10:52:53 PM

What if you don't have WMI access, but PsExec works? Invoke-PsExec to the rescue!

PS C:\temp> . C:\Dropbox\PowerShell\Invoke-PsExec\Invoke-PsExec.ps1

PS C:\temp> $cred = Get-Credential
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:

PS C:\temp> $res = Invoke-PsExec -Cn vista64esxi, 2008r2esxi2 -HideSummary `
  -Command '(gwmi win32_operatingsystem).lastbootuptime' -IsPSCommand `
  -Credential $cred 
  

PS C:\temp> $res | ft -AutoSize ComputerName, @{n='LastBootUpTime'; e={([wmi]'').ConvertToDateTime($_.STDOUT)}}

ComputerName LastBootUpTime     
------------ --------------     
2008r2esxi2  2016-02-13 19:06:26
vista64esxi  2016-02-10 06:41:00

Oh, wait, the remote computer doesn't even have PowerShell and WMI doesn't work?! Unless you can Invoke-PsExec up, you might be out of luck. If PsExec does work, you could roll something like the following, where you use wmic.exe remotely, and parse the results using PowerShell locally.

PS C:\temp> $res2 = Invoke-PsExec -Cn vista64esxi, 2008r2esxi2 -HideSummary `
  -Command 'wmic os get lastbootuptime /format:list' -Credential $cred

PS C:\temp> $res2 | ft -AutoSize ComputerName, @{n='LastBootUpTime'; `
  e={ ($_.STDOUT | ?{$_} | %{ ([wmi]'').ConvertToDateTime($_.Split('=')[1].Trim()) } ) } }

ComputerName LastBootUpTime     
------------ --------------     
2008r2esxi2  2016-02-13 19:06:26
vista64esxi  2016-02-10 06:41:00

A Simple PowerShell Function For Checking Last Boot Up Time Using WMI

The -ComputerName parameter for Get-WmiObject accepts an array of strings, but if one of them has an error, I haven't found a way of connecting the results with the computer names it succeeded for (NB! Also see "alternate method in retrospect" below, but you need PSv3 for that). I think I'm missing something here, but considering this, a very basic, generic function for this that you could put in your profile, or dot-source when needed, might look something like this:

<#
.SYNOPSIS
    Get the last boot up time of a remote Windows computer via WMI.

.PARAMETER ComputerName
    Target host or hosts to retrieve the last boot up time for.
#>

function Get-LastBootUpTime {
    param([Parameter(Mandatory=$true)][string[]] $ComputerName)
    foreach ($Computer in $ComputerName) {
        New-Object psobject -Property @{
            ComputerName = $Computer
            LastBootUpTime = [Management.ManagementDateTimeConverter]::ToDateTime( (Get-WmiObject -Class Win32_OperatingSystem -Computer $Computer | Select -Exp LastBootUpTime) )
        }
    }
}

To use it, you'd do something like this, where you first dot-source the script to get the function in the current scope, and then use the function:

PS C:\temp> . .\PowerShell\Get-LastBootUpTime.ps1
PS C:\temp> Get-LastBootUpTime -ComputerName 2008r2esxi, winxpssd, win2k | ft -a

ComputerName LastBootUpTime
------------ --------------
2008r2esxi   8/29/2014 3:16:43 AM
winxpssd     7/15/2014 2:30:16 PM
win2k        6/9/2014 3:47:13 PM

Getting both reboot time and "uptime"

Here is another example function where I also get the "uptime" as a time span object showing how many days, hours, minutes, and seconds the computer has been up.

<#
.SYNOPSIS
    Get the last boot up time of a remote Windows computer via WMI.

.PARAMETER ComputerName
    Target host or hosts to retrieve the last boot up time for.
#>
function Get-LastBootUpTime {
    param(
        [Parameter(Mandatory=$true)][Alias('Cn')][string[]] $ComputerName,
        [System.Management.Automation.Credential()] $Credential = [System.Management.Automation.PSCredential]::Empty)
    foreach ($Computer in $ComputerName) {
        $WmiHash = @{
            ComputerName = $Computer
            ErrorAction = 'Stop'
            Class = 'Win32_OperatingSystem'
        }
        if ($Credential.Username -match '\S') {
            $WmiHash.Credential = $Credential
        }
        try {
            if ($LastBootUpTime = Get-WmiObject @WmiHash | Select -ExpandProperty LastBootUpTime -ErrorAction SilentlyContinue) {
                $LastBootUpTime = [Management.ManagementDateTimeConverter]::ToDateTime($LastBootUpTime)
                New-Object PSObject -Property @{
                    ComputerName = $Computer
                    LastBootUpTime = $LastBootUpTime
                    UpTime = [datetime]::Now - $LastBootUpTime
                }
            }
            else {
                Write-Warning -Message "Failed to retrieve last boot up time for $Computer (unknown failure)."
            }
        }
        catch {
            Write-Warning -Message "Failed to execute WMI query against ${Computer}: $($_.Exception.Message)"
        }
    }
}

And here's an example run:

Last-reboot-time-and-uptime-example-function.png

Alternate Way In Retrospect

I have since writing this above found a way to get the computername associated with the data despite errors. There's a "hidden" PSComputerName property, but it does not exist in PowerShell version 2, only version 3 and up.

PS C:\> gwmi win32_operatingsystem -comp vista64esxi, esxi, 2008r2esxi2, 2008r2esxi -Credential $Cred |
    select PSComputerName, @{n='BootTime';e={$_.ConvertToDateTime($_.LastBootupTime)}}

PSComputerName                                                   BootTime
--------------                                                   --------
VISTA64ESXI                                                      2015-10-15 06:30:13

gwmi : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
At line:1 char:1
+ gwmi win32_operatingsystem -comp vista64esxi, esxi, 2008r2esxi2, 2008r2esxi -Cre ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], COMException
    + FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

2008R2ESXI2                                                      2015-10-17 23:30:40
2008R2ESXI                                                       2015-10-15 03:07:57

In PSv3 and up, you can use "-ErrorVariable SomeVarName", and you can then look at the "TargetObject" property from those objects to see which ones failed. There's a TargetObject property in the error object in v2 as well, but it's empty when I test...

You can also use "-ErrorAction SilentlyContinue" on gwmi/Get-WmiObject to suppress errors.

Multiple Target Computers

NB. I would word this differently in retrospect. You can produce CSV reports using all the methods above as well, but the method described in this section scales well for large environments with many computers, and gives the capability to get a nuanced report. Serializing WMI is necessary when you need a "snapshot" (from a window in time), and that's what this does.

  • -

Now to the slightly more interesting part where I demonstrate how to process a bunch of remote computers and create a simple report of when they were last booted. I promote my own Get-WmiObject-Wrapper script, which is really quite ideal for this purpose. It is written as both a serial version, and an asynchronous version using PowerShell runspaces. The asynchronous version in my experience processes thousands of servers in a few minutes, and the majority of the time is actually spent writing the resulting XML file afterwards.

You can download the script from the above link. The documentation there is fairly extensive, but if you follow this guide you should be good to go without too much hassle.

  • Download the files at the link above and put them in your working directory.
  • Retrieve target computer names from AD, if necessary, and put them in a text file or a variable holding an array of target computer names. If you have a text file of target computers, use the parameter "-ComputerName (gc .\hosts.txt)".
  • Retrieve the desired data using the WMI wrapper script against target hosts.

Here's a complete example in a screenshot (PowerShell version 3 is required for the first line's syntax to work):

Last-boot-up-time-wmi-async-example.png

Get Data In XML Format

First I get all the computers in my little lab AD and then I collect data with the Get-WmiObject-Wrapper script, using the asynchronous version.

PS C:\PS> Import-Module ActiveDirectory
PS C:\PS> $Comps = Get-ADComputer -filter '*' | Select -Exp Name
PS C:\PS> $Comps.Count
19

PS C:\PS> .\Get-WmiObject-Wrapper-Async.ps1 -Computer $Comps -OutputFile boottime.xml
         -MultiClassProperty 'Win32_OperatingSystem:LastBootUpTime' -InvokeAsync 20

Script start time: 09/08/2013 10:29:39
Starting jobs... 09/08/2013 10:29:40
Finished starting jobs. 09/08/2013 10:29:42
Waiting for jobs to finish...
Jobs should have finished. 09/08/2013 10:29:52
Disposed job objects. 09/08/2013 10:29:52

Successfully saved 'boottime.xml'
Script start time: 09/08/2013 10:29:39
Script end time:   09/08/2013 10:29:52

Exposed data hash as $Global:WmiData.
Access it with "$WmiData.GetEnumerator()" from the shell.

PS C:\PS> $WmiData.Keys.Count
19

Now we have boottime.xml which contains the collected data, error messages and "no ping reply" messages.

Create A CSV Report Of Retrieved Boot Times

This creates a CSV report of the target computers from which we were successfully able to retrieve the last boot-up time. If you use the property "NoPing", you get computers that did not respond to ping - and if you use "Error", you get computers that had WMI errors.

PS C:\PS> .\Gwmi-Wrapper-Report.ps1 .\boottime.xml -Property LastBootUpTime -AsCsv |
         Set-Content -Encoding utf8 boottime.csv

You can then process it with Import-Csv and manipulate it in the usual ways. Here I convert the unfriendly date string to a regular [DateTime] object, sort it by this date/time and export it to a new CSV file with "clean dates", and then have a look at it with Import-Csv (ipcsv).

PS C:\PS> Import-Csv .\boottime.csv | Select ComputerName, @{n='LastBoot';
    e={[Management.ManagementDateTimeConverter]::ToDateTime($_.Value)}} |
    Sort 'LastBoot' | Export-Csv -enc UTF8 boottime-clean_date.csv

PS C:\PS> ipcsv .\boottime-clean_date.csv | ft -auto

Computer    Last boot
--------    ---------
2008R2ESXI  8/14/2013 3:45:08 AM
SERVER2012  8/14/2013 6:44:21 AM
WIN8ESXI    8/14/2013 6:44:42 AM
2008R2ESXI2 8/14/2013 6:46:02 AM
SIEMENS     8/14/2013 6:57:55 AM
SERVER2008  8/14/2013 7:01:56 AM
SS-WIN7     8/14/2013 7:10:27 AM
WINXPSSD    8/14/2013 7:33:16 AM
VISTA64ESXI 8/15/2013 6:30:34 AM
WIN2K       9/8/2013 12:13:39 PM