Since writing my original PowerCLI script to monitor vSphere for Orphaned snapshots, I have learnt quite a bit more about writing Scripts in PowerShell, mainly using parameter validation and error catching to make scripts more reliable.

With this new version of the script it is now configurable by starting the script with carious parameters to: alert on snapshots that have been in vCenter for X number of days; delete snapshots that have been in vCenter for X number of days; to not delete any snapshots with “Do Not Delete” in the description field; and to alert on orphaned snapshots.  These parameters can be used on there own or together in any combination.  There is a bit of an explanation of what the script is doing, just scroll to the bottom of the post to get the script in its entirety

The First section of the script simply enables the advanced functions of Powershell, allowing the script to support the -Verbose -Whatif parameters, then defines the custom parameters the script will accept and validates them where possible.

[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Medium")]
    PARAM 
        (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Connection $_})]
        [string] $vCenter,

        [ValidateRange(0,99)]
        [int] $RemoveOlderThan = 100,

        [switch] $DoNotDelete,

        [ValidateRange(0,99)]
        [string] $AlertOlderThan = 100,

        [switch] $AlertOrphan
        )

The next section is the begin block which contains a couple of checks that only need to be run the first time a script is run in a session  these test that the user is running PowerShell as an Administrator, Sets up a logging source for the alerts going to the Application log and then finally loads the VMware Snap-in to allow the management of vCenter server.  There is also the first “TRY/CATCH” block in this section which will catch any terminating errors from cmdlets being run and log the error to the application log.

BEGIN
        {
        Write-Verbose "Testing elevated privileges"
        $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
        $role = [Security.Principal.WindowsBuiltinRole]::Administrator

        $principal = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList $currentUser
        IF (-not $principal.IsInRole($role))
            {
            THROW "This process must be run with elevated privileges"
            }

        Write-Verbose "Adding logging source"
        IF(![System.Diagnostics.EventLog]::SourceExists("Kelway")) 
            { 
            [System.Diagnostics.EventLog]::CreateEventSource("Kelway","Application") 
            } 

        Write-Verbose "Loading VMware Snapin"
        IF ((Get-PSSnapin -Name VMware.VimAutomation.Core) -eq $null)
            {
            TRY
                {
                Add-PSSnapin VMware.VimAutomation.Core
                }
            CATCH
                {
                Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
                Write-Error "Loading the VMware snapin failed, check the Application log for Event ID 1234 for more information"
                }
            }       
        }

The next section is the beginning of the Process block where the actual work starts to be done.   Initially it is just connecting to the vCenter Server, there is then a series of IF statements to Remove Snapshots after X number of days if the parameter do so has been set, unless the DoNotDelete switch has been set and “Do Not Delete” is in the snapshots description.

PROCESS
        {
        Write-Verbose "Connecting to vCenter"
        TRY
            {
            Connect-VIServer -Server $vCenter
            }
        CATCH
            {
            Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
            Write-Error "Connecting to $vCenter failed, check the Application log for Event ID 1234 for more information"           
            }

        Write-Verbose "Retreiving list of Snapshots registered on vCenter"
        $SnapshotList = Get-VM | Get-Snapshot

        IF ($SnapshotList -ne $null)
            {
            IF ($DoNotDelete -eq $True)
                {
                IF ($RemoveOlderThan -ne 100)
                    {
                    Write-Verbose "Removing Snapshots older than $RemoveOlderThan days, that do not have $DoNotRemove in the description."
                    FOREACH ($Snapshot in $SnapshotList)
                        {
                        IF ($Snapshot.Created -le (Get-Date).AddDays(-$RemoveOlderThan) -and $Snapshot.Description -notlike "*Do Not Delete*")
                            {
                            IF ($psCmdlet.ShouldProcess($Snapshot.VM, "Deletes $Snapshot.Name"))
                                {
                                TRY
                                    {
                                    Remove-Snapshot -RunAsync -Snapshot $Snapshot -Confirm:$false
                                    }
                                CATCH
                                    {
                                    Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
                                    Write-Error "The request to remove $Snapshot.Name on $Snapshot.VM failed, check the Application log for Event ID 1234 for more information"
                                    }
                                }
                            }
                        }
                    }
                }
            ELSE
                {
                IF ($RemoveOlderThan -ne 100)
                    {
                    Write-Verbose "Removing Snapshots older than $RemoveOlderThan days."
                    FOREACH ($Snapshot in $SnapshotList)
                        {
                        IF ($Snapshot.Created -lt (Get-Date).AddDays(-$RemoveOlderThan))
                            {
                            IF ($psCmdlet.ShouldProcess($Snapshot.VM, "Deletes $Snapshot.Name"))
                                {
                                TRY
                                    {
                                Remove-Snapshot -RunAsync -Snapshot $Snapshot -Confirm:$false
                                    }
                                CATCH
                                    {
                                    Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
                                    Write-Error "The request to remove $Snapshot.Name on $Snapshot.VM failed, check the Application log for Event ID 1234 for more information"
                                    }
                                }
                            }
                        }
                    }
                }

Next the script performs some logic to check if the parameter has been set to alert on snapshots older than X number of days, if it has it then gets a list of those snapshots, then uses the vSphere SDK to attempt to get the name or the user who created the Snapshot but querying the Tasks in vCenter for a create snapshot task from within 5 seconds either way of the time the snapshot was created on the VM the snapshot was created on.  A custom array is then built to output the new extended snapshot details to and Application log event.

IF ($AlertOlderThan -ne 100)
                {
                Write-Verbose "Getting list of Old Snapshots"
                $OldSnapshotList = $SnapshotList | Where {$_.Created -LE (Get-Date).AddDays(-$AlertOlderThan)}
                IF ($OldSnapshotList -ne $null)
                    {
                    Write-Verbose "Getting Additional Snapshot Details"
                    #Open report array
                    $Report = @()
                    $SnapUser = ""

                    FOREACH ($Snapshot in $OldSnapshotList)
                        {
                        #Use Task log SDK to get the user who created the Snapshot
                        $TaskMgr = Get-View TaskManager
                        $TaskNumber = 100

                        #Create task filter for search
                        $Filter = New-Object VMware.Vim.TaskFilterSpec
                        $Filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime
                        $Filter.Time.beginTime = ((($Snapshot.Created).AddSeconds(-5)).ToUniversalTime())
                        $Filter.Time.timeType = "startedTime"
                        $Filter.Time.EndTime = ((($Snapshot.Created).AddSeconds(5)).ToUniversalTime())
                        $Filter.State = "success"
                        $Filter.Entity = New-Object VMware.Vim.TaskFilterSpecByEntity
                        $Filter.Entity.recursion = "self"
                        $Filter.Entity.entity = (Get-Vm -Name $Snapshot.VM.Name).Extensiondata.MoRef

                        $TaskCollector = Get-View ($TaskMgr.CreateCollectorForTasks($Filter))
                        $TaskCollector.RewindCollector | Out-Null
                        $Tasks = $TaskCollector.ReadNextTasks($TaskNumber)
                            #Get only the task for the snapshot in question and out put the username of the snapshot creator
                            FOREACH ($Task in $Tasks)
                                {
                                $GuestName = $Snapshot.VM
                                $Task = $Task | where {$_.DescriptionId -eq "VirtualMachine.createSnapshot" -and $_.State -eq "success" -and $_.EntityName -eq $GuestName}                          
                                IF ($Task -ne $null)
                                    {
                                    $SnapUser = $Task
                                    }
                                $TaskCollector.ReadNextTasks($TaskNumber)
                                }
                            #Create a custom object for reporting
                            $objReport = New-Object System.Object
                            $objReport | Add-Member -Type NoteProperty -Name "Name" -Value $Snapshot.Name
                            $objReport | Add-Member -Type NoteProperty -Name "Description" -Value $Snapshot.Description                 
                            $objReport | Add-Member -Type NoteProperty -Name "Created By" -Value $SnapUser.Reason.Username
                            $objReport | Add-Member -Type NoteProperty -Name "Attached To" -Value $Snapshot.VM
                            $objReport | Add-Member -Type NoteProperty -Name "Created On" -Value $Snapshot.Created
                            $Report += $objReport
                        #There is a default limit of 32 collector objects, destroying collector after use
                        $TaskCollector.DestroyCollector()

                        }
                    $Report = $Report | Out-String
                    Write-EventLog -LogName Application -Source "Kelway" -EventId 1067 -EntryType Error -Message "The following snapshots older than $Alertolderthan days are still in vCenter:`n $Report"
                    }
                }
            }

The final part of the script provides the functionality to alert when Orphaned snapshots are present, again there is an IF statement to determine if the AlertOrphan parameter has been set, it then goes about geting a list of all virtual hard disks in vSphere that end in a 6 digit number, denoting that they are snapshot disks and then if the VM with that disk does not have a snapshot present, these are Orphaned snapshots.  Again these are pulled together in to an array and logged to an event in the Application log.

IF ($AlertOrphan -eq $true)
            {
            $VMs = Get-VM
            $OrphanedSnapshotList = @()
            FOREACH ($VM in $VMs)
                {
                $HardDisks = $VM | Get-HardDisk

                FOREACH ($HardDisk in $HardDisks)
                    {
                    IF ($HardDisk.Filename -match ".*-[0-9]{6}.vmdk")
                        {
                        $Z = $null
                        $Z = Get-Snapshot -VM $VM
                        IF ($z -eq $null)
                            {
                            $OrphanedSnapshotList += $VM.Name
                            }
                        }
                    }
                }

            IF ($OrphanedSnapshotList -ne $null)
                {
                $OrphanedSnapshotList = $OrphanedSnapshotList | Out-String 
                Write-EventLog -LogName Application -Source "Kelway" -EventId 1066 -EntryType Error -Message "The following VMs have Orphaned Snapshots present:`n $OrphanedSnapshotList"
                }
            }
        }

Finally there is a section to provide a blank End block and comment based help.

END
        {
        }
># 
.SYNOPSIS
Helps to Manage snapshots in vSphere enviroment

.DESCRIPTION
Enables the abiltiy to either Alert on or Delete snapshots that have been on a system for longer than x days, and alert on orphaned snapshots.

.PARAMETER  vCenter
The I.P, Hostname or FQDN for the vCenter server, more recent versions of vCenter require the use of an FQDN that matches the certificate on the vCenter Server

.PARAMETER  RemoveOlderThan
Number of days a snapshot is allowed to exist for before deletion.

.PARAMETER Alert Older Than
Number of days a snapshot is allowed to exist for before an alert is generated

.PARAMETER AlertOrphan
Switch parameter to set whether an alert should be generated on discovery of an orphaned snapshot

.PARAMETER DoNotDelete
Switch parameter, if set and Snapshots with "Do Not Delete" in the description will not be deleted

.EXAMPLE 
Manage-SWSnapshots -Server "hostname.domain.com" -RemoveOlderThan 7 -AlertOlderThan 3 -AlertOrphan -DoNotDelete 

This wil Delete any snapshots older than 7 days unless the string "Do Not Delete" is in the description field and alert on any snapshots older than 3 days or Orphaned

.Example
Manage-SWSnapshots -Server "hostname.domain.com" -AlertOrphan

This will alert on Orphan snapshots only

.Example
Manage-SWSnapshots -Server "hostname.domain.com" -RemoveOlderThan 7 -AlertOlderThan 3
This will alert on snapshots older than 3 days and Remove all snapshots older than 7 days.

.INPUTS
None

.OUTPUTS
None

.NOTES

.LINK

>#

This version of the Script offers a number of improvements over the original one in terms or functionality and reliability, it also works completely self contained now and does not rely on being able to write out to text files.  I will include the full script below.

[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Medium")]
    PARAM 
        (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Connection $_})]
        [string] $vCenter,

        [ValidateRange(0,99)]
        [int] $RemoveOlderThan = 100,

        [switch] $DoNotDelete,

        [ValidateRange(0,99)]
        [string] $AlertOlderThan = 100,

        [switch] $AlertOrphan
        )

    BEGIN
        {
        Write-Verbose "Testing elevated privileges"
        $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
        $role = [Security.Principal.WindowsBuiltinRole]::Administrator

        $principal = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList $currentUser
        IF (-not $principal.IsInRole($role))
            {
            THROW "This process must be run with elevated privileges"
            }

        Write-Verbose "Adding logging source"
        IF(![System.Diagnostics.EventLog]::SourceExists("Kelway")) 
            { 
            [System.Diagnostics.EventLog]::CreateEventSource("Kelway","Application") 
            } 

        Write-Verbose "Loading VMware Snapin"
        IF ((Get-PSSnapin -Name VMware.VimAutomation.Core) -eq $null)
            {
            TRY
                {
                Add-PSSnapin VMware.VimAutomation.Core
                }
            CATCH
                {
                Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
                Write-Error "Loading the VMware snapin failed, check the Application log for Event ID 1234 for more information"
                }
            }       
        }

    PROCESS
        {
        Write-Verbose "Connecting to vCenter"
        TRY
            {
            Connect-VIServer -Server $vCenter
            }
        CATCH
            {
            Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
            Write-Error "Connecting to $vCenter failed, check the Application log for Event ID 1234 for more information"           
            }

        Write-Verbose "Retreiving list of Snapshots registered on vCenter"
        $SnapshotList = Get-VM | Get-Snapshot

        IF ($SnapshotList -ne $null)
            {
            IF ($DoNotDelete -eq $True)
                {
                IF ($RemoveOlderThan -ne 100)
                    {
                    Write-Verbose "Removing Snapshots older than $RemoveOlderThan days, that do not have $DoNotRemove in the description."
                    FOREACH ($Snapshot in $SnapshotList)
                        {
                        IF ($Snapshot.Created -le (Get-Date).AddDays(-$RemoveOlderThan) -and $Snapshot.Description -notlike "*Do Not Delete*")
                            {
                            IF ($psCmdlet.ShouldProcess($Snapshot.VM, "Deletes $Snapshot.Name"))
                                {
                                TRY
                                    {
                                    Remove-Snapshot -RunAsync -Snapshot $Snapshot -Confirm:$false
                                    }
                                CATCH
                                    {
                                    Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
                                    Write-Error "The request to remove $Snapshot.Name on $Snapshot.VM failed, check the Application log for Event ID 1234 for more information"
                                    }
                                }
                            }
                        }
                    }
                }
            ELSE
                {
                IF ($RemoveOlderThan -ne 100)
                    {
                    Write-Verbose "Removing Snapshots older than $RemoveOlderThan days."
                    FOREACH ($Snapshot in $SnapshotList)
                        {
                        IF ($Snapshot.Created -lt (Get-Date).AddDays(-$RemoveOlderThan))
                            {
                            IF ($psCmdlet.ShouldProcess($Snapshot.VM, "Deletes $Snapshot.Name"))
                                {
                                TRY
                                    {
                                Remove-Snapshot -RunAsync -Snapshot $Snapshot -Confirm:$false
                                    }
                                CATCH
                                    {
                                    Write-EventLog -LogName Application -Source "Kelway" -EventId 1234 -EntryType Error -Message $_.Exception.Message
                                    Write-Error "The request to remove $Snapshot.Name on $Snapshot.VM failed, check the Application log for Event ID 1234 for more information"
                                    }
                                }
                            }
                        }
                    }
                }

            IF ($AlertOlderThan -ne 100)
                {
                Write-Verbose "Getting list of Old Snapshots"
                $OldSnapshotList = $SnapshotList | Where {$_.Created -LE (Get-Date).AddDays(-$AlertOlderThan)}
                IF ($OldSnapshotList -ne $null)
                    {
                    Write-Verbose "Getting Additional Snapshot Details"
                    #Open report array
                    $Report = @()
                    $SnapUser = ""

                    FOREACH ($Snapshot in $OldSnapshotList)
                        {
                        #Use Task log SDK to get the user who created the Snapshot
                        $TaskMgr = Get-View TaskManager
                        $TaskNumber = 100

                        #Create task filter for search
                        $Filter = New-Object VMware.Vim.TaskFilterSpec
                        $Filter.Time = New-Object VMware.Vim.TaskFilterSpecByTime
                        $Filter.Time.beginTime = ((($Snapshot.Created).AddSeconds(-5)).ToUniversalTime())
                        $Filter.Time.timeType = "startedTime"
                        $Filter.Time.EndTime = ((($Snapshot.Created).AddSeconds(5)).ToUniversalTime())
                        $Filter.State = "success"
                        $Filter.Entity = New-Object VMware.Vim.TaskFilterSpecByEntity
                        $Filter.Entity.recursion = "self"
                        $Filter.Entity.entity = (Get-Vm -Name $Snapshot.VM.Name).Extensiondata.MoRef

                        $TaskCollector = Get-View ($TaskMgr.CreateCollectorForTasks($Filter))
                        $TaskCollector.RewindCollector | Out-Null
                        $Tasks = $TaskCollector.ReadNextTasks($TaskNumber)
                            #Get only the task for the snapshot in question and out put the username of the snapshot creator
                            FOREACH ($Task in $Tasks)
                                {
                                $GuestName = $Snapshot.VM
                                $Task = $Task | where {$_.DescriptionId -eq "VirtualMachine.createSnapshot" -and $_.State -eq "success" -and $_.EntityName -eq $GuestName}                          
                                IF ($Task -ne $null)
                                    {
                                    $SnapUser = $Task
                                    }
                                $TaskCollector.ReadNextTasks($TaskNumber)
                                }
                            #Create a custom object for reporting
                            $objReport = New-Object System.Object
                            $objReport | Add-Member -Type NoteProperty -Name "Name" -Value $Snapshot.Name
                            $objReport | Add-Member -Type NoteProperty -Name "Description" -Value $Snapshot.Description                 
                            $objReport | Add-Member -Type NoteProperty -Name "Created By" -Value $SnapUser.Reason.Username
                            $objReport | Add-Member -Type NoteProperty -Name "Attached To" -Value $Snapshot.VM
                            $objReport | Add-Member -Type NoteProperty -Name "Created On" -Value $Snapshot.Created
                            $Report += $objReport
                        #There is a default limit of 32 collector objects, destroying collector after use
                        $TaskCollector.DestroyCollector()

                        }
                    $Report = $Report | Out-String
                    Write-EventLog -LogName Application -Source "Kelway" -EventId 1067 -EntryType Error -Message "The following snapshots older than $Alertolderthan days are still in vCenter:`n $Report"
                    }
                }
            }

        IF ($AlertOrphan -eq $true)
            {
            $VMs = Get-VM
            $OrphanedSnapshotList = @()
            FOREACH ($VM in $VMs)
                {
                $HardDisks = $VM | Get-HardDisk

                FOREACH ($HardDisk in $HardDisks)
                    {
                    IF ($HardDisk.Filename -match ".*-[0-9]{6}.vmdk")
                        {
                        $Z = $null
                        $Z = Get-Snapshot -VM $VM
                        IF ($z -eq $null)
                            {
                            $OrphanedSnapshotList += $VM.Name
                            }
                        }
                    }
                }

            IF ($OrphanedSnapshotList -ne $null)
                {
                $OrphanedSnapshotList = $OrphanedSnapshotList | Out-String 
                Write-EventLog -LogName Application -Source "Kelway" -EventId 1066 -EntryType Error -Message "The following VMs have Orphaned Snapshots present:`n $OrphanedSnapshotList"
                }
            }
        }

    END
        {
        }
<# 
.SYNOPSIS
Helps to Manage snapshots in vSphere enviroment

.DESCRIPTION
Enables the abiltiy to either Alert on or Delete snapshots that have been on a system for longer than x days, and alert on orphaned snapshots.

.PARAMETER  vCenter
The I.P, Hostname or FQDN for the vCenter server, more recent versions of vCenter require the use of an FQDN that matches the certificate on the vCenter Server

.PARAMETER  RemoveOlderThan
Number of days a snapshot is allowed to exist for before deletion.

.PARAMETER Alert Older Than
Number of days a snapshot is allowed to exist for before an alert is generated

.PARAMETER AlertOrphan
Switch parameter to set whether an alert should be generated on discovery of an orphaned snapshot

.PARAMETER DoNotDelete
Switch parameter, if set and Snapshots with "Do Not Delete" in the description will not be deleted

.EXAMPLE 
Manage-SWSnapshots -Server "hostname.domain.com" -RemoveOlderThan 7 -AlertOlderThan 3 -AlertOrphan -DoNotDelete 

This wil Delete any snapshots older than 7 days unless the string "Do Not Delete" is in the description field and alert on any snapshots older than 3 days or Orphaned

.Example
Manage-SWSnapshots -Server "hostname.domain.com" -AlertOrphan

This will alert on Orphan snapshots only

.Example
Manage-SWSnapshots -Server "hostname.domain.com" -RemoveOlderThan 7 -AlertOlderThan 3
This will alert on snapshots older than 3 days and Remove all snapshots older than 7 days.

.INPUTS
None

.OUTPUTS
None

.NOTES

.LINK

#>

Comments