Public/Restore-CloudPC.ps1

function Restore-CloudPC {
    <#
    .SYNOPSIS
        Restores a Windows 365 Cloud PC from a restore point snapshot.

    .DESCRIPTION
        Calls Microsoft Graph v1.0
        https://graph.microsoft.com/v1.0/deviceManagement/virtualEndpoint/cloudPCs/{id}/restore
        to restore a Cloud PC from a snapshot ID.

        This is a destructive asynchronous service action. Graph returns
        204 No Content when the restore request is accepted. Use -WhatIf to
        preview the request before restoring a device.

        Requires the CloudPC.ReadWrite.All scope; the cmdlet automatically
        reauthenticates via Connect-CloudPC if the current Graph session does
        not already have it.

    .PARAMETER CloudPC
        A WindowsCloudPC.CloudPC object returned by Get-CloudPC, or an exact
        Cloud PC name, Cloud PC ID, managed device ID, Azure AD device ID, or
        assigned user principal name. Accepts pipeline input.

    .PARAMETER Id
        The Cloud PC ID when you do not have a CloudPC object available.

    .PARAMETER Snapshot
        A WindowsCloudPC.Snapshot object returned by Get-CloudPCSnapshot.
        The CloudPcId and SnapshotId properties are used for the restore request.

    .PARAMETER SnapshotId
        The snapshot ID to restore from when the Cloud PC is supplied separately.

    .PARAMETER Force
        Suppress confirmation prompts. Equivalent to -Confirm:$false.

    .PARAMETER PassThru
        Emit a WindowsCloudPC.RestoreResult object describing the request.

    .EXAMPLE
        Get-CloudPCSnapshot -CloudPC 'CPC-USER-01' |
            Select-Object -First 1 |
            Restore-CloudPC -WhatIf

    .EXAMPLE
        Restore-CloudPC -CloudPC 'CPC-USER-01' -SnapshotId '<snapshot-id>' -Force -PassThru

    .EXAMPLE
        Restore-CloudPC -Id '<cloud-pc-id>' -SnapshotId '<snapshot-id>' -PassThru
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High', DefaultParameterSetName = 'ByObject')]
    [OutputType('WindowsCloudPC.RestoreResult')]
    param(
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ByObject')]
        [object]$CloudPC,

        [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'ById')]
        [Alias('CloudPcId')]
        [string]$Id,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'BySnapshot')]
        [object]$Snapshot,

        [Parameter(Mandatory, ParameterSetName = 'ByObject')]
        [Parameter(Mandatory, ParameterSetName = 'ById')]
        [ValidateNotNullOrEmpty()]
        [string]$SnapshotId,

        [switch]$Force,

        [switch]$PassThru
    )

    begin {
        if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
            $ConfirmPreference = 'None'
        }
        Connect-CloudPC -AdditionalScopes 'CloudPC.ReadWrite.All' | Out-Null
    }

    process {
        if ($PSCmdlet.ParameterSetName -eq 'BySnapshot') {
            if (-not $Snapshot.CloudPcId -or -not $Snapshot.SnapshotId) {
                Write-Error "Restore-CloudPC: Snapshot input must include CloudPcId and SnapshotId properties."
                return
            }

            $targetPc = [pscustomobject]@{
                PSTypeName = 'WindowsCloudPC.CloudPCTarget'
                Id         = $Snapshot.CloudPcId
                Name       = if ($Snapshot.CloudPcName) { $Snapshot.CloudPcName } else { $Snapshot.CloudPcId }
            }
            $snapshotIdToRestore = $Snapshot.SnapshotId
        }
        else {
            try {
                $targetPc = if ($PSCmdlet.ParameterSetName -eq 'ById') {
                    Resolve-CloudPCTarget -Id $Id -CommandName 'Restore-CloudPC'
                }
                else {
                    Resolve-CloudPCTarget -CloudPC $CloudPC -CommandName 'Restore-CloudPC'
                }
            }
            catch {
                Write-Error -ErrorRecord $_
                return
            }

            $snapshotIdToRestore = $SnapshotId
        }

        if ([string]::IsNullOrWhiteSpace($snapshotIdToRestore)) {
            Write-Error "Restore-CloudPC: SnapshotId is empty."
            return
        }

        $target = "Cloud PC '$($targetPc.Name)' ($($targetPc.Id))"
        $status = 'Accepted'
        $errorMessage = $null
        $requestedAt = [datetime]::Now

        if (-not $PSCmdlet.ShouldProcess($target, "Restore from snapshot '$snapshotIdToRestore'")) {
            if ($PassThru) {
                [pscustomobject]@{
                    PSTypeName   = 'WindowsCloudPC.RestoreResult'
                    CloudPcId    = $targetPc.Id
                    CloudPcName  = $targetPc.Name
                    SnapshotId   = $snapshotIdToRestore
                    Status       = 'WhatIf'
                    RequestedAt  = $null
                    ErrorMessage = $null
                }
            }
            return
        }

        $escapedCloudPcId = [uri]::EscapeDataString($targetPc.Id)
        $uri = "https://graph.microsoft.com/v1.0/deviceManagement/virtualEndpoint/cloudPCs/$escapedCloudPcId/restore"
        $body = @{ cloudPcSnapshotId = $snapshotIdToRestore } | ConvertTo-Json -Depth 4

        try {
            Invoke-MgGraphRequest -Method POST -Uri $uri -Body $body -ContentType 'application/json' | Out-Null
            Write-Verbose "Restore-CloudPC: restore accepted for $target"
        }
        catch {
            $status = 'Failed'
            $errorMessage = $_.Exception.Message
            Write-Error -Message "Restore-CloudPC: restore failed for $target -- $errorMessage" -Exception $_.Exception
        }

        if ($PassThru) {
            [pscustomobject]@{
                PSTypeName   = 'WindowsCloudPC.RestoreResult'
                CloudPcId    = $targetPc.Id
                CloudPcName  = $targetPc.Name
                SnapshotId   = $snapshotIdToRestore
                Status       = $status
                RequestedAt  = $requestedAt
                ErrorMessage = $errorMessage
            }
        }
    }
}