This is a script I wrote to delete snapshots over a certian age wth an override. The override is in the name of the snapshot, so the snapshot can be deleted after x days or after a date.
If the snapshot is named “my snapshot _ DeleteAfter60days” the script will look for “DeleteAfter” or “DA” after the last “_”. If it finds it it will remove that string (and “days”) if the resulting string is numeric and less then 6 didgits it will assume the number is days to keep. If on the other hand the number is 6 or more didgits it will assume its a date.
This is best expianed by example.
Snapshot name Action
- snapshot Deleted after the defualt days
- snap_DeleteAfter10 Deleted after 10 days
- snap_da20 Deleted after 20 days
- snap_da50days Deleted after 50 days
- snap _da01/01/2020 Deleted after the 01/01/2020
- snap_DoNotDelete Is ignored by the script
- snap_da01012021 Deleted afterthe 01/01/2021
- snap_da101220 Deleted afterthe 10/12/2020
So here is the script. I have left the -whatif attribute on the delete snapshot commands so running this script should not do any harm.
# Semi smart snapshot deleter script # Jason Street # 10/2020 # # script will delete any snapshot over $DeleteAfterDays old. # unles the end of the snap name is _DAx or _DeleteAfter x (x = date, in format ddmmyyyy or dd/mm/yyyy) or # or _DAn (n = number of days sinse creation) # or _$IgnoreString (will be ignored by the sctipt) $vCenter = "vcenter001.jase.local" $DeleteAfterDays = 60 $IgnoreString = "DoNotDelete" $LogFile = ".\SnapDel.log" $DebugFile = ".\SnapDel_debug.log" ### start of functions function Send-DebugLog { [CmdletBinding()] param ( [Parameter(Mandatory = $false,ValueFromPipeline=$true)] $Message, [Parameter(Mandatory = $false)] $Result, # result of last command ($?) [Parameter(Mandatory = $false)] [string]$LogFilePath = $null, # log file location [Parameter(Mandatory = $false)] [string]$DebugFilePath = $null, # debug file location [Parameter(Mandatory = $false)] [switch]$ScreenPrint = $false, # print message to the screen [Parameter(Mandatory = $false)] [switch]$LogIt = $false, # send message to log file if set [Parameter(Mandatory = $false)] [switch]$ClearLog = $false, # clears the log file [Parameter(Mandatory = $false)] [switch]$ClearDebug = $false # clears the debug log file ) #if ($DebugFilePath -eq $null){$DebugFilePath = $DebugFile} #if ($LogFilePath -eq $null){$LogFilePath = $LogFile} if ($ClearLog -eq $true){"Script run at " + ((get-date).toString()) | Out-File $LogFile} if ($ClearDebug -eq $true){"Script run at " + ((get-date).toString()) | Out-File $DebugFile} if ($Message -ne $Null) { if ($Result -eq '' -or $Result -eq $null){$OutMessage = $Message} if ($Result -eq $true){$OutMessage = "OK " + $Message} if ($Result -eq $false){$OutMessage = "Fail " + $Message} $OutMessage | out-file $DebugFile -Append if ($LogIt -eq $true){$OutMessage | out-file $LogFile -Append} if ($ScreenPrint -eq $true) { if ($Result -eq $Null){write-host $OutMessage -ForegroundColor Gray} if ($Result -eq $true){write-host $OutMessage -ForegroundColor Green} if ($Result -eq $false){write-host $OutMessage -ForegroundColor red} } } } function isNumeric ($x) { try { 0 + $x | Out-Null return $true } catch { return $false } } <# .SYNOPSIS To look for a date string in a text string .DESCRIPTION will look for the following at the end of a text string _DAddmmyy, _DeleteAfterxDays, _DA dmyy and return the a data object (if a date is found or a int of the number of days or a $null if not found .PARAMETER NameString The test string to be searched .PARAMETER Dilimitor the Dilimitor between the user test and the date text #> function Get-DateFromName { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$NameString, [Parameter(Mandatory = $false)] [string]$Dilimitor = "_", [Parameter(Mandatory = $false)] [string]$IgnoreString = $null ) "Entering function Get-DatefromName" | Send-DebugLog " with $NameString" | Send-DebugLog # get last "word" and remove spaces $LWord = ($NameString.split($Dilimitor))[($NameString.split($Dilimitor)).count - 1] $LWord = $LWord -replace ' ','' # check the last word has the keep override in if ($IgnoreString -ne $null) { if ($LWord -eq $IgnoreString) { return "Ignore" } } # check the last word has the delete override in if (($LWord -like "da*") -or ($Lword -like "deleteafter*")) { # remove the known text as it is not needed. $LWord = $LWord -replace "deleteafter","da" $LWord = $LWord -replace "days",'' $LWord = $LWord -replace "da",'' $LWord = $LWord -replace "%2f","." $LWord = $LWord -replace "%5c","." $LWord = $LWord -replace "%252f","." $LWord = $LWord -replace " ",'' " LWord = $LWord" | Send-DebugLog # test if all we have in numbers left if (isNumeric $LWord) { # is most likely a day counter but could be a date with no delims if ($LWord.length -lt 6) { # number less then 6 didgits, assuming its just a day #"Assuming is a day" " returning int = $LWord"| Send-DebugLog return [int32]$LWord }else{ # nummer 6 more more didgits, assuming its a date with the format DDMMYY if ($LWord.length -eq 6) { # assuming date is in format YYMMDD " returning date = " + (get-date -Year $LWord.Substring(4,2) -Month $LWord.Substring(2,2) -day $Lword.Substring(0,2)) | Send-DebugLog return get-date -Year $LWord.Substring(4,2) -Month $LWord.Substring(2,2) -day $Lword.Substring(0,2) }else{ if ($LWord.length -eq 8) { # assuming date is in format DDMMYYYY " returning date = " + (get-date -Year $LWord.Substring(8,4) -Month $LWord.Substring(4,2) -day $Lword.Substring(0,2)) | Send-DebugLog return get-date -Year $LWord.Substring(8,4) -Month $LWord.Substring(4,2) -day $Lword.Substring(0,2) }else{ # No idea what the number is " returning null"| Send-DebugLog return $null } } } }else{ # could be a date if (!(isNumeric $LWord.Substring(2,1)) ) { # 3rd charrictor is not a number, could be a delimitor # remove all occerances of the 3rd charrictor and see if we get a number. if (isNumeric $LWord.replace($LWord.Substring(2,1),'')) { # date most likely to be DDxMMxYYYY $DateBits = $LWord.split($LWord.Substring(2,1)) # check the yaer is 2 digits, if so add 2000 to it if ($DateBits[2].tostring().Length -eq 2){$DateBits[2] = [int]$DateBits[2] + 2000} " returning date = " + (get-date -Year $DateBits[2] -Month $DateBits[1] -day $DateBits[0]) | Send-DebugLog return get-date -Year $DateBits[2] -Month $DateBits[1] -day $DateBits[0] }else{ # date is not a number with out the dilims removed. unable to process. " returning null"| Send-DebugLog return $null } }else{ if (!(isNumeric $LWord.Substring(1,1)) ) { # 2nd charrictor is not a number, could be a delimitor # check $LWord is a number after removing the 2nd charrictor (in all locations as there will be 2 dilims) if (isNumeric $LWord.replace($LWord.Substring(1,1),'')) { # date is most likely in the format DxMMxYYYY $DateBits = $LWord.split($LWord.Substring(1,1)) # check if the yaer is 2 digits, if so add 2000 to it if ($DateBits[2].tostring().Length -eq 2){$DateBits[2] = [int]$DateBits[2] + 2000} " returning date = " + (get-date -Year $DateBits[2] -Month $DateBits[1] -Day $DateBits[0]) | Send-DebugLog return get-date -Year $DateBits[2] -Month $DateBits[1] -Day $DateBits[0] }else{ # date is not a number with out the dilims removed. unable to process. " returning null"| Send-DebugLog return $null } }else{ " returning null"| Send-DebugLog return $null } } } }else{ # Override keywords not found " returning null, override keywords not found"| Send-DebugLog return $null } } ### end of functions Send-DebugLog -LogIt -ClearLog -ClearDebug connect-viserver vCenter001.jase.local $Snaps = get-vm | get-snapshot foreach ($Snap in $Snaps) { # Loop throug each snapshot # read the snapshot name and work out if there is delete override $DelInfo = Get-DateFromName ($Snap.name) "_" -IgnoreString $IgnoreString if ($DelInfo) { # function returned a date if ($DelInfo.gettype().name -eq "DateTime") { # snap has a delete after date if ($DelInfo -lt (get-date)) { # snap has a delete date is is now old enougth to be deleted ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " will be deleted !! " + $DelInfo.tostring() + " in " + [int](($DelInfo - (get-date)).TotalDays) + " days") | Send-DebugLog -LogIt -ScreenPrint Remove-Snapshot $Snap -WhatIf "Deleting Snapshot on VM " + $Snap.vm + " called " + $Snap.name | Send-DebugLog -Result $? -LogIt -ScreenPrint }else{ # snap has a delete date is is not old enougth yet to be deleteded ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " will be deleted after " + $DelInfo.tostring() + " in " + [int](($DelInfo - (get-date)).TotalDays) + " days") | Send-DebugLog -LogIt -ScreenPrint } } # function returned a number if ($DelInfo.gettype().name -eq "Int32") { # snap has a number of days to keep if ($Snap.created.adddays($DelInfo) -lt (get-date)) { # snap has reached its expirary in days and will be deleted. ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " will be deleted as its " + $DelInfo.tostring() + " days old " ) | Send-DebugLog -LogIt -ScreenPrint Remove-Snapshot $Snap -WhatIf "Deleting Snapshot on VM " + $Snap.vm + " called " + $Snap.name | Send-DebugLog -Result $? -LogIt -ScreenPrint }else{ # snap has not reached it "expirary" het so will not be deleted (yet). ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " will be deleted in " + [int](($Snap.created.adddays($DelInfo) - (get-date)).totaldays) + " days " ) | Send-DebugLog -LogIt -ScreenPrint } } # function returned the string "ignore" if ($DelInfo -eq "ignore") { # the function fond the ignor sting. This script will not delete this snapshot. ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " has been set to ignore ") | Send-DebugLog -LogIt -ScreenPrint } }else{ # function returned a null if ($Snap.created.adddays($DeleteAfterDays) -lt (get-date)) { ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " unknown, but its older then $DeleteAfterDays days") | Send-DebugLog -LogIt -ScreenPrint Remove-Snapshot $Snap -WhatIf "Deleting Snapshot on VM " + $Snap.vm + " called " + $Snap.name | Send-DebugLog -Result $? -LogIt -ScreenPrint }else{ ("VM " + $Snap.vm + " with snapshot " + $Snap.name + " unknown, but its not going to get deleted for " + [int](($Snap.created.adddays($DeleteAfterDays) - (get-date)).TotalDays) + " days") | Send-DebugLog -LogIt -ScreenPrint } } }
One thing to note is that the log will be over-written every time the script runs. So currently there will no perminant log of what snapshots where deleted.