Public/Clear-AnyStackOrphanedSnapshots.ps1

function Get-FlatSnapshotList {
    param([object[]]$SnapshotTree)
    foreach ($snap in $SnapshotTree) {
        $snap
        if ($snap.ChildSnapshotList) {
            Get-FlatSnapshotList -SnapshotTree $snap.ChildSnapshotList
        }
    }
}

function Clear-AnyStackOrphanedSnapshots {
    <#
    .SYNOPSIS
        Removes old VM snapshots.
    .DESCRIPTION
        Deletes snapshots older than AgeDays.
    .PARAMETER Server
        vCenter Server hostname or VIServer object. Uses active connection if omitted.
    .PARAMETER AgeDays
        Age in days (default 7).
    .PARAMETER VmName
        Filter by VM.
    .PARAMETER ClusterName
        Filter by cluster.
    .EXAMPLE
        PS> Clear-AnyStackOrphanedSnapshots
    .OUTPUTS
        PSCustomObject
    .NOTES
        Author: The AnyStack Architect
        Requires: VCF.PowerCLI 9.0+, vSphere 8.0 U3+
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [ValidateNotNull()]
        $Server,
        [Parameter(Mandatory=$false)]
        [int]$AgeDays = 7,
        [Parameter(Mandatory=$false)]
        [string]$VmName,
        [Parameter(Mandatory=$false)]
        [string]$ClusterName
    )
    begin {
        $vi = Get-AnyStackConnection -Server $Server
        $ErrorActionPreference = 'Stop'
    }
    process {
        try {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Clearing snapshots on $($vi.Name)"
            $filter = if ($VmName) { @{Name="*$VmName*"} } else { $null }
            $vms = Invoke-AnyStackWithRetry -ScriptBlock { Get-View -Server $vi -ViewType VirtualMachine -Filter $filter -Property Name,Snapshot }
            
            $threshold = (Get-Date).AddDays(-$AgeDays)
            foreach ($vm in $vms) {
                if ($vm.Snapshot -and $vm.Snapshot.RootSnapshotList) {
                    $allSnaps = Get-FlatSnapshotList -SnapshotTree $vm.Snapshot.RootSnapshotList
                    foreach ($snap in $allSnaps) {
                        if ($snap.CreateTime -lt $threshold) {
                            if ($PSCmdlet.ShouldProcess($vm.Name, "Remove Snapshot $($snap.Name)")) {
                                Invoke-AnyStackWithRetry -ScriptBlock { $vm.RemoveSnapshot_Task($snap.Snapshot, $false, $true) }
                                
                                [PSCustomObject]@{
                                    PSTypeName   = 'AnyStack.ClearedSnapshot'
                                    Timestamp    = (Get-Date)
                                    Server       = $vi.Name
                                    VmName       = $vm.Name
                                    SnapshotName = $snap.Name
                                    SnapshotAge  = [int]((Get-Date) - $snap.CreateTime).TotalDays
                                    SizeGB       = 0
                                    Removed      = $true
                                }
                            }
                        }
                    }
                }
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new(function Get-FlatSnapshotList {
    param([object[]]$SnapshotTree)
    foreach ($snap in $SnapshotTree) {
        $snap
        if ($snap.ChildSnapshotList) {
            Get-FlatSnapshotList -SnapshotTree $snap.ChildSnapshotList
        }
    }
}

function Clear-AnyStackOrphanedSnapshots {
    <#
    .SYNOPSIS
        Removes old VM snapshots.
    .DESCRIPTION
        Deletes snapshots older than AgeDays.
    .PARAMETER Server
        vCenter Server hostname or VIServer object. Uses active connection if omitted.
    .PARAMETER AgeDays
        Age in days (default 7).
    .PARAMETER VmName
        Filter by VM.
    .PARAMETER ClusterName
        Filter by cluster.
    .EXAMPLE
        PS> Clear-AnyStackOrphanedSnapshots
    .OUTPUTS
        PSCustomObject
    .NOTES
        Author: The AnyStack Architect
        Requires: VCF.PowerCLI 9.0+, vSphere 8.0 U3+
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory=$false, ValueFromPipeline=$true)]
        [ValidateNotNull()]
        $Server,
        [Parameter(Mandatory=$false)]
        [int]$AgeDays = 7,
        [Parameter(Mandatory=$false)]
        [string]$VmName,
        [Parameter(Mandatory=$false)]
        [string]$ClusterName
    )
    begin {
        $vi = Get-AnyStackConnection -Server $Server
        $ErrorActionPreference = 'Stop'
    }
    process {
        try {
            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Clearing snapshots on $($vi.Name)"
            $filter = if ($VmName) { @{Name="*$VmName*"} } else { $null }
            $vms = Invoke-AnyStackWithRetry -ScriptBlock { Get-View -Server $vi -ViewType VirtualMachine -Filter $filter -Property Name,Snapshot }
            
            $threshold = (Get-Date).AddDays(-$AgeDays)
            foreach ($vm in $vms) {
                if ($vm.Snapshot -and $vm.Snapshot.RootSnapshotList) {
                    $allSnaps = Get-FlatSnapshotList -SnapshotTree $vm.Snapshot.RootSnapshotList
                    foreach ($snap in $allSnaps) {
                        if ($snap.CreateTime -lt $threshold) {
                            if ($PSCmdlet.ShouldProcess($vm.Name, "Remove Snapshot $($snap.Name)")) {
                                Invoke-AnyStackWithRetry -ScriptBlock { $vm.RemoveSnapshot_Task($snap.Snapshot, $false, $true) }
                                
                                [PSCustomObject]@{
                                    PSTypeName   = 'AnyStack.ClearedSnapshot'
                                    Timestamp    = (Get-Date)
                                    Server       = $vi.Name
                                    VmName       = $vm.Name
                                    SnapshotName = $snap.Name
                                    SnapshotAge  = [int]((Get-Date) - $snap.CreateTime).TotalDays
                                    SizeGB       = 0
                                    Removed      = $true
                                }
                            }
                        }
                    }
                }
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new($_, 'UnexpectedError', [System.Management.Automation.ErrorCategory]::NotSpecified, $null))
        }
    }
}

 



.Exception, 'UnexpectedError', [System.Management.Automation.ErrorCategory]::NotSpecified, $null))
        }
    }
}