Get Virtual Machine Last On and Off Time

Table of contents

In the past I’ve written about how to identify old or obsolete virtual machines based on the time stamp of the associated VHD or VHDX file. The PowerShell techniques in that script get the job done and there’s nothing necessarily wrong with it, although it does make an assumption that you can reach the disk file remotely. But, as I was researching a problem, which will be the subject of another article, I discovered a new way to identify when a virtual machine was last turned on. In fact, I also found how to find when the virtual machine was last turned off and when its state changed, say from running to paused. The key is in the virtual machine configuration file.

Virtual machine configurations are stored as XML files. The file name is the same as the virtual machine’s guid, or ID. You can use the Get-VM cmdlet in PowerShell to discover the ID and location.

PS C:> get-vm chi-client02 -ComputerName chi-hvr2 | select id,configurationlocation

Id                                                          ConfigurationLocation
--                                                          ---------------------
6164b819-d828-425c-823a-561d96ec0975                        C:VMCHI-Client02

The location is relative to the Hyper-V server, or in my case CHI-HVR2. Beneath that location there should be a folder called Virtual Machines. Here’s how I can use PowerShell remoting to see the files.

PS C:> invoke-command { dir 'c:vmchi-client02virtual machines'} -ComputerName chi-hvr2

    Directory: C:vmchi-client02virtual machines

Mode        LastWriteTime          Length Name                                     PSComputerName
----        -------------          ------ ----                                     --------------
d----       4/7/2014  11:00 AM            6164B819-D828-425C-823A-561D96EC0975     chi-hvr2
-a---       4/8/2014  12:17 PM      58408 6164B819-D828-425C-823A-561D96EC0975.XML chi-hvr

Now, I am now so much interested in the time stamp on the xml file as I am as to what is inside it. It turns out that Hyper-V records values for when the virtual machine was last on, last off and when its state last changed. As far as I can tell that information is not surfaced anywhere in the Hyper-V Manager or even through PowerShell. Fortunately, PowerShell and XML play well together. Let me get the remote xml file and save it locally as an XML document variable.

PS C:> $vm = get-vm chi-client02 -ComputerName chi-hvr2
PS C:> [xml]$config = invoke-command { get-content "$($using:vm.configurationlocation)Virtual Machines$($using:vm.id).xml"} -ComputerName $vm.computername
PS C:> $config

xml                                                         configuration
---                                                         -------------
version="1.0" encoding="UTF-16" standalone="yes"            configuration

Now to find those last settings.

PS C:> $config | select-xml -XPath "//last_powered_on_time|//last_powered_off_time|//last_state_change_time"

Node                                    Path                                    Pattern
----                                    ----                                    -------
last_powered_off_time                   InputStream                             //last_powered_on_time|//last_powere...
last_powered_on_time                    InputStream                             //last_powered_on_time|//last_powere...
last_state_change_time                  InputStream                             //last_powered_on_time|//last_powere...

That’s what we’re after. Note that the node names are case-sensitive because this is XML. But what is the value?

PS C:> $config | select-xml -XPath "//last_powered_on_time" | select -ExpandProperty node

type                                                        #text
----                                                        -----
integer                                                     130413564635668426

The #text property indicates when this virtual machine was last powered on. But I’m betting you have no idea what that value really means. It is the number of ticks since December 31, 1600 11:59PM. No, that is not a typo. But it doesn’t matter because we can still represent that date in PowerShell.

PS C:> [datetime]$d = "12/31/1600 11:59:59 PM"

The reason is so that we can add the number of ticks to $d to get the last power on time.

PS C:> $ticks = ($config | select-xml -XPath "//last_powered_on_time").node.'#text' 
PS C:> $d.AddTicks($ticks)

Monday, April 7, 2014 3:01:02 PM

I enclosed the #text property in single quotes because of the # character. But we’re done, right? It turns out that date is in universal or UTC time. It doesn’t take my time zone into account. But instead of hard-coding an adjustment, I’ll let PowerShell do it dynamically. First I need to know my current offset which I can discover from WMI.

PS C:> Get-CimInstance -ClassName Win32_TimeZone

                                   Bias SettingID                               Caption
                                   ---- ---------                               -------
                                   -300                                         (UTC-05:00) Eastern Time (US & Canada)

The bias is the number of minutes. So I’ll create a variable for my UTC offset in minutes and add it to the UTC date time value.

PS C:> $UTCOffset= (Get-CimInstance -ClassName Win32_TimeZone -Property Bias).Bias
PS C:> $UTC= $d.AddTicks($ticks)
PS C:> $UTC.AddMinutes($UTCOffset)

Monday, April 7, 2014 10:01:02 AM

Now I know when this virtual machine was last powered on in my local time. I can repeat the process for the other Last* settings in the XML configuration. Naturally you don’t want to have to do these types of calculations manually so I wrote you a PowerShell function.

#requires -version 3.0
#requires -module Hyper-V

Function Get-VMLastTime {

<#
.Synopsis
Get special dates for a Hyper-V virtual machine
.Description
This command will read the XML configuration file for a given Hyper-V virtual machine and get some values that are not normally visible:

LastOn      the time the virtual machine was last powered on
LastOff     the time the virtual machine was last powered down
LastChange  the time the virtual machine's state change, say from running to off.

Additionally, the command will show you a timespan for when the virtual machine's state last changed. If the virtual machine is running this will have a value of 0.00:00:00. Virtual machines that have never been powered on will have a value of -1.00:00:00.
.Example
PS C:> get-vmlasttime chi-client02 -comp chi-hvr2

VMName       : CHI-Client02
State        : Off
LastOn       : 4/7/2014 10:01:02 AM
LastOff      : 4/8/2014 11:17:02 AM
LastChange   : 4/8/2014 11:17:02 AM
LastUse      : 150.03:44:56.5874097
Computername : chi-hvr2
.Example
PS Scripts:> get-vmlasttime -Computername chi-hvr2 | where state -ne running | Select VMName,Last* | format-table

VMName                  LastOn                  LastOff                 LastChange              LastUse
------                  ------                  -------                 ----------              -------
Web03                   6/18/2014 5:58:24 AM    6/19/2014 7:24:26 AM    6/19/2014 7:24:26 AM    78.07:39:15.2009942
Web02                   6/18/2014 5:58:24 AM    6/19/2014 7:24:19 AM    6/19/2014 7:24:19 AM    78.07:39:21.6099380
Web01                   12/31/1600 6:59:59 PM   6/17/2014 1:05:38 PM    6/17/2014 1:05:38 PM    -1.00:00:00
Dev02                   6/19/2014 7:43:18 AM    8/28/2014 4:41:10 PM    8/28/2014 4:41:10 PM    7.22:22:31.3071186
CHI-TEST                9/5/2014 11:43:57 AM    8/22/2014 7:34:24 AM    9/5/2014 1:32:35 PM     01:31:06.2942361
CHI-Client02            4/7/2014 10:01:02 AM    4/8/2014 11:17:02 AM    4/8/2014 11:17:02 AM    150.03:46:39.5460388
.Example
PS C:> get-vmlasttime -Computername chi-hvr2 | where {$_.LastUse -ge (New-Timespan -days 90)}

VMName       : CHI-Client02
State        : Off
LastOn       : 4/7/2014 10:01:02 AM
LastOff      : 4/8/2014 11:17:02 AM
LastChange   : 4/8/2014 11:17:02 AM
LastUse      : 150.03:44:56.5874097
Computername : chi-hvr2
.Notes
Last Updated: Sept 5, 2014
Version     : 1.0

Learn more:
 PowerShell in Depth: An Administrator's Guide (http://www.manning.com/jones6/)
 PowerShell Deep Dives (http://manning.com/hicks/)
 Learn PowerShell in a Month of Lunches (http://manning.com/jones3/)
 Learn PowerShell Toolmaking in a Month of Lunches (http://manning.com/jones4/)

  ****************************************************************
  * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
  * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK.  IF   *
  * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
  * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING.             *
  ****************************************************************

.Link
Get-VM
#>
[cmdletbinding()]

Param(
[Parameter(Position=0,HelpMessage="Enter a VM Name",
ValueFromPipeline,ValueFromPipelineByPropertyName)]
[ValidateNotNullorEmpty()]
[Alias("vmname")]
[string]$Name = "*",
[ValidateNotNullorEmpty()]
[alias("cn")]
[string]$Computername = $env:COMPUTERNAME
)

Begin {
    Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"  
    #what is the offset in minutes from UTC?
    $UTCOffset= (Get-CimInstance -ClassName Win32_TimeZone -Property Bias).Bias

    #time is calculated from this date
    [datetime]$d = "12/31/1600 11:59:59 PM"

     #define a hashtable of parameters to splat to Get-VM
    $vmParams = @{
      ErrorAction="Stop"
      Name=""
    }

    #script block to execute
    $sb = {
        Param($config)

        if (Test-Path -Path $config) {
        #read the content
        [xml]$xml = get-content $config

        $xmlName = $xml.configuration.properties.name.InnerText

        $off = $xml.configuration.properties.last_powered_off_time.InnerText
        $on = $xml.configuration.properties.last_powered_on_time.InnerText
        $changed = $xml.configuration.properties.last_state_change_time.InnerText

        #create a hashtable of values
        $hash =  @{
            LastPowerOn = $on
            LastPowerOff =$off
            LastChange = $changed
        }

       $hash 
       } 
       else {
        Write-warning "Could not find $config"
       }

    } #close scriptblock

     #Hashtable of parameters to splat to Invoke-Command
     $icmParam=@{
       ScriptBlock=$sb
       ArgumentList= @()
     }

    #if computername is not the local host add it to the parameter set
    if ($Computername -AND ($Computername -ne $env:COMPUTERNAME)) { 
        Write-Verbose "Searching on $computername"
        $vmParams.Add("Computername",$Computername)
        #create a PSSession for Invoke-Command
        Try {
            Write-Verbose "Creating temporary PSSession"
            $tmpSession = New-PSSession -ComputerName $Computername -ErrorAction Stop

            $icmParam.Add("Session",$tmpSession)
        }
        Catch {
            Throw "Failed to create temporary PSSession to $computername."
        }
    }
} #begin

Process {

 if ($name -is [string]) {
        Write-Verbose -Message "Getting virtual machine(s)"
        $vmParams.Name=$name
        Try {
            write-verbose ($vmparams | out-string)
            $vms = Get-VM @vmParams 
        }
        Catch {
            Write-Warning "Failed to find a VM or VMs with a name like $name"
            throw $_
            #bail out
            Return
        }
    }
    elseif ($name -is [Microsoft.HyperV.PowerShell.VirtualMachine] ) {
        #otherwise we'll assume $Name is a virtual machine object
        Write-Verbose "Found one or more virtual machines matching the name"
        $vms = $name
    }
    else {
        #invalid object type
        Write-Error "The input object was invalid."
        #bail out
        return
    }

foreach ($vm in $vms) {
    Write-Verbose "Processing $($vm.name)"
    <#
     Can't use Join-Path 
    #>
    $vmpath = "$($vm.configurationLocation)Virtual Machines" 
    $config = "$vmpath$($vm.VMId).xml" 

    Write-verbose $config

    #add the path to the config file as an parameter for the scriptblock
    $icmParam.ArgumentList= @($config)

    $detail = Invoke-Command @icmParam

    if ($detail) {
        write-verbose ($detail | out-string)

        [datetime]$on = $d.AddTicks($detail.LastPoweron).addMinutes($UTCOffset)
        [datetime]$off = $d.AddTicks($detail.LastPoweroff).addMinutes($UTCOffset)
        [datetime]$changed =$d.AddTicks($detail.Lastchange).addMinutes($UTCOffset)

        [pscustomobject]@{
        VMName = $vm.Name
        State = $vm.State
        LastOn = $on
        LastOff = $off
        LastChange = $changed
        LastUse = if ($vm.state -eq "Off" -AND $on -le "1/1/1601" ) {
         New-TimeSpan -Days -1
        }
        elseif ($vm.state -ne "running") {
        (Get-Date) - $changed
        }
        else {
         New-Timespan
        }
        Computername = $vm.ComputerName
        }
    } #if detail
    else {
        Write-Warning "There was a problem retrieving XML information for $($vm.name)"
    }
    Remove-Variable detail
} #foreach vm
} #process

End {
    #remove temp PSSession if found
    if ($tmpSession) {
        Write-Verbose "Removing temporary PSSession"
        $tmpSession | Remove-PSSession
    }
    Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
} #end

} #end function

My script assumes you have PowerShell remote access to the Hyper-V server. The function writes an object for each virtual machine showing you its current state, last times as well as a last use age property. This property is the age based on when the virtual machine’s state last changed.

PS C:> get-vmlasttime chi-client02 -Computername chi-hvr2

VMName       : CHI-Client02
State        : Off
LastOn       : 4/7/2014 10:01:02 AM
LastOff      : 4/8/2014 11:17:02 AM
LastChange   : 4/8/2014 11:17:02 AM
LastUse      : 150.03:44:56.5874097
Computername : chi-hvr2

You can use this function for all sorts of reporting. Although I expect you will want to limit your queries to machines that are not running.

PS C:> get-vm -ComputerName chi-hvr2 | where {$_.state -ne 'running'} | get-vmlasttime -Computername chi-hvr2| out-gridview -title Offline

When you are piping something from Get-VM to Get-VMLastTime be sure to specify the Hyper-V server name. Here’s my result sorted by the LastOn property.

A LastUse value of 0 means the vm is running and a value -1 indicates a virtual machine that has never been powered on. That might be helpful to know.

PS C:> get-vmlasttime -comp chi-hvr2 | where {$_.Lastuse -lt 0}

VMName       : Web01
State        : Off
LastOn       : 12/31/1600 6:59:59 PM
LastOff      : 6/17/2014 1:05:38 PM
LastChange   : 6/17/2014 1:05:38 PM
LastUse      : -1.00:00:00
Computername : chi-hvr2

Or find virtual machines that haven’t been powered on in a given number of days. You have to remember to filter with a timespan object.

PS C:> get-vmlasttime -comp chi-hvr2 | where {$_.Lastuse -ge (new-timespan -days 90)}

VMName       : CHI-Client02
State        : Off
LastOn       : 4/7/2014 10:01:02 AM
LastOff      : 4/8/2014 11:17:02 AM
LastChange   : 4/8/2014 11:17:02 AM
LastUse      : 150.03:52:04.2096517
Computername : chi-hvr2

But I urge you to be cautious on how you use this tool. You might have a virtual machine that powered on a year ago and was shut down yesterday for maintenance or something. That’s where the LastUse property I added can come in handy. Otherwise, there’s really no limit to how you can use this information, or how you might want to extend it. You might want to include uptime or when the virtual machine was created.

I hope you find the function useful and even more so I hope you expanded your PowerShell knowledge.

 

Altaro Hyper-V Backup
Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

14 thoughts on "Get Virtual Machine Last On and Off Time"

  • Mr Philip Waller says:

    I am amazed that Microsoft only provide this method for understanding when a VM was last powered on?
    that if you have 500 hyper v servers you have to trawl through the CSV’s of these servers to identify when a file was last accessed

    Risible by Microsoft

    i am sure with vMware you can run a PS1 script against vCentre

    and that is why vMware will always beat MS in virtualization

    • You still need to query the host server. But here’s a better way that queries the virtual machine information via WMI.

      #specify the name of Hyper-V Host
      $computername = $env:COMPUTERNAME
      $ns = "RootVirtualizationv2"
      $vms = get-ciminstance -Namespace $ns -ClassName msvm_computersystem -filter "Caption = 'Virtual Machine'" -ComputerName $computername

      foreach ($vm in $vms) {
      if ($vm.onTimeinMilliseconds -gt 0) {
      $Running = $True
      }
      else {
      $Running = $False
      }
      [pscustomobject]@{
      Computername = $computername.toUpper()
      VMName = $vm.elementName
      IsRunning = $Running
      Created = $vm.InstallDate
      Age = (Get-Date) - $vm.InstallDate
      LastStateChange = $vm.TimeOfLastStateChange
      ChangeAge = (Get-Date) - $vm.TimeOfLastStateChange
      }
      } #foreach vm

    • Eric Siron says:

      FWIW, VMware doesn’t store this information with their VMs at all. You have to check the host’s event log and hope that it hasn’t rolled off. If the VM might have moved, you have to ask all the hosts that it might have lived on. That technique would work with Hyper-V as well, but Jeff’s method requires a lot less flailing around and isn’t at the mercy of the event log’s capacity.

  • hrudaya says:

    Hi Jeffery its a good piece of information.
    I have a situation like each day how much time the Virtual Machine got shutdown or switched off in a cloud environment I need to find, And according to that usage calculation to be made.
    So Get-VMLastTime cmdlets might help me to get the data.

    I have question like Get-VMLastTime where I can execute like in Hyepr-V machine or VIrtual machine manager

    I executed in Virtual Machine manager and am getting these warning

    PS C:Usershrnayak> get-vm | Get-VMLastTime
    WARNING: Could not find Virtual Machines{some-name}.xml

  • AROK says:

    How do you get this last powered off time information from the new Hyper-V configuration file .vmcx that is now in binary format?

  • Lucas says:

    Hi Jeffery.
    Congrats by this function… it´s amazing.
    I have one question. I am running a HyperV on a Windows2012Rrv R2 server and looks like the backup operation have been changing the values last_powered_off_time.
    Is it make sense?

  • Mike says:

    Stumbled upon this page today as I was looking for Last On/Off info. Has anything changed in the last 2 years or is this script still the best way to gather that information?

  • Kamil says:

    Hello, Thanks for the Great Article.
    But it’s not working on Hyper-v 2016 :/

    Do you know how to get Last On and Off Time information about vm’s on Hyper-v 2016 ??

    • Zoran says:

      Hi Kamil,
      I don’t think there are any backward compatibility issues which would prevent this from working, assuming you have the correct PowerShell modules installed. Could you please share the error you are seeing?
      Thanks,
      Symon Perriman
      Altaro Editor

    • It looks a lot has changed in Hyper-V since I wrote this code. Looks like I need to revisit it.

Leave a comment or ask a question

Your email address will not be published. Required fields are marked *

Your email address will not be published.

Notify me of follow-up replies via email

Yes, I would like to receive new blog posts by email

What is the color of grass?

Please note: If you’re not already a member on the Dojo Forums you will create a new account and receive an activation email.