Public/New-CloudPCSnapshot.ps1

function New-CloudPCSnapshot {
    <#
    .SYNOPSIS
        Creates restore point snapshots for one or more Windows 365 Cloud PCs.

    .DESCRIPTION
        Calls the Microsoft Graph beta /deviceManagement/virtualEndpoint/cloudPCs/{id}/createSnapshot
        action. Graph returns 204 No Content when the asynchronous snapshot request is accepted.

        Targets can be a single Cloud PC object, a Cloud PC ID, a friendly Cloud PC name,
        all Cloud PCs in the tenant, all Cloud PCs assigned to a user, or all Cloud PCs
        associated with a provisioning policy.

        The cmdlet emits one WindowsCloudPC.SnapshotRequestResult row per target so batch
        runs show exactly which Cloud PCs were invoked.

    .PARAMETER CloudPC
        A WindowsCloudPC.CloudPC object returned by Get-CloudPC, or a Cloud PC friendly name.
        Accepts pipeline input.

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

    .PARAMETER All
        Creates snapshots for every Cloud PC returned by Get-CloudPC.

    .PARAMETER User
        Creates snapshots for Cloud PCs returned by Get-CloudPC -UserPrincipalName.

    .PARAMETER ProvisioningPolicyId
        Creates snapshots for Cloud PCs associated with a provisioning policy.

    .PARAMETER ExcludeCloudPC
        Cloud PCs to skip. Match values against Cloud PC Id, Name, ManagedDeviceId,
        AadDeviceId, or AssignedUserUpn.

    .PARAMETER StorageAccountId
        Optional storage account ID that receives the restore point.

    .PARAMETER AccessTier
        Optional blob access tier: hot, cool, cold, archive, or unknownFutureValue.

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

    .EXAMPLE
        New-CloudPCSnapshot -CloudPC 'CFD-Vance-XS4KT' -Force

        Creates a snapshot for one Cloud PC by friendly name.

    .EXAMPLE
        New-CloudPCSnapshot -User 'user@contoso.com' -Force

        Creates snapshots for every Cloud PC assigned to the user.

    .EXAMPLE
        New-CloudPCSnapshot -ProvisioningPolicyId '<policy-id>' -ExcludeCloudPC 'CPC-KEEP-01','user2@contoso.com' -Force

        Creates snapshots for every Cloud PC in the provisioning policy except the excluded targets.

    .EXAMPLE
        New-CloudPCSnapshot -All -WhatIf

        Shows every Cloud PC that would receive a snapshot without sending requests.
    #>

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

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

        [Parameter(Mandatory, ParameterSetName = 'All')]
        [switch]$All,

        [Parameter(Mandatory, ParameterSetName = 'ByUser')]
        [Alias('UserPrincipalName','UPN')]
        [string]$User,

        [Parameter(Mandatory, ParameterSetName = 'ByPolicy')]
        [Alias('PolicyId')]
        [string]$ProvisioningPolicyId,

        [Parameter(ParameterSetName = 'All')]
        [Parameter(ParameterSetName = 'ByUser')]
        [Parameter(ParameterSetName = 'ByPolicy')]
        [Alias('Exclude','ExcludeId','ExcludeName')]
        [string[]]$ExcludeCloudPC = @(),

        [string]$StorageAccountId,

        [ValidateSet('hot','cool','cold','archive','unknownFutureValue')]
        [string]$AccessTier,

        [switch]$Force
    )

    begin {
        if ($Force -and -not $PSBoundParameters.ContainsKey('Confirm')) {
            $ConfirmPreference = 'None'
        }

        Connect-CloudPC -AdditionalScopes 'CloudPC.ReadWrite.All' | Out-Null

        $excludeSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
        foreach ($item in @($ExcludeCloudPC)) {
            if (-not [string]::IsNullOrWhiteSpace($item)) {
                $null = $excludeSet.Add($item.Trim())
            }
        }
    }

    process {
        $targets = @()
        $policyDisplayName = $null

        switch ($PSCmdlet.ParameterSetName) {
            'All' {
                Write-Verbose "Retrieving all Cloud PCs."
                $targets = @(Get-CloudPC)
                Write-Verbose "Found $($targets.Count) Cloud PC(s)."
            }
            'ByUser' {
                Write-Verbose "Retrieving Cloud PCs for user '$User'."
                $targets = @(Get-CloudPC -UserPrincipalName $User)
                Write-Verbose "Found $($targets.Count) Cloud PC(s) for user '$User'."
            }
            'ByPolicy' {
                Write-Verbose "Retrieving Cloud PCs for provisioning policy '$ProvisioningPolicyId'."
                $policy = Get-CloudPCByProvisioningPolicy -ProvisioningPolicyId $ProvisioningPolicyId
                if (-not $policy) {
                    Write-Error "New-CloudPCSnapshot: provisioning policy '$ProvisioningPolicyId' was not found."
                    return
                }
                $targets = @($policy.CloudPCs)
                $policyDisplayName = $policy.DisplayName
                Write-Verbose "Found $($targets.Count) Cloud PC(s) in provisioning policy '$($policy.DisplayName)' ($ProvisioningPolicyId)."
            }
            'ByObject' {
                if ($CloudPC -is [string]) {
                    Write-Verbose "Resolving Cloud PC name '$CloudPC'."
                    $matches = @(Get-CloudPC | Where-Object {
                        $_.Name -eq $CloudPC -or
                        $_.Id -eq $CloudPC -or
                        $_.ManagedDeviceId -eq $CloudPC -or
                        $_.AadDeviceId -eq $CloudPC -or
                        $_.AssignedUserUpn -eq $CloudPC
                    })

                    if ($matches.Count -eq 0) {
                        Write-Error "New-CloudPCSnapshot: Cloud PC '$CloudPC' was not found. Pipe from Get-CloudPC, use -Id, -User, -ProvisioningPolicyId, or -All."
                        return
                    }

                    if ($matches.Count -gt 1) {
                        Write-Error "New-CloudPCSnapshot: Cloud PC '$CloudPC' matched $($matches.Count) Cloud PCs. Pipe the exact object from Get-CloudPC or use -Id."
                        return
                    }

                    $targets = @($matches[0])
                }
                else {
                    $targets = @($CloudPC)
                }
            }
            'ById' {
                $targets = @(
                    [pscustomobject]@{
                        PSTypeName       = 'WindowsCloudPC.CloudPC'
                        Id               = $Id
                        Name             = $Id
                        ManagedDeviceId  = $null
                        AadDeviceId      = $null
                        AssignedUserUpn  = $null
                    }
                )
            }
        }

        if ($targets.Count -eq 0) {
            Write-Error "New-CloudPCSnapshot: no Cloud PCs matched the requested target."
            return
        }

        foreach ($pc in $targets) {
            $cloudPcId = $pc.Id
            $cloudPcName = if ($pc.Name) { $pc.Name } else { $pc.Id }
            $assignedUserUpn = $pc.AssignedUserUpn
            $resultPolicyId = if ($PSCmdlet.ParameterSetName -eq 'ByPolicy') { $ProvisioningPolicyId } else { $pc.ProvisioningPolicyId }
            $resultPolicyName = if ($PSCmdlet.ParameterSetName -eq 'ByPolicy') { $policyDisplayName } else { $pc.ProvisioningPolicyName }

            if (-not $cloudPcId) {
                Write-Error "New-CloudPCSnapshot: Cloud PC Id is empty; nothing to invoke."
                continue
            }

            $matchValues = @(
                $pc.Id
                $pc.Name
                $pc.ManagedDeviceId
                $pc.AadDeviceId
                $pc.AssignedUserUpn
            ) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }

            $excludedBy = $matchValues | Where-Object { $excludeSet.Contains($_) } | Select-Object -First 1
            if ($excludedBy) {
                [pscustomobject]@{
                    PSTypeName       = 'WindowsCloudPC.SnapshotRequestResult'
                    CloudPcId        = $cloudPcId
                    CloudPcName      = $cloudPcName
                    AssignedUserUpn  = $assignedUserUpn
                    ProvisioningPolicyId   = $resultPolicyId
                    ProvisioningPolicyName = $resultPolicyName
                    Excluded         = $true
                    ExcludedBy       = $excludedBy
                    Status           = 'Excluded'
                    RequestedAt      = $null
                    StorageAccountId = if ($PSBoundParameters.ContainsKey('StorageAccountId')) { $StorageAccountId } else { $null }
                    AccessTier       = if ($PSBoundParameters.ContainsKey('AccessTier')) { $AccessTier } else { $null }
                    ErrorMessage     = $null
                }
                continue
            }

            $target = "Cloud PC '$cloudPcName' ($cloudPcId)"
            if (-not $PSCmdlet.ShouldProcess($target, 'Create snapshot')) {
                [pscustomobject]@{
                    PSTypeName       = 'WindowsCloudPC.SnapshotRequestResult'
                    CloudPcId        = $cloudPcId
                    CloudPcName      = $cloudPcName
                    AssignedUserUpn  = $assignedUserUpn
                    ProvisioningPolicyId   = $resultPolicyId
                    ProvisioningPolicyName = $resultPolicyName
                    Excluded         = $false
                    ExcludedBy       = $null
                    Status           = 'WhatIf'
                    RequestedAt      = $null
                    StorageAccountId = if ($PSBoundParameters.ContainsKey('StorageAccountId')) { $StorageAccountId } else { $null }
                    AccessTier       = if ($PSBoundParameters.ContainsKey('AccessTier')) { $AccessTier } else { $null }
                    ErrorMessage     = $null
                }
                continue
            }

            $body = [ordered]@{}
            if ($PSBoundParameters.ContainsKey('StorageAccountId')) {
                $body.storageAccountId = $StorageAccountId
            }
            if ($PSBoundParameters.ContainsKey('AccessTier')) {
                $body.accessTier = $AccessTier
            }

            $escapedCloudPcId = [uri]::EscapeDataString($cloudPcId)
            $uri = "https://graph.microsoft.com/beta/deviceManagement/virtualEndpoint/cloudPCs/$escapedCloudPcId/createSnapshot"
            $status = 'Accepted'
            $errorMessage = $null

            try {
                Write-Verbose "Creating snapshot for Cloud PC '$cloudPcName' ($cloudPcId)."
                Invoke-MgGraphRequest -Method POST -Uri $uri -ContentType 'application/json' -Body ($body | ConvertTo-Json -Depth 3 -Compress) | Out-Null
            }
            catch {
                $status = 'Failed'
                $errorMessage = $_.Exception.Message
                Write-Error -Message "New-CloudPCSnapshot: create snapshot failed for $target -- $errorMessage" -Exception $_.Exception
            }

            [pscustomobject]@{
                PSTypeName       = 'WindowsCloudPC.SnapshotRequestResult'
                CloudPcId        = $cloudPcId
                CloudPcName      = $cloudPcName
                AssignedUserUpn  = $assignedUserUpn
                ProvisioningPolicyId   = $resultPolicyId
                ProvisioningPolicyName = $resultPolicyName
                Excluded         = $false
                ExcludedBy       = $null
                Status           = $status
                RequestedAt      = [datetime]::Now
                StorageAccountId = if ($PSBoundParameters.ContainsKey('StorageAccountId')) { $StorageAccountId } else { $null }
                AccessTier       = if ($PSBoundParameters.ContainsKey('AccessTier')) { $AccessTier } else { $null }
                ErrorMessage     = $errorMessage
            }
        }
    }

    end { }
}