PowerCLI: SnapReminder

Fed up of chasing those people who constantly create snapshots and leave them hanging around for weeks or even months on end ?

You no longer have to do the chasing, just use the following script to automatically find the offending snapshot, find the person who created it, get their email address from AD and send them an email reminding them of their mortal sin.

The email address is taken fromΒ  the Email field as shown below:

SnapReminder_AD

A few requirements:

  • The accounts must have the E-Mail field filled out
  • The account you run the script as must have read permissions to AD (Any member of the domain should have this)
  • You need to fill in the smtp server address, VI Server name and the from address at the top of this script
  • You can run this as a scheduled task to constantly email the offending parties πŸ™‚
  • The below script is set to remind of anything over 2 weeks old but this can easily be amended
    Once completed the offending person will receive an email as below:SnapReminder

    Thanks to LucD for helping with finding the user who created the snapshot on the PowerCLI Forum.

The Script:

# - SnapReminder V1.0 By Virtu-Al - http://virtu-al.net
#
# Please use the below variables to define your settings before use
#
$smtpServer = "mysmtpserver.mydomain.com"
$MailFrom = "me@mydomain.com"
$VISRV = "MYVISERVER"

function Find-User ($username){
	if ($username -ne $null)
	{
		$usr = (($username.split("\"))[1])
		$root = [ADSI]""
		$filter = ("(&(objectCategory=user)(samAccountName=$Usr))")
		$ds = new-object system.DirectoryServices.DirectorySearcher($root,$filter)
		$ds.PageSize = 1000
		$ds.FindOne()
	}
}

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){
	$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"
	
	$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
}

Function SnapMail ($Mailto, $snapshot)
{
	$msg = new-object Net.Mail.MailMessage
	$smtp = new-object Net.Mail.SmtpClient($smtpServer)
	$msg.From = $MailFrom
	$msg.To.Add($Mailto)

	$msg.Subject = "Snapshot Reminder"

$MailText = @"
This is a reminder that you have a snapshot active on $($snapshot.VM) which was taken on $($snapshot.Created).

Name: $($snapshot.Name)

Description: $($snapshot.Description)
"@	

	$msg.Body = $MailText
	$smtp.Send($msg)
}

Connect-VIServer $VISRV

foreach ($snap in (Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-14))})){
	$SnapshotInfo = Get-SnapshotExtra $snap
	$mailto = ((Find-User $SnapshotInfo.Creator).Properties.mail)
	SnapMail $mailto $SnapshotInfo
}

67 thoughts on “PowerCLI: SnapReminder”

  1. At my company we use an admin account that is based on our domain user account appended with “_admin”. So if my username is ghr, my admin account is ghr_admin. Unfortunately, there are no email addresses associated with the admin account and the admin account will be the “creator” of the snapshot.

    How do I “trim” the creator so the it is the username without the _admin so the $Mailto will work?

    Thanks

    1. I’ve exactly the same case where we prefix the user with Admin-
      i would like to know how to trim the same so I can make use of the script

  2. I keep getting the “You cannot call a method on a null-valued expression too.
    At C:\Scripts\SnapshotCreator.ps1:63 char:4” error. It seems to come from transient snapshots. That is, our backup procedures create and delete many snapshots. The snapshot creation shows up in the event tasks but when the snapshots no longer exist you get the null error. Adding the line “if (!$snapshot) { return }” after $snapshot is defined around line 64 skips the deleted snapshots. There may be a better solution. I am new to PS and PowerCLI.

    1. I found the answer. Depending on whether you want to add another ‘To’, ‘Cc’ or Bcc’ email address, add the following lines in Snapmail function:
      $msg.To.Add(first.last@company.com)
      $msg.CC.Add($EmailCc)
      $msg.BCC.Add($EmailBCc)

  3. I really need to get this script working as there are several people in my office that create snapshots and never go back and delete them. After putting in my details at the top, I’ve changed the date of creation time from 14 days to 2 days. When I run this I get:
    You cannot call a method on a null-valued expression.
    At C:\Scripts\SnapshotCreator.ps1:63 char:4
    + $key = $_.EntityName + “&” + ($snapshot.CreateTime.ToString())
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorID : InvokeMethodOnNull

    If I run this it gives me the VM Name, CreationDate, and the User (which isn’t Local Admin account):
    $myVMs = Get-VM
    $VMsWithSnaps = @()
    foreach ($vm in $myVMs) {
    $vmView = $vm | Get-View
    if ($vmView.snapshot -ne $null) {
    Write-Host “VM $vm has a snapshot”
    $SnapshotEvents = Get-VIEvent -Entity $vm -type info -MaxSamples 1000 | Where {
    $_.FullFormattedMessage.contains(“Create virtual machine snapshot”)}
    try {
    $user = $SnapshotEvents[0].UserName
    $time = $SnapshotEvents[0].CreatedTime
    } catch [System.Exception] {
    $user = $SnapshotEvents.UserName
    $time = $SnapshotEvents.CreatedTime
    }
    $VMInfo = β€œβ€ | Select “VM”,”CreationDate”,”User”
    $VMInfo.”VM” = $vm.Name
    $VMInfo.”CreationDate” = $time
    $VMInfo.”User” = $user
    $VMsWithSnaps += $VMInfo
    }
    }
    $VMsWithSnaps | Sort CreationDate

    What do I need to change in the SnapReminder script to get it to work properly and email the SnapShot creator?

  4. hi,
    how can I see the list of task snapshot failure on VM ?
    As seen in the ESX Task view panel (not Event view).
    I do not want to have the list of event but list task thougt PowerCLI.
    thank you

  5. Any way to be able to poll 2 vcenter servers in the one script? I get an error when I add multiples.

  6. Hello,

    This appears to have stopped working after we upgraded our vcenter to 5.0, are you planning on releasing an updated version?
    Also, is it possible to add the ability to ignore certain machines, for example Veeam now snapshots each replica so we would want to ignore all machines ending in “_replica”.

    Cheers

    Lloyd

  7. Cannot get it working.
    Getting the same error as P on the line 63. From what I read it has to do something with the time format. Removing the line won’t help.

  8. Hello i came across this script and this is exactly what we need. My problem is I can’t get it to work in our environment. I keep getting the follow error when I execute it.

    You cannot call a method on a null-valued expression.
    At C:\SnapReminder.ps1:63 char:63
    + $key = $_.EntityName + “&” + ($snapshot.CreateTime.ToString <<<<
    ())
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeE
    xception
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At C:\SnapReminder.ps1:63 char:63
    + $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString <<<<
    ())
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeE
    xception
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At C:\SnapReminder.ps1:63 char:63
    + $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString <<<<
    ())
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeE
    xception
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At C:\SnapReminder.ps1:63 char:63
    + $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString <<<<
    ())
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeE
    xception
    + FullyQualifiedErrorId : InvokeMethodOnNull

    You cannot call a method on a null-valued expression.
    At C:\SnapReminder.ps1:63 char:63
    + $key = $_.EntityName + "&" + ($snapshot.CreateTime.ToString <<<<
    ())
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeE
    xception
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Exception calling "Add" with "1" argument(s): "Value cannot be null.
    Parameter name: item"
    At C:\SnapReminder.ps1:85 char:13
    + $msg.To.Add <<<< ($Mailto)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

    Exception calling "Send" with "1" argument(s): "A recipient must be specified.
    At C:\SnapReminder.ps1:98 char:12
    + $smtp.Send <<<< ($msg)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

    Any help would be great. I have read through the treads and tried changing some of the lines as suggested and still no go. Any thoughts?

  9. I just can’t get this script to work it just keeps saying that it doesn’t expect $smptServer at he beginning.nplease please help

    Thanks Steve

  10. This is a great script you can run on a daily scheduled basis. Just modify the following statement in the ‘foreach’ loop towards the bottom of the script: “(Get-Date).AddDays(0)”. Awesome script and works as described. Kudos to the author!

  11. Dumped some more objects and actually it WAS what Jurgen suggested. Probably works nicely in UK as-is, but if the timezone isn’t GMT/UTC you’re generating two different $key’s in the code.
    This resolved some of the problems, but still _old_ (over 2yrs or so) snapshots are missing the creator – for which I can see two plausible reasons – too many tasks and/or this damn DST (making the deviation an ever-changing target).
    But this is a minor issue, since who would want daily e-mails for lengthy periods of time πŸ˜‰
    Thanks for the script anyway.

  12. Whew, I’m not alone!

    I too have spent the day trying to get SnapReminder able to email the creator of the snapshot. It seems that there is not a “Creator” attribute (value? variable?). When I run the script through the PowerGUI Script Editor, I can expand the$SnapshotInfo variable, I do not see “Creator” – or any reference to the creator of the snapshot.

    I’ve found a script that pulls out snapshot creator information from the VPX_TASK table in the vCenter database, so the information is in there. I’m not sure where else it might recorded.

  13. Thanks for the swift response. Unfortunately the output is (for each snapshot) “Snapshot ***** has a creator of .”
    I can see the use that initiated the taks in vSphere client, so the info has to be somewhere. I also already tried that timedeviation-trick that Jurgen suggested, although I’m not quite sure why it would help since all the servers are on the same timezone.

  14. What do you get if you change the last loop to this, do you get a username ?

    foreach ($snap in (Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-14))})){
       $SnapshotInfo = Get-SnapshotExtra $snap  
    Write-Host "Snapshot $($snap.name) has a creator of $($SnapshotInfo.Creator)."	    
    }
  15. I tried the script on 4.1 and it gets all the snapshots (and sizes with a small modification) but the creator can not be found for ANY of the dozen or so snapshots. Any ideas why? The snapshots have been created using vSphere client, either while on ESX 4.0 or on ESXi 4.1 – no difference in results. I tried to dump the whole SnapshotInfo-object, but couldn’t find anything pointing to the creator of the snapshot.

  16. Or change the following rule:

    $key = $_.EntityName + “&” + ($snapshot.CreateTime.ToString())

    To:

    $timedeviation = get-date -uformat “%Z”
    $key = $_.EntityName + “&” + (($snapshot.CreateTime.AddHours($timedeviation)).ToString())

  17. Chris

    Remove the following line:

    $key = $_.vm.Name + “&” + ($_.Created.ToString())

    That works for me because i’m in a not UCT/GMT timezone

  18. Can anyone help me. I keep getting the same error running this script. It seems like it can’t get the username.

    Exception calling “Add” with “1” argument(s): “Value cannot be null.
    Parameter name: item”
    At C:\Users\gateway\Desktop\snap.ps1:86 char:13
    + $msg.To.Add <<<< ($Mailto)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

    Exception calling "Send" with "1" argument(s): "A recipient must be specified."
    At C:\Users\gateway\Desktop\snap.ps1:99 char:12
    + $smtp.Send <<<< ($msg)
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException

  19. How can I have the following script omit removing snaps from certain vm’s? I want to remove majority of snapshots from vm’s, but not all of my vm’s but this would be very tedious to do by hand!

    script –

    $vcserver=”xxxxxx”

    Add-PSsnapin VMware.VimAutomation.Core
    #Initialize-VIToolkitEnvironment.ps1
    connect-VIServer $vcserver

    get-vm | get-snapshot | where { $_.name -eq “xxxxxxxxxx” } | remove-snapshot -confirm:$false

  20. @Andreea –

    Should be – just change this line:
    $mailto = ((Find-User $SnapshotInfo.Creator).Properties.mail)

    Where it refers to .mail, find out what the Web Page field is called and use it here.

  21. To login to vC we are using OU accounts and we can’t add the email to the E-Mail field because it’s already in use by our regular accounts. Is there any way I can put the email under Web Page filed and point to that field for the emails to be sent?
    Thanks

  22. How can I tell this script to get all snapshots regardless. I tried changing the line

    foreach ($snap in (Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-14))

    to

    foreach ($snap in (Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(0))

    but I get a NULL value error.
    Thanks

  23. Hi Frank,

    I have done this like that:

    $MailText = @”
    This is a reminder that you have a snapshot active on $($snapshot.VM) which was taken on $($snapshot.Created).

    Created by: $($(Find-User $SnapshotInfo.Creator).Properties.name)
    Email: $($(Find-User $SnapshotInfo.Creator).Properties.mail)

    Name: $($snapshot.Name)
    Description: $($snapshot.Description)
    Snapshot size: $([math]::Round($snapshot.SizeMB))MB
    “@

    $msg.Body = $MailText
    $smtp.Send($msg)
    }

    So you will have nice presentation of who/when/why/size and you probably can put whatever info youd like to πŸ™‚ just need to add few lines…

    My question on the other hand is:

    how could I create only one email with all of the snapshots taken?

    i am thinking on putting a loop on the end of the script but not sure where…

  24. Is it also possible to add the users fullname. So you can personalize the mail.

    Hi $fullname

    blablabla

    I tried to alter the script but i’m a complete noob without people I know with the rigt knowledge.

    1. I just did this – here you go:

      Modify the “SnapMail” function as follows:
      Function SnapMail ($names, $MailTo, $snapshot)
      {
      $msg = new-object Net.Mail.MailMessage
      $smtp = new-object Net.Mail.SmtpClient($smtpServer)
      $msg.From = $smtpFrom
      $msg.To.Add($MailTo)
      $msg.Subject = $noticeName
      $MailText = @”
      $names,

      You have an active snapshot on the VM ($($snapshot.VM)) created on $($snapshot.Created).
      Name: $($snapshot.Name)
      Description: $($snapshot.Description)
      Snapshot size: $([math]::Round($snapshot.SizeMB))MB

      Please can you review this Snapshot and the justification for keeping it. If not, please
      “@
      $msg.Body = $MailText
      $smtp.Send($msg)
      }

      The little foreach loop at the bottom – now looks like this:
      foreach ($snap in (Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-$daysOld))})){
      $SnapshotInfo = Get-SnapshotExtra $snap
      $mailto = ((Find-User $SnapshotInfo.Creator).Properties.mail)
      $smtpGivenname = ((Find-User $SnapshotInfo.Creator).Properties.givenname)
      $smtpSurname = ((Find-User $SnapshotInfo.Creator).Properties.sn)
      $smtpFullname = ((Find-User $SnapshotInfo.Creator).Properties.displayName)
      #SnapMail $mailto $SnapshotInfo
      SnapMail $smtpGivenname $smtpTo $SnapshotInfo
      #SnapMail $smtpSurname $smtpTo $SnapshotInfo
      #SnapMail $smtpFullname $smtpTo $SnapshotInfo
      }

      Uncomment the line you want to use – comment out givenName if you don’t need it.

  25. Sorry I didn’t get to this for a few days. I changed the for loop and am getting the same result. One thing that I notice is that I get lots (LOTS) of errors while running the script like this:

    You cannot call a method on a null-valued expression.
    At C:\Users\pat\Desktop\SnapReminder.ps1:63 char:63
    + $key = $_.EntityName + “&” + ($snapshot.CreateTime.ToString <<<< ())
    + CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

    Daniel's post from 2009 indicates that this is because the administrator created the snapshot which isn't the case in my situation although the script says that the local administrator ("meteor") did create it.

    Anyway, I'm running it against my test lab cluster where there are 3 snapshots. Here's an example of the information from one of the snapshots:

    Creator : meteor
    Description :
    Created : 11/29/2010 2:30:44 PM
    Quiesced : False
    PowerState : PoweredOn
    VM : centrify3
    VMId : VirtualMachine-vm-3883
    Parent :
    ParentSnapshotId :
    Children :
    SizeMB : 7721.44
    IsCurrent : True
    IsReplaySupported : False
    Id : VirtualMachineSnapshot-snapshot-3886
    Name : before centrify install

    Here's the information on the same snapshot taken from a daily vCheck report generated this morning at 3AM:
    centrify3 before centrify install 51 Allen, Pat 8714.44 11/29/2010 2:30:44 PM
    It shows the correct creator, "Allen, Pat". It's interesting to note that the size is different. At 3AM it was 8714MB whereas at 2PM it's 7721MB.

    Any thoughts would certainly be appreciated! THANKS!
    Pat

  26. This script doesn’t work if the snap was created with a scheduled task – there is no email recipient. Is there a way around this?

  27. Hi Al,

    I’ve run vCheck on a daily basis for quite a while now and it’s GREAT! Thanks for putting this and everything else out in the community.

    I decided to put together a script to automate reminding folks about snapshots. My first idea was parsing the output of vCheck but then I checked your site and found SnapRemind! This is EXACTLY what I need to put together and it would save me a tremendous amount of work. (Especially considering that I’m an old Unix nerd and I’m just starting to learn Power CLI.) But I’m having a little problem with things. When I run your vCheck script, it shows me all the snapshots with the correct creator names. When I run the SnapReminder script, it always shows the name of the local administrator as the creator of the snapshot. The local admin doesn’t have an email address so no mail is sent. I’m running vSphere 4.1 in case that matters.

    Any idea what might be wrong?

    THANKS again!
    Pat

    1. That is strange, what happens if you replace the bottom foreach loop with this:

      foreach ($snap in (Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-14))})){
      Get-SnapshotExtra $snap
      }

      Do you get the creator for each snapshot listed ?

  28. Mr G

    Sure, just change line 104 to the following:

    foreach ($snap in (Get-Datacenter “MyDC” | Get-VM | Get-Snapshot | Where {$_.Created -lt ((Get-Date).AddDays(-14))})){

  29. @Falko
    I had this error also. In my case it was because the Administrator made the snapshot an there is no email adress for Administrator in the AD. My Solution:

    Line 10:
    if ($username -ne $null -and $username -ne “Administrator”)

    Line 86:
    if($Mailto -eq $null){
    $msg.To.Add(“vmadmin@blah.bla”)
    }
    else{
    $msg.To.Add($Mailto)
    }

    Hth

  30. Well, for me it is semi-working. I get emails, but the script gives me the following error:

    You cannot call a method on a null-valued expression.
    At :line:63 char:83
    + $key = $_.EntityName + “&” + ($snapshot.CreateTime.ToString <<<< ())

    Index operation failed; the array index evaluated to null.
    At :line:64 char:32
    + $report[ <<<< $key] = $row

  31. Good script,thanks. I had a problem with the timing, so I changed the line 50 to:
    $filter.Time.beginTime = (($snap.Created).AddMinutes(-30)

  32. @chris
    My blog mangled the code, I have now moved it over to a different hosting method so please try the above.

    Let me know if you have any further issues.

  33. I get an error when running the script error line 93 char 13

    + $MailText = @ <<<< "

    If I remove the @ I get another error with another @ symbol farther down.

Leave a Reply