Any idea how I can relatemy Get-Snapshot information to the Get-VIEvent information to work out who created a snapshot ?
Thanks
Alan
Short question but a long answer. In the end I got there.
There were a few quirks in writing this script.
1) The Created timestamp in the Event object is not always exactly the same as the time stamp in the VirtualMachine Snapshot property.
The solution is to use the Task Entity property to get the time stamp of the snapshot.
2) The Get-ViEvent cmdlet returns a VimApi.ManagedObjectReference instead of a VMware.Vim.ManagedObjectReference.
As such this is not a big problem since I can easily convert the one MoRef to the other (unfortunately not via casting).
3) The speed of the Get-ViEvent cmdlet is not ideal compered to the Task Collector form the APIs
From 2) and 3) I decided to go for the Task Collector methods.
The flow of the script is quite simple.
First create a hash table from all tasks that created snapshots on the specific guest.
The key into the hash table is the name of the guest concatenated with the snapshot creation timestamp.
Then the script use the Get-Snapshot cmdlet to retrieve the snapshots for the guest.
From this output a key is constructed and then the key is used to look up the user in the hash table.
The User property is added to the SnapshotImpl object.
function Get-SnapshotTree{ param($tree, $target) $found = $null foreach($elem in $tree){ if($elem.Snapshot.Value -eq $target.Value){ $found = $elem continue } } if($found -eq $null -and $elem.ChildSnapshotList -ne $null){ $found = Get-SnapshotTree $elem.ChildSnapshotList $target } return $found } $daysBack = 3 # How many days back from now $guestName = <VM-name> # The name of the guest $tasknumber = 999 # Windowsize of the Task collector #$serviceInstance = get-view ServiceInstance $taskMgr = Get-View TaskManager # Create hash table. Each entry is a create snapshot task $report = @{} $filter = New-Object VMware.Vim.TaskFilterSpec $filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime $filter.Time.beginTime = (Get-Date).AddDays(-$daysBack) $filter.Time.timeType = "startedTime" $collectionImpl = Get-View ($taskMgr.CreateCollectorForTasks($filter)) $dummy = $collectionImpl.RewindCollector $collection = $collectionImpl.ReadNextTasks($tasknumber) while($collection -ne $null){ $collection | where {$_.DescriptionId -eq "VirtualMachine.createSnapshot" -and $_.State -eq "success" -and $_.EntityName -eq $guestName} | %{ $row = New-Object PsObject $row | Add-Member -MemberType NoteProperty -Name User -Value $_.Reason.UserName $vm = Get-View $_.Entity $snapshot = Get-SnapshotTree $vm.Snapshot.RootSnapshotList $_.Result $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString()) $report[$key] = $row } $collection = $collectionImpl.ReadNextTasks($tasknumber) } $collectionImpl.DestroyCollector() # Get the guest's snapshots and add the user $snapshotsExtra = Get-VM $guestName | Get-Snapshot | % { $key = $_.vm.Name + "&" + ($_.Created.ToString()) if($report.ContainsKey($key)){ $_ | Add-Member -MemberType NoteProperty -Name User -Value $report[$key].User } $_ } $snapshotsExtra | Export-Csv "C:\SnapshotsExtra.csv" -NoTypeInformation -UseCulture
Note1: the -UseCulture parameter on the Export-Csv cmdlet is PS v2 CTP3
Note2: it shouldn't be too difficult to convert the script to handle all guests instead of just one. The name of the guest is already in the key in the hash table.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Short question but a long answer. In the end I got there.
There were a few quirks in writing this script.
1) The Created timestamp in the Event object is not always exactly the same as the time stamp in the VirtualMachine Snapshot property.
The solution is to use the Task Entity property to get the time stamp of the snapshot.
2) The Get-ViEvent cmdlet returns a VimApi.ManagedObjectReference instead of a VMware.Vim.ManagedObjectReference.
As such this is not a big problem since I can easily convert the one MoRef to the other (unfortunately not via casting).
3) The speed of the Get-ViEvent cmdlet is not ideal compered to the Task Collector form the APIs
From 2) and 3) I decided to go for the Task Collector methods.
The flow of the script is quite simple.
First create a hash table from all tasks that created snapshots on the specific guest.
The key into the hash table is the name of the guest concatenated with the snapshot creation timestamp.
Then the script use the Get-Snapshot cmdlet to retrieve the snapshots for the guest.
From this output a key is constructed and then the key is used to look up the user in the hash table.
The User property is added to the SnapshotImpl object.
function Get-SnapshotTree{ param($tree, $target) $found = $null foreach($elem in $tree){ if($elem.Snapshot.Value -eq $target.Value){ $found = $elem continue } } if($found -eq $null -and $elem.ChildSnapshotList -ne $null){ $found = Get-SnapshotTree $elem.ChildSnapshotList $target } return $found } $daysBack = 3 # How many days back from now $guestName = <VM-name> # The name of the guest $tasknumber = 999 # Windowsize of the Task collector #$serviceInstance = get-view ServiceInstance $taskMgr = Get-View TaskManager # Create hash table. Each entry is a create snapshot task $report = @{} $filter = New-Object VMware.Vim.TaskFilterSpec $filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime $filter.Time.beginTime = (Get-Date).AddDays(-$daysBack) $filter.Time.timeType = "startedTime" $collectionImpl = Get-View ($taskMgr.CreateCollectorForTasks($filter)) $dummy = $collectionImpl.RewindCollector $collection = $collectionImpl.ReadNextTasks($tasknumber) while($collection -ne $null){ $collection | where {$_.DescriptionId -eq "VirtualMachine.createSnapshot" -and $_.State -eq "success" -and $_.EntityName -eq $guestName} | %{ $row = New-Object PsObject $row | Add-Member -MemberType NoteProperty -Name User -Value $_.Reason.UserName $vm = Get-View $_.Entity $snapshot = Get-SnapshotTree $vm.Snapshot.RootSnapshotList $_.Result $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString()) $report[$key] = $row } $collection = $collectionImpl.ReadNextTasks($tasknumber) } $collectionImpl.DestroyCollector() # Get the guest's snapshots and add the user $snapshotsExtra = Get-VM $guestName | Get-Snapshot | % { $key = $_.vm.Name + "&" + ($_.Created.ToString()) if($report.ContainsKey($key)){ $_ | Add-Member -MemberType NoteProperty -Name User -Value $report[$key].User } $_ } $snapshotsExtra | Export-Csv "C:\SnapshotsExtra.csv" -NoTypeInformation -UseCulture
Note1: the -UseCulture parameter on the Export-Csv cmdlet is PS v2 CTP3
Note2: it shouldn't be too difficult to convert the script to handle all guests instead of just one. The name of the guest is already in the key in the hash table.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Luc, is there nothing you can not do ?!
Thanks very much, I managed to adapt your code to allow me to create a function for this info.
function Get-SnapshotTree{ param($tree, $target) $found = $null foreach($elem in $tree){ if($elem.Snapshot.Value -eq $target.Value){ $found = $elem continue } } if($found -eq $null -and $elem.ChildSnapshotList -ne $null){ $found = Get-SnapshotTree $elem.ChildSnapshotList $target } return $found } function Get-SnapshotExtra ($snap){ #$daysBack = 5 # How many days back from now $guestName = $snap.VM # The name of the guest $tasknumber = 999 # Windowsize of the Task collector #$serviceInstance = get-view ServiceInstance $taskMgr = Get-View TaskManager # Create hash table. Each entry is a create snapshot task $report = @{} $filter = New-Object VMware.Vim.TaskFilterSpec $filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime $filter.Time.beginTime = (($snap.Created).AddSeconds(-5)) $filter.Time.timeType = "startedTime" $collectionImpl = Get-View ($taskMgr.CreateCollectorForTasks($filter)) $dummy = $collectionImpl.RewindCollector $collection = $collectionImpl.ReadNextTasks($tasknumber) while($collection -ne $null){ $collection | where {$_.DescriptionId -eq "VirtualMachine.createSnapshot" -and $_.State -eq "success" -and $_.EntityName -eq $guestName} | %{ $row = New-Object PsObject $row | Add-Member -MemberType NoteProperty -Name User -Value $_.Reason.UserName $vm = Get-View $_.Entity $snapshot = Get-SnapshotTree $vm.Snapshot.RootSnapshotList $_.Result $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString()) $report[$key] = $row } $collection = $collectionImpl.ReadNextTasks($tasknumber) } $collectionImpl.DestroyCollector() # Get the guest's snapshots and add the user $snapshotsExtra = $snap | % { $key = $_.vm.Name + "&" + ($_.Created.ToString()) if($report.ContainsKey($key)){ $_ | Add-Member -MemberType NoteProperty -Name Creator -Value $report[$key].User } $_ } $snapshotsExtra } $Snapshots = Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-14))} $mySnaps = @() foreach ($snap in $Snapshots){ $SnapshotInfo = Get-SnapshotExtra $snap $mySnaps += $SnapshotInfo } $mySnaps | Select VM, Name, Creator, Description
If you found this information useful, please consider awarding points for Correct or Helpful.
Alan Renouf
It's the combined strength of PowerShell, PowerCLI and the SDK that allows me to do this kind of stuff.
They are the instruments, I'm just the conductor
Nice adaption to a general function btw.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Ok, so I have a question. I know that this is a bit of an old topic, but why create a filter for the TaskCollector, and then not specify all the relevant filters. I.e. you specify start time, but then don't filter on state, entity, or end time. The only reason I bring it up is that I was running this in the snapreminder.ps1 script, and a couple of very old snapshots brought huge performance problems by bringing up hundreds of records since then.
Basically I did that for performance reasons, the more complex the filter, the longer it will take.
But you are of course right.
In the mean time the performance of the Get-VIEvent cmdlet has improved. So you could even consider using the cmdlet instead of the collector. Just filter on TaskEvent.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
You can always specify more filter properties to narrow down your search.
If it fits you, use it as it will return lesser objects to filter out afterwards and improve performance.
Don't know why there weren't any additional properties in the code though.
Thanks for the suggestion, I'll try reworking it with the Get-Event. However, I'd like to finish out the thought exercise. I was trying to do this:
$filter = New-Object VMware.Vim.TaskFilterSpec
$filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime
$filter.Time.beginTime = (($snap.Created).AddSeconds(-5))
$filter.Time.timeType = "startedTime"
$filter.State = "success"
$filter.Entity = New-Object VMware.Vim.TaskFilterSpecByEntity
$filter.Entity.recursion = "self"
$filter.Entity.entity = Get-Vm -Name $snap.VM.Name | Get-View | Get-VIObjectByVIView
but get the following error
Get-VIObjectByVIView : 2/2/2011 1:53:20 PM Get-VIObjectByVIView Object reference not set to an instance of an object.At C:\scripts\snapreminder.ps1:59 char:89+ $filter.Entity.entity = Get-Vm -Name $snap.VM.Name | Get-View | Get-VIObjectByVIView <<<<+ CategoryInfo : NotSpecified: (:) [Get-VIObjectByVIView], VimException+ FullyQualifiedErrorId : Core_BaseCmdlet_UnknownError,VMware.VimAutomation.ViCore.Cmdlets.Commands.DotNetInterop.GetVIObjectByVIViewCommandAny ideas on what I am doing wrong?
The property Entity needs a MoRef.
Try replacing this line
$filter.Entity.entity = Get-Vm -Name $snap.VM.Name | Get-View | Get-VIObjectByVIView
by this
$filter.Entity.entity = (Get-Vm -Name $snap.VM.Name).Extensiondata.MoRef
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
That worked great. But it looks like I've run into some time based problems....
when I get to the end of the Get-SnapshotExtra, where it's trying to see if the find the VM+Snapshot creation time in the map,
# Get the guest's snapshots and add the user
$snapshotsExtra = $snap | % {
$key = $_.vm.Name + "&" + ($_.Created.ToString())
if($report.ContainsKey($key)){
$_ | Add-Member -MemberType NoteProperty -Name Creator -Value $report[$key].User
}
$_
if I output the value of $key and $report it shows:
[DBG]: PS C:\Users\admin.ccc>>> $keyFSBUS&1/8/2011 5:00:44 PM____________________________________________________________________________________________________________________________________________________________________________________________________________________[DBG]: PS C:\Users\admin.ccc>>> $reportName Value---- -----FSBUS&1/8/2011 11:00:44 PM @{User=ICXT\svc.scripts}Now, since we are in Central time zone, the 6 hour difference *kind* of makes sense, but is there away to determine what TZ the data was in, and adjust it before we convert it to the string to put in the tables?
Thanks for all the help, you've been extremely considerate.
Those timestamp are kept in UTC.
You can easily convert to your localtime like this
$_.Created.ToLocalTime()
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Awesome. I had been messing w/ this for awhile trying to figure out best method. Thanks as always.
Ok, I took all the changes, added in some of the parts from list_snapshots_v6.ps1, and have the one below.
Creates a list of snapshots, identifies who created them, and emails the owners about them, as well as emailing you a summary.
Next step is to add a custom field to VMs allowing them to be excluded from this reporting.
Hi.
This script is very helpful for me but i have a little problem with the creator information. We have VMs who have 2 days old snapshots and the script shows under "Created by" Unknown Creator. Has someone an idea why this information isn't shown there?
Thanks for help
ron999
Very cool. I've spent most of the day searching/trying scripts to do this, and yours is the best, and *almost* perfect for what I'm looking for:
- would it be easy enough to add the snapshot size to the email and summary?
- if the snapshot owner is not determined (returns "unknown owner"), do I understand correctly that you attempt to find that information in the VC task list?
- if the owner is determined, how is the email address determined from DOMAIN\Owner? Would it be possible to extract Owner and append the email domain name, e.g. from DOMAIN\Owner => Owner@mydomain.com?
Did you ever complete the "next step" you mention for exclude VMs from the report?
Wow, this is so cool guys an all star thread 🙂
thanks guys.
It would seem all/any snapshots the owner is unknown. Any idea why this would be?
The script tries to macth the Event, which holds the creator information, with a snapshot based on the timestamp availble on the snapshot and in the event.
I noticed that sometimes these timestamps might differ with a couple of seconds.
A solution would be to not look for an exact match of the timestamp, but look for an event that is "close enough". For example a couple of seconds before and after the snapshot create time.
This poses a certain risk of matching the wrong event with the snapshot, but that can be avoided by adding an additional test, for example for the VM name. It's highly unlikely that you have 2 snapshots for the same VM in a matter of seconds.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Agreed - but *how* to do this? Looking through the script, I see this seemingly relevant function:
function Get-SnapshotExtra ($snap)
{
$guestName = $snap.VM # The name of the guest
$tasknumber = 999 # Windowsize of the Task collector
$taskMgr = Get-View TaskManager
# Create hash table. Each entry is a create snapshot task
$report = @{}
$filter = New-Object VMware.Vim.TaskFilterSpec
$filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime
$filter.Time.beginTime = (($snap.Created).AddSeconds(-5))
$filter.Time.timeType = "startedTime"
$filter.State = "success"
$filter.Entity = New-Object VMware.Vim.TaskFilterSpecByEntity
$filter.Entity.recursion = "self"
$filter.Entity.entity = (Get-Vm -Name $snap.VM.Name).Extensiondata.MoRef
$collectionImpl = Get-View ($taskMgr.CreateCollectorForTasks($filter))
$dummy = $collectionImpl.RewindCollector
$collection = $collectionImpl.ReadNextTasks($tasknumber)
while($collection -ne $null){
$collection | where {$_.DescriptionId -eq "VirtualMachine.createSnapshot" -and $_.State -eq "success" -and $_.EntityName -eq $guestName} | %{
$row = New-Object PsObject
$row | Add-Member -MemberType NoteProperty -Name User -Value $_.Reason.UserName
$vm = Get-View $_.Entity
$snapshot = Get-SnapshotTree $vm.Snapshot.RootSnapshotList $_.Result
if ( $snapshot -ne $null)
{
$key = $_.EntityName + "&" + ($snapshot.CreateTime.ToLocalTime().ToString())
$report[$key] = $row
}
}
$collection = $collectionImpl.ReadNextTasks($tasknumber)
}
$collectionImpl.DestroyCollector()
# Get the guest's snapshots and add the user
$snapshotsExtra = $snap | % {
$key = $_.vm.Name + "&" + ($_.Created.ToLocalTime().ToString())
if($report.ContainsKey($key)){
$_ | Add-Member -MemberType NoteProperty -Name Creator -Value $report[$key].User
write-host $report[$key].User is creator of $key
}
$_
}
$snapshotsExtra
}
Isn't this already adjusting by 5 seconds? This still seems like it's testing for a specific time. Is there a better way to do this so that I can find an entry, say +/- 10 seconds?
It reads the events starting 5 seconds before the create time it gets from the snapshot.
But later on it calculates a key, containing the time from the event, for the hashtable
$key = $_.EntityName + "&" + ($snapshot.CreateTime.ToLocalTime().ToString())
which it then later on uses the time from the snapshot to create a new key which it uses to check if there is an antry with that key in the hash table.
if($report.ContainsKey($key)){
What you could do is round the time in the key to for example a 10 seconds interval.
In other words, these time values, 21:17:10/21:17:14/21:17:19, would all become 21:17:10.
This is still not a 100% foolproof solution but should produce better matches between the create time from the snapshot and the event time.
PS: you could of course allso end up with "unknow" creators because that property is simply not there in the event.
I have seen that happen also a couple of times.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference