Save to My DOJO
Table of contents
Recently I posted an article on using PowerShell to find when a virtual machine was last used. If you missed the original article, you might want to check it out as the concepts haven’t really changed. When I first wrote my PowerShell function, I made an assumption that it had to run locally. And for the most part that is still true. In order to get the actual VHD or VHDX file I need to be on the server because the path is relative to the server. Although sometimes you can run commands remotely from your desktop and get some information.
PS C:> get-vm chi-dc01 -ComputerName hv01 | select id | get-vhd -ComputerName hv01 ComputerName : hv01 Path : D:VHDGlobomantics-DC_F1517EF1-534A-45F3-8228-969A87E391A7.avhd VhdFormat : VHD VhdType : Differencing FileSize : 4704739328 Size : 16106127360 MinimumSize : 16105078784 LogicalSectorSize : 512 PhysicalSectorSize : 512 BlockSize : 2097152 ParentPath : D:VHDGlobomantics-DC.vhd FragmentationPercentage : Alignment : 1 Attached : False DiskNumber : IsDeleted : False Number :
But this doesn’t show me time stamps on the file. To get that I still need Get-Item which has to run locally, that is, on the server. But since I want to do as much management as I can remotely I revisited my original function and came up with a version that allows you to specify a server name.
#requires -version 3.0 #requires -modules Hyper-V Function Get-VMLastUse { <# .Synopsis Find a virtual machine last use date. .Description This command will write a custom object to the pipeline which should indicate when the virtual machine was last used. The command finds all hard drives that are associated with a Hyper-V virtual machine and selects the first one. The assumption is that if the virtual machine is running the hard drive file will be changed and the first hard drive listed will most likely be the system drive. The function retrieves the last write time property from the first VHD or VHDX file to determine how long it has been since the file was last used. If the virtual machine is currently running the last use time will be 0:00:00. You can pipe a collection of Hyper-V virtual machines or specify a virtual machine name. Wildcards are supported. The default is to display last use data for all virtual machines. You can run this on a Hyper-V server or from any domain member that has the Hyper-V management tools installed, such as a Windows 8 computer. The command uses PowerShell remoting to retrieve the disk information. .Parameter Name The name of a Hyper-V virtual machine or a VM object. You can pipe Get-VM to this command. .Parameter Computername The name of the server to query. The default is the local host. If you pipe a Get-VM command that queries a remote computer, the computer name will automatically be used. .Example PS C:> Get-vmlastuse xp* VMName CreationTime LastUse LastUseAge ------ ------------ ------- ---------- XP Lab 3/3/2013 1:05:29 PM 7/14/2013 9:07:19 AM 33.00:57:04.8442216 Get last use information for any virtual machine starting with XP. .Example PS C>> get-vmlastuse ubuntu* -computer HV01 VMName CreationTime LastUse LastUseAge ------ ------------ ------- ---------- Ubuntu 12 x86 3/3/2013 3:31:55 PM 6/25/2013 8:26:00 AM 52.01:47:42.9022213 Get the Ubuntu VM from server HV01. .Example PS C:> get-vm -computer HV01 | where {$_.state -eq 'off'} | get-vmlastuse VMName CreationTime LastUse LastUseAge ------ ------------ ------- ---------- 10961A-LON-CL1 3/15/2013 6:08:54 AM 8/13/2013 5:06:02 PM 2.17:02:13.8564362 10961A-LON-DC1 3/15/2013 6:08:09 AM 8/13/2013 3:22:44 PM 2.18:45:32.0323689 10961A-LON-SVR1 3/15/2013 6:09:32 AM 8/13/2013 3:21:36 PM 2.18:46:40.0599579 CHI-APP01 6/5/2013 12:49:28 PM 8/16/2013 8:48:54 AM 01:19:21.9799246 CHI-Client02 3/3/2013 3:31:42 PM 8/3/2013 7:48:14 PM 12.14:20:02.4111888 CHI-DEV01 5/29/2013 4:18:21 PM 8/16/2013 9:32:30 AM 00:35:46.8916567 ... Get last use information for any virtual machine that is currently off on a remote Hyper-V server. .Example PS C:> get-vmlastuse -computer HV01 | Sort LastUseAge | Out-Gridview -title "Last Use Report" Get last use information for all virtual machines on server HV01, sorted by age. All results will be displayed with Out-Gridview. .Notes version 2.0 Brought to you by Altaro http://altaro.com/hyper-v New to PowerShell? Try Learn PowerShell 3 in a Month of Lunches **************************************************************** * 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. * **************************************************************** .Inputs String or Hyper-V Virtual Machine .Outputs Custom object .Link Get-VM Get-Item #> [cmdletbinding()] Param ( [Parameter(Position=0, HelpMessage="Enter a Hyper-V virtual machine name", ValueFromPipeline,ValueFromPipelinebyPropertyName)] [ValidateNotNullorEmpty()] [alias("vm")] [object]$Name="*", [Parameter(ValueFromPipelinebyPropertyname)] [alias("cn")] [string]$Computername ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" #define a hashtable of parameters to splat to Get-VM $vmParams = @{ ErrorAction="Stop" } #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 } Catch { Throw "Failed to create temporary PSSession to $computername." } } } #begin Process { if ($name -is [string]) { Write-Verbose -Message "Getting virtual machine(s)" $vmParams.Add("Name",$name) Try { $vms = Get-VM @vmParams } Catch { Write-Warning "Failed to find a VM or VMs with a name like $name" #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) { #if VM is on a remote machine using PowerShell remoting to get the information Write-Verbose "Processing $($vm.name)" $sb = { param([string]$Path,[string]$vmname) Try { $diskfile = Get-Item -Path $Path -ErrorAction Stop $diskFile | Select-Object @{Name="LastUse";Expression={$diskFile.LastWriteTime}}, @{Name="LastUseAge";Expression={(Get-Date) - $diskFile.LastWriteTime}} } Catch { Write-Warning "$($vmname): Could not find $path." } } #end scriptblock #get first drive file $diskpath= $vm.HardDrives[0].Path #only proceed if a hard drive path was found if ($diskpath) { $icmParam=@{ ScriptBlock=$sb ArgumentList= @($diskpath,$vm.name) } Write-Verbose "Getting details for $(($icmParam.ArgumentList)[0])" if ($vmParams.computername) { $icmParam.Add("Session",$tmpSession) } $details = Invoke-Command @icmParam #write a custom object to the pipeline $objHash=[ordered]@{ VMName=$vm.name CreationTime=$vm.CreationTime LastUse=$details.LastUse LastUseAge=$details.LastUseAge } #if VM is running set the LastUseAge to 0:00:00 if ($vm.state -eq 'running') { $objHash.LastUseAge= New-TimeSpan -hours 0 } #write the object to the pipeline New-Object -TypeName PSObject -Property $objHash } #if $diskpath Else { Write-Warning "$($vm.name): No hard drives defined." } }#foreach } #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
This function uses PowerShell remoting and Invoke-Command to retrieve the necessary file information. If you specify a remote computername, the function will create a temporary PSSession and use it with Invoke-Command. It will be removed at the end of the command. More than likely your remote server is enabled for PowerShell remoting so this should just work. I also added some better error handling for virtual machines with missing hard disks or those that had none attached.
My theory, and I haven’t found anything better, is that the first disk will be the system disk and will thus reflect an updated timestamp when the virtual machine is started. As I was testing I noticed that virtual machines that had been on for awhile, actually looked like they had not been used in a while which is misleading. In my function I decided that if the virtual machine is running to set the Last Use Age to 0. I think this better reflects the age of my virtual machines. Once the function is loaded into my PowerShell session I can run a command like this.
PS C:> get-vmlastuse –computername HV01 | Out-Gridview -Title "VM Aging"
Which produces a result like this.
I can click on column headings to sort or apply additional filters. When I ran the command I saw warnings about missing disk files. The virtual machines in this output, like “XP Lab” have a hard disk file defined but something has happened to the file since the virtual machine was created. Certainly something for me to investigate.
You can still run this version of my function on the server and it will work just fine. But if you have the Hyper-V module installed locally, you can run it and specify the name of your Hyper-V server. For me, remote management is the name of the game and PowerShell makes it a fun game to play.
Not a DOJO Member yet?
Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!
5 thoughts on "Get-Virtual Machine Last Use – Revisited"
Hello Jeffery,
Thank you sososososo much for posting this blog , It’s brilliant !
But when I use this , I get a problem. I run this script on the server.
Some of the “Creation Time” showing ” 1/1/1601 8:00:00 AM” , How could this happen ?
Thanks again
I’ve seen this as well on my systems. And I think it might be related to importing a virtual machine. I’ll have to look into this further.
I have a Clustered Hyper-V Scenario with 4 members, when i run the script:
PS C:Tools> .Get-VMLastUse -Computername desacluster03
I get nothing! Any ideas?
You need to dot source the ps1 file
. c:toolsget-vmlastuse.ps1
There is a space between the period and the path to the file
Then you can run the function
Get-VMLastUse -compurtername foo