Save to My DOJO
Table of contents
When using PowerShell to build reports about your virtual infrastructure, have you run into situations where you run a command like this:
get-vm -ComputerName chi-hvr2 | sort CreationTime | Select name,creationtime
Only to end up with output like this figure?
What’s up with that creation time? If you run client Hyper-V on Windows 8 or Windows 8.1 you might be more likely to see this type of result although I have seen this behavior on Hyper-V server as well. Of course, if you stage a virtual machine on Windows 8.1 and later move it to a server, you will most likely carry over this problem. Perhaps this doesn’t bother you but data discrepancies like this bother me. So after a bit of research I found a few references to a bug on client Hyper-V where the creation time property is not captured.
The creation time property is stored, or should be stored, in the virtual machine configuration file. This is an XML file which means we can use PowerShell to find the value and if necessary create it. Let me use a virtual machine on my Windows 8.1 client to demonstrate.
PS C:> $VM = get-vm dev01
The configuration file is an XML file that uses the virtual machines ID, which I can pull from the $VM variable.
PS C:> $VM | select configurationlocation,id ConfigurationLocation Id --------------------- -- D:VMDev01 26abea54-55f8-4501-91d1-769fbe381c4f
Using PowerShell, I can construct the path and read in the contents as an XML document.
PS C:> [xml]$config = get-content "$($vm.ConfigurationLocation)virtual machines$($vm.id).xml"
The creation time is in the properties node.
PS C:> $config.configuration.properties.creation_time type #text ---- ----- bytes aUkVnjOLzwE=
You can also use Select-XML to verify the property exists.
PS C:> $config | select-xml -xpath "//creation_time" Node Path Pattern ---- ---- ------- creation_time InputStream //creation_time
The problem is that for some virtual machines, this node is missing and hence the error.
But first, you may be wondering about the node value. In my example aUkVnj0Lzew= doesn’t look like any date time value to me.
PS C:> $text = 'aUkVnjOLzwE='
That value is a string representation of an array of bytes which represents the number of ticks since December 29, 1600 11:59:59PM. To translate I can convert to the string back into an array of bytes.
PS C:> [byte[]]$barray = [convert]::FromBase64String($text)
Next, I can turn the byte array into a value.
PS C:> $converted 130475968028821865
This is the number of ticks so all I need to do is add them to the December 31, 1600 date.
PS C:> ([datetime]"12/31/1600 23:59:59").AddTicks($converted) Wednesday, June 18, 2014 8:26:41 PM
So that’s when this virtual machine was created which I can see here:
PS C:> $vm.creationtime Wednesday, June 18, 2014 4:26:42 PM
Well, almost. The creation time I calculated is in Universal Time. But that is easy to accommodate by adding my offset from UTC.
PS C:> ([datetime]"12/31/1600 23:59:59").AddTicks($converted).addhours(-4) Wednesday, June 18, 2014 4:26:41 PM
I show you all of this because to correct the missing creation date problem, all you need to do is insert the creation_time node with the proper value and save the file. But don’t panic, I have a function that will do the heavy lifting for you.
#requires -version 3.0 #requires -module Hyper-V Function Set-VMCreationTime { <# .Synopsis Update the Hyper-V virtual machine CreationTime property .Description This command can be used to set a Hyper-V virtual machine's CreationTime property. This value is stored in the XML configuration file and sometimes it is missing. This especially seems to be the case with virtual machines created on Windows 8 or 8.1. This command will use the the creation time of the XML configuration as the virtual machine creation date. You may not see the new creation time until you start a new PowerShell session. This command supports -WhatIf and -Verbose. ****************** * VERY IMPORTANT * ****************** The Hyper-V Management service will be briefly STOPPED while the configuration file is updated. If you are updating many virtual machines, this will bounce the service repeatedly. USE AT YOUR OWN RISK AND HAVE ADEQUATE BACKUPS WITH VERIFIED RESTORES. .Example PS C:> Set-VMCreationTime dev02 -Computername chi-hvr2 Name : Dev02 CreationTime : 6/19/2014 8:41:46 AM ComputerName : CHI-HVR2 .Example PS C:> get-vm -comp Server01 | where creationtime -lt "1/1/1601" | set-vmcreationtime -Computername Server01 | format-table Name CreationTime ComputerName ---- ------------ ------------ Test VM Alpha 11/26/2013 9:30:22 AM Server01 Test VM Bravo 11/26/2013 9:30:23 AM Server01 Test01 6/19/2014 2:46:47 PM Server01 Ubuntu 13 x86 11/26/2013 8:20:13 AM Server01 WebTest 6/18/2014 2:44:46 PM Server01 WebTest01 6/19/2014 3:12:34 PM Server01 Win2012R2-Core-Baseline 11/26/2013 9:33:34 AM Server01 Win2012R2-Core-Baseline-x64 11/27/2013 10:55:26 AM Server01 Win8Demo 6/18/2014 8:58:51 PM Server01 Update all virtual machines with no creation time. .Notes Last Updated: September 8, 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(SupportsShouldProcess)] 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)" #define a hashtable of parameters to splat to Get-VM $vmParams = @{ ErrorAction="Stop" Name="" } #script block to execute $sb = { [cmdletbinding(SupportsShouldProcess)] Param([string]$config,[boolean]$V,[boolean]$W) if ($v) { $VerbosePreference = "Continue" } #set the WhatIf preference locally $WhatIfPreference = $W #time is calculated from this date [datetime]$d = "12/31/1600 11:59:59 PM" if (test-Path -Path $config) { #get the contents of the configuration file as an XML document [xml]$xml = Get-Content -Path $config #only process if there is no Creation_Time Node if (-Not ($xml.configuration.properties.creation_time.'#text')) { $Created = (get-item $config).CreationTimeUTC Write-Verbose "Updating creation time to $Created" [int64]$ticks = ($Created - $d).ticks #convert number of ticks into an array of bytes [byte[]]$bytes = [BitConverter]::GetBytes($ticks) #convert byte array to string $binString = [convert]::ToBase64String($bytes) #create a new XML node $creationtime = $xml.CreateNode("element","creation_time","") $creationtime.SetAttribute("type","bytes") $Creationtime.InnerText = $binString write-verbose ($creationtime | out-string) $xml.configuration.properties.AppendChild($creationtime) | Out-null #the vmms service must be stopped in order to save the file if ($PSCmdlet.ShouldProcess($config)) { Write-Verbose "Stopping vmms service" Stop-Service -Name vmms Write-Verbose "Saving configuration" $xml.Save($config) Write-Verbose "Starting vmms service" Start-Service -Name vmms #give the vmms service a chance to restart Start-Sleep -Seconds 2 $vmname = $xml.configuration.properties.name.InnerText Write-Verbose "Refreshing $vmname" Get-VM -Name $vmname | Select Name,CreationTime,Computername } #should process } #if no creation_time node else { Write-Verbose "Creation_Time Node already defined" } } else { Write-Warning "Can't find $config" } } #close scriptblock #Hashtable of parameters to splat to Invoke-Command $icmParam=@{ ScriptBlock=$sb ArgumentList= @() HideComputername = $True } #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." } } else { $icmParam.add("Computername",$Computername) } } #begin Process { if ($name -is [string]) { Write-Verbose -Message "Getting virtual machine(s)" $vmParams.Name=$name Try { $vms = Get-VM @vmParams } #Try Catch { Write-Warning "Failed to find a VM or VMs with a name like $name" #bail out Return } #Catch } #if name is a string 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 } #elseif VM object 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 #pass verbose parameter to scriptblock if ($VerbosePreference -eq "Continue") { $v = $True } else { $v = $false } $icmParam.ArgumentList= @($config,$v,$WhatIfPreference) Invoke-Command @icmParam | Select * -ExcludeProperty RunspaceID } #foreach vm } #process End { #remove temp PSSession if found if ($tmpSession) { Write-Verbose "Removing temporary PSSession" #always remove the session $tmpSession | Remove-PSSession -WhatIf:$False } Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } #end } #end function
This function uses PowerShell remoting to edit the XML file because the configuration location is relative to the Hyper-V server. The function uses the creation date and time for the XML configuration file as the new value, if it is missing. Fortunately, in PowerShell file objects have a property that reflects the creation time as a UTC datetime.
PS C:> dir "$($vm.ConfigurationLocation)virtual machines$($vm.id).xml" | select CreationTime* CreationTime CreationTimeUtc ------------ --------------- 6/18/2014 4:26:41 PM 6/18/2014 8:26:41 PM
My function takes the UTC value and turns it into the string representation of the byte array of ticks since December 31, 1600. If there is no creation_time node in the XML file, the function creates the node, sets the value and adds it to the XML document. But there is one major caveat with this process. In order to update the XML configuration file, the virtual machine service must be stopped on the Hyper-V host. When the service is running all of the XML configuration files are locked and you can’t save the updated file back to disk. To be absolutely clear, when you use this function, the Hyper-V virtual machine service will be temporarily stopped and restarted. It is only off for the briefest of moments while the updated configuration file is written to disk. But I’m assuming that you will only need to use this function to remediate server-based virtual machines on a rare case by case basis. On client Hyper-V you might need to use it more frequently.
Let me then revisit the original virtual machine and correct it with my function. First, I need to load the function into my PowerShell session by dot-sourcing it.
PS C:> . C:scriptsSet-VMCreationTime.ps1
The function supports –WhatIf so you can see what it would do without making and changes or restarting services.
From the verbose output you can see what date time value will be used and what it will look like in the XML file. Once satisfied, I can run the command without –Whatif.
PS C:> Set-VMCreationTime -vmname dev02 -Computername chi-hvr2 Name : Dev02 CreationTime : 6/19/2014 8:41:46 AM ComputerName : CHI-HVR2
Done! The output come from running Get-VM in the remote session on the Hyper-V host so I can verify the change. I do that because there is an issue with Get-VM in PowerShell. If you have run Get-VM at least once in your PowerShell session, some properties, like CreationTime, don’t get refreshed the second time you get the virtual machine.
PS C:> get-vm dev02 -ComputerName chi-hvr2 | select name,creationtime Name CreationTime ---- ------------ Dev02 12/31/1600 7:00:00 PM
But if I start new PowerShell session I will see the new creation time. Here’s a quick way to test.
PS C:> powershell -noprofile {get-vm dev02 -ComputerName chi-hvr2 | select name,creationtime} Name CreationTime ---- ------------ Dev02 6/19/2014 8:41:46 AM
So if you’ve been annoyed by invalid virtual machine creation times, now you have an answer. There are other examples in the comment-based help. You can run the command as often as you want because it will only make a change if the Creation_Time node is missing under Properties. As with all things PowerShell, please make sure you first test this in a non-production environment. I hope you’ll let me know how this works out for you.
Not a DOJO Member yet?
Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!
11 thoughts on "Restoring Virtual Machine Creation Time"
Excellent work, thank you.
I *think* that my checkpoints, which also showed Created: 12/31/1600…, actually became “valid” data and so this script did not work on those VMs. In other words, there IS a Creation_Time node for those machines BUT it contains 12/31/1600. Instead of punting if the node exists, the script should examine the value. I don’t know PowerShell well enough to update line #84 for you…
Worked great everywhere else. Thanks!
Thanks for this detail explanation of this annoying issue . I had this on W2K12 server and i fix it, using alternative editing directly the xml configuration files. All vm in error that the date were 1600 was because the missing creationtime.
Why is this parameter missing ? maybe sometime MS will explain it.
Hello, Really nice script, but it doesn’t work with a Cluster. Please for help to fix that strange bug.
I don’t have a cluster that I can test with, at least not in the short term.
There is only one disadvantage of that script. It doesn’t work for Windows 10 Hyper-V Host. I have one Hyper-V Guest with configuration level 7. It seems to me there are no more xml file for configuration.
This may be another topic I have to revisit.
Will it be possible to upload the ps script anywhere? I tried copying and saving it as ps1. The script does not run. May be something wrong with copying process.
You should be able to copy and paste the code snippets into a ps1 file. You need to make sure you have an execution policy that permits running scripts. You also have to dot source the script file to load the function into your session.
PS> . .ps1
PS> Set-VMCreationTime -vmname dev02 -Computername chi-hvr2
Excellent work, thank you.
I *think* that my checkpoints, which also showed Created: 12/31/1600…, actually became “valid” data and so this script did not work on those VMs. In other words, there IS a Creation_Time node for those machines BUT it contains 12/31/1600. Instead of punting if the node exists, the script should examine the value. I don’t know PowerShell well enough to update line #84 for you…
Worked great everywhere else. Thanks!
This script works great on a non clustered Hyper-V server… however, once Failover Clustering comes into play, the XML files are not longer available (or so it seems).
Hi Jean-Sebastien,
To use a script on a failover cluster, you can store the script on a file share which is accessible by every node. The file share itself does not need to be clustered, although that would also provide highly-available access to that script.
Thanks,
Symon Perriman
Altaro Editor