Public/Set-CPCCrossRegionDisasterRecovery.ps1

function Set-CPCCrossRegionDisasterRecovery {
    <#
    .SYNOPSIS
    Configures cross-region disaster recovery settings on a Cloud PC User Settings Policy
    .DESCRIPTION
    The function configures cross-region disaster recovery settings on an existing Cloud PC
    User Settings Policy. Disaster recovery ensures Cloud PCs can be provisioned in an
    alternate Azure region when the primary region is unavailable.

    Supported disaster recovery types:
    - notConfigured : Disaster recovery is disabled (default)
    - crossRegion : Cross-region DR — Windows 365 provisions a standby Cloud PC in the
                      specified backup region automatically
    - premium : Premium DR — adds user-initiated failover capability on top of
                      cross-region standby

    Only the crossRegionDisasterRecoverySetting of the policy is changed, and within it only
    what you pass: -MaintainCrossRegionRestorePointEnabled and -UserInitiatedDisasterRecoveryAllowed
    keep their current value when they are not passed. Graph replaces the
    whole policy on an update, so the cmdlet reads the current policy and sends all other
    settings (local admin, reset, restore point settings and so on) back unchanged. Changes
    someone else makes to the policy between that read and the update are overwritten. The
    deprecated restorePointSetting frequencyInHours and the retired selfServiceEnabled and
    notificationSetting are never sent.

    crossRegionDisasterRecoveryEnabled is deprecated in the Graph docs, but Graph still rejects
    a crossRegion setting without it, so it is sent as $true for crossRegion and premium and as
    $false for notConfigured.

    This function requires the Graph API beta endpoint and the CloudPC.ReadWrite.All permission.
    .PARAMETER Name
    The exact display name of the Cloud PC User Settings Policy to update. The name must match
    exactly one policy.
    .PARAMETER DisasterRecoveryType
    The disaster recovery mode to configure. Valid values: notConfigured, crossRegion, premium.
    .PARAMETER MaintainCrossRegionRestorePointEnabled
    When $true, Windows 365 continuously maintains cross-region restore points that can be
    used during failover. When $false, only the initial provisioning image is available
    after a disaster, which may result in data loss. When not passed, the current value of
    the policy is kept (Graph's default for a new setting is $false).
    .PARAMETER UserInitiatedDisasterRecoveryAllowed
    When $true, end users can activate disaster recovery themselves from the Windows 365
    portal. Only applicable when DisasterRecoveryType is 'premium'; for the other types it is
    always sent as $false. When not passed with premium, the current value of the policy is
    kept (Graph's default for a new setting is $false).
    .PARAMETER RegionName
    The Azure region name to use as the disaster recovery target (e.g. 'westus',
    'northeurope', 'eastasia'). Required when DisasterRecoveryType is 'crossRegion' or
    'premium'.
    .PARAMETER RegionGroup
    The cloudPcRegionGroup that the disaster recovery region belongs to (e.g. 'usEast',
    'usWest', 'europeUnion', 'unitedKingdom', 'asia'). Required when DisasterRecoveryType is
    'crossRegion' or 'premium'. The value is passed to Graph as is, so region groups that
    Microsoft adds later work without a module update; Graph rejects an unknown value.
    See https://learn.microsoft.com/graph/api/resources/cloudpcregiongroup?view=graph-rest-beta
    .EXAMPLE
    Set-CPCCrossRegionDisasterRecovery -Name "UserSettings01" -DisasterRecoveryType crossRegion -RegionName "westus" -RegionGroup "usWest" -MaintainCrossRegionRestorePointEnabled $true
    .EXAMPLE
    Set-CPCCrossRegionDisasterRecovery -Name "UserSettings01" -DisasterRecoveryType premium -RegionName "northeurope" -RegionGroup "europeUnion" -MaintainCrossRegionRestorePointEnabled $true -UserInitiatedDisasterRecoveryAllowed $true
    .EXAMPLE
    Set-CPCCrossRegionDisasterRecovery -Name "UserSettings01" -DisasterRecoveryType notConfigured
    .NOTES
    Required permission: CloudPC.ReadWrite.All
    https://learn.microsoft.com/graph/api/cloudpcusersetting-update?view=graph-rest-beta
    https://learn.microsoft.com/graph/api/cloudpcusersetting-get?view=graph-rest-beta
    https://learn.microsoft.com/graph/api/resources/cloudpccrossregiondisasterrecoverysetting?view=graph-rest-beta
    https://learn.microsoft.com/graph/api/resources/cloudpcregiongroup?view=graph-rest-beta
    https://learn.microsoft.com/graph/api/resources/cloudpcrestorepointsetting?view=graph-rest-beta
    #>


    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [ValidateSet('notConfigured', 'crossRegion', 'premium')]
        [string]$DisasterRecoveryType,

        [Parameter(Mandatory = $false)]
        [bool]$MaintainCrossRegionRestorePointEnabled,

        [Parameter(Mandatory = $false)]
        [bool]$UserInitiatedDisasterRecoveryAllowed,

        [Parameter(Mandatory = $false)]
        [string]$RegionName,

        [Parameter(Mandatory = $false)]
        [string]$RegionGroup
    )

    begin {
        Get-TokenValidity

        If ($DisasterRecoveryType -in @('crossRegion', 'premium')) {
            If ([string]::IsNullOrWhiteSpace($RegionName) -or [string]::IsNullOrWhiteSpace($RegionGroup)) {
                Stop-CPCCommand -Cmdlet $PSCmdlet -Message "RegionName and RegionGroup are required when DisasterRecoveryType is '$DisasterRecoveryType'"
            }
        }

        If ($UserInitiatedDisasterRecoveryAllowed -and $DisasterRecoveryType -ne 'premium') {
            Write-Warning "UserInitiatedDisasterRecoveryAllowed is only supported for premium disaster recovery and is sent as false"
        }

        Write-Verbose "Looking up User Settings Policy: $Name"
        $Policy = @(Get-CPCUserSettingsPolicy -Name $Name | Where-Object { $_.displayName -eq $Name })

        If ($Policy.Count -eq 0) {
            Stop-CPCCommand -Cmdlet $PSCmdlet -Message "No User Settings Policy found with name '$Name'" -Category ObjectNotFound
        }

        If ($Policy.Count -gt 1) {
            Stop-CPCCommand -Cmdlet $PSCmdlet -Message "$($Policy.Count) User Settings Policies found with name '$Name' ($($Policy.id -join ', ')). The name must match exactly one policy"
        }

        $Policy = $Policy[0]

        $url = "https://graph.microsoft.com/$script:MSGraphVersion/deviceManagement/virtualEndpoint/userSettings/$($Policy.id)"
        Write-Verbose "PATCH URL: $url"
    }

    Process {
        # PATCH replaces the whole policy and resets every setting that is left out,
        # so read the current settings and change only what was passed
        try {
            $body = Get-CPCUserSettingBody -Id $Policy.id -ErrorAction Stop
        }
        catch {
            Stop-CPCCommand -Cmdlet $PSCmdlet -Message (Get-GraphErrorMessage $_) -Category InvalidOperation
        }

        # Start from the current disaster recovery setting, so options that weren't passed keep their value
        $drSetting = @{}
        If ($body["crossRegionDisasterRecoverySetting"] -is [hashtable]) {
            $drSetting = $body["crossRegionDisasterRecoverySetting"]
        }

        $drSetting["disasterRecoveryType"] = $DisasterRecoveryType
        # crossRegionDisasterRecoveryEnabled is deprecated, but Graph still rejects crossRegion unless it is true
        $drSetting["crossRegionDisasterRecoveryEnabled"] = ($DisasterRecoveryType -ne "notConfigured")

        If ($PSBoundParameters.ContainsKey('MaintainCrossRegionRestorePointEnabled')) {
            $drSetting["maintainCrossRegionRestorePointEnabled"] = $MaintainCrossRegionRestorePointEnabled
        }

        # User-initiated failover is only supported for premium disaster recovery
        If ($DisasterRecoveryType -ne 'premium') {
            $drSetting["userInitiatedDisasterRecoveryAllowed"] = $false
        }
        ElseIf ($PSBoundParameters.ContainsKey('UserInitiatedDisasterRecoveryAllowed')) {
            $drSetting["userInitiatedDisasterRecoveryAllowed"] = $UserInitiatedDisasterRecoveryAllowed
        }

        If ($DisasterRecoveryType -in @('crossRegion', 'premium')) {
            # Abstract Graph type: name the Microsoft-hosted subtype that regionName/regionGroup belong to
            $drSetting["disasterRecoveryNetworkSetting"] = @{
                '@odata.type' = '#microsoft.graph.cloudPcDisasterRecoveryMicrosoftHostedNetworkSetting'
                regionName    = $RegionName
                regionGroup   = $RegionGroup
            }
        }
        Else {
            # No network setting with notConfigured, as before
            $drSetting.Remove("disasterRecoveryNetworkSetting")
        }

        $body["crossRegionDisasterRecoverySetting"] = $drSetting

        $bodyJson = $body | ConvertTo-Json -Depth 10
        Write-Verbose "Request Body: $bodyJson"

        If ($PSCmdlet.ShouldProcess($Name, "Set Cross-Region Disaster Recovery to $DisasterRecoveryType")) {
            try {
                # Graph returns 204 No Content; nothing is written to the output
                $null = Invoke-RestMethod -Uri $url -Headers $script:Authheader -Method PATCH -Body $bodyJson -ContentType "application/json" -ErrorAction Stop
            }
            catch {
                Throw (Get-GraphErrorMessage $_)
            }
        }
    }
}