Public/Backup/Restore-VergeVMFromCloudSnapshot.ps1

function Restore-VergeVMFromCloudSnapshot {
    <#
    .SYNOPSIS
        Restores a VM from a cloud (system) snapshot in VergeOS.

    .DESCRIPTION
        Restore-VergeVMFromCloudSnapshot recovers a VM from a cloud snapshot.
        This creates a new VM from the snapshot data, it does not overwrite
        any existing VM.

        Use Get-VergeCloudSnapshot -IncludeVMs to see available VMs in a snapshot.

    .PARAMETER CloudSnapshot
        A cloud snapshot object from Get-VergeCloudSnapshot. Accepts pipeline input.

    .PARAMETER CloudSnapshotKey
        The key (ID) of the cloud snapshot containing the VM.

    .PARAMETER CloudSnapshotName
        The name of the cloud snapshot containing the VM.

    .PARAMETER VMName
        The name of the VM within the cloud snapshot to restore.

    .PARAMETER VMKey
        The key of the VM within the cloud snapshot (from cloud_snapshot_vms).

    .PARAMETER NewName
        Optional new name for the restored VM. If not specified, the original name
        is used (which may conflict with an existing VM).

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Restore-VergeVMFromCloudSnapshot -CloudSnapshotName "Daily_20260123" -VMName "WebServer01"

        Restores the VM "WebServer01" from the specified cloud snapshot.

    .EXAMPLE
        Get-VergeCloudSnapshot -Name "Daily*" | Restore-VergeVMFromCloudSnapshot -VMName "DBServer" -NewName "DBServer-Restored"

        Restores the VM with a new name from the first matching cloud snapshot.

    .EXAMPLE
        $snap = Get-VergeCloudSnapshot -Key 5 -IncludeVMs
        $snap.VMs | ForEach-Object { Restore-VergeVMFromCloudSnapshot -CloudSnapshotKey 5 -VMKey $_.Key }

        Restores all VMs from a specific cloud snapshot.

    .OUTPUTS
        PSCustomObject with recovery status information.

    .NOTES
        The recovered VM will be created as a new VM. If a VM with the same name
        already exists, you should specify -NewName to avoid conflicts.

        This operation may take time depending on VM size.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium', DefaultParameterSetName = 'BySnapshotName')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByObject')]
        [PSTypeName('Verge.CloudSnapshot')]
        [PSCustomObject]$CloudSnapshot,

        [Parameter(Mandatory, ParameterSetName = 'BySnapshotKey')]
        [int]$CloudSnapshotKey,

        [Parameter(Mandatory, ParameterSetName = 'BySnapshotName')]
        [string]$CloudSnapshotName,

        [Parameter(ParameterSetName = 'ByObject')]
        [Parameter(ParameterSetName = 'BySnapshotKey')]
        [Parameter(ParameterSetName = 'BySnapshotName')]
        [string]$VMName,

        [Parameter()]
        [int]$VMKey,

        [Parameter()]
        [string]$NewName,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }
    }

    process {
        # Resolve the cloud snapshot
        $targetSnapshot = switch ($PSCmdlet.ParameterSetName) {
            'ByObject' { $CloudSnapshot }
            'BySnapshotKey' { Get-VergeCloudSnapshot -Key $CloudSnapshotKey -IncludeExpired -Server $Server }
            'BySnapshotName' { Get-VergeCloudSnapshot -Name $CloudSnapshotName -IncludeExpired -Server $Server | Select-Object -First 1 }
        }

        if (-not $targetSnapshot) {
            $identifier = if ($CloudSnapshotKey) { "Key: $CloudSnapshotKey" } else { "Name: $CloudSnapshotName" }
            Write-Error -Message "Cloud snapshot not found ($identifier)" -ErrorId 'CloudSnapshotNotFound'
            return
        }

        $snapshotKey = $targetSnapshot.Key
        $snapshotName = $targetSnapshot.Name

        # Find the VM within the snapshot
        if (-not $VMKey -and -not $VMName) {
            Write-Error -Message "Either -VMName or -VMKey must be specified" -ErrorId 'VMNotSpecified'
            return
        }

        # Query cloud_snapshot_vms to find the VM
        $vmParams = @{
            'fields' = @(
                '$key'
                'name'
                'description'
                'original_key'
                'status'
            ) -join ','
            'filter' = "cloud_snapshot eq $snapshotKey"
        }

        if ($VMKey) {
            $vmParams['filter'] += " and `$key eq $VMKey"
        }
        elseif ($VMName) {
            $vmParams['filter'] += " and name eq '$VMName'"
        }

        try {
            $vmResponse = Invoke-VergeAPI -Method GET -Endpoint 'cloud_snapshot_vms' -Query $vmParams -Connection $Server
            $snapshotVMs = if ($vmResponse -is [array]) { $vmResponse } elseif ($vmResponse) { @($vmResponse) } else { @() }

            if ($snapshotVMs.Count -eq 0) {
                $vmIdentifier = if ($VMKey) { "Key: $VMKey" } else { "Name: $VMName" }
                Write-Error -Message "VM not found in cloud snapshot '$snapshotName' ($vmIdentifier)" -ErrorId 'VMNotFoundInSnapshot'
                return
            }

            foreach ($vm in $snapshotVMs) {
                $vmDisplayName = $vm.name
                $vmSnapshotKey = $vm.'$key'

                $restoreName = if ($NewName) { $NewName } else { $vmDisplayName }

                if ($PSCmdlet.ShouldProcess("VM '$vmDisplayName' from Cloud Snapshot '$snapshotName'", 'Restore')) {
                    Write-Verbose "Restoring VM '$vmDisplayName' from cloud snapshot '$snapshotName' (VM Snapshot Key: $vmSnapshotKey)"

                    # Build the recover action
                    $body = @{
                        action = 'recover'
                        params = @{
                            rows = @($vmSnapshotKey)
                        }
                    }

                    if ($NewName) {
                        $body['params']['name'] = $NewName
                    }

                    try {
                        $response = Invoke-VergeAPI -Method POST -Endpoint 'cloud_snapshot_vm_actions' -Body $body -Connection $Server

                        # Create output object
                        $output = [PSCustomObject]@{
                            PSTypeName          = 'Verge.CloudSnapshotVMRestore'
                            CloudSnapshotKey    = $snapshotKey
                            CloudSnapshotName   = $snapshotName
                            VMSnapshotKey       = $vmSnapshotKey
                            VMName              = $vmDisplayName
                            RestoredAs          = $restoreName
                            Status              = 'Initiated'
                            Response            = $response
                        }

                        if ($response -and $response.task) {
                            $output | Add-Member -MemberType NoteProperty -Name 'TaskKey' -Value $response.task -Force
                        }

                        Write-Output $output
                        Write-Verbose "VM restore initiated for '$vmDisplayName'"
                    }
                    catch {
                        Write-Error -Message "Failed to restore VM '$vmDisplayName' from cloud snapshot: $($_.Exception.Message)" -ErrorId 'RestoreVMFailed'
                    }
                }
            }
        }
        catch {
            Write-Error -Message "Failed to query VMs in cloud snapshot '$snapshotName': $($_.Exception.Message)" -ErrorId 'QuerySnapshotVMsFailed'
        }
    }
}