Private/Switch-VmPowerSubscriptionContext.ps1

function Switch-VmPowerSubscriptionContext {
    <#
    .SYNOPSIS
    Point the Az context at the subscription a machine lives in, before acting on it.

    .DESCRIPTION
    Discovery and execution do not have the same reach, and that asymmetry is easy to miss. One
    Resource Graph query answers for every subscription the identity can read. Stop-AzVM and
    Start-AzVM have no -SubscriptionId at all: they act in whatever subscription the current context
    names, and Connect-AzAccount -Identity picks the first one it sees.

    Two things go wrong without this. A plan spanning three subscriptions fails on two of them with
    a resource-not-found that names a machine the report just listed. And where a resource group
    name and a machine name are reused across subscriptions - dev/test estates do this constantly -
    the call resolves against the wrong subscription and deallocates the wrong machine.

    The context is changed only when it has to be, so a single-subscription estate never pays for a
    switch it does not need. Scope is Process: an Automation sandbox is thrown away after the job
    and has no profile worth writing to disk.

    .PARAMETER SubscriptionId
    Subscription the machine lives in. An empty value leaves the context alone, which is what a
    plan built from a fixture rather than from Resource Graph produces.
    #>

    [CmdletBinding()]
    [OutputType([void])]
    param(
        [Parameter()]
        [AllowEmptyString()]
        [string]$SubscriptionId
    )

    if ([string]::IsNullOrWhiteSpace($SubscriptionId)) { return }

    $context = Get-AzContext
    if ($context -and $context.Subscription -and $context.Subscription.Id -eq $SubscriptionId) { return }

    try {
        $null = Set-AzContext -Subscription $SubscriptionId -Scope Process -Confirm:$false -ErrorAction Stop -WarningAction SilentlyContinue
    }
    catch {
        # Reported against the machine rather than thrown out of the run: one unreachable
        # subscription is not a reason to abandon the ones that are reachable.
        throw ("Could not switch to subscription $SubscriptionId, so this machine was not touched. " +
            'Resource Graph found it with read access alone; acting on it needs the operator role ' +
            "in that subscription as well. $($_.Exception.Message)")
    }

    Write-Verbose "Context is now subscription $SubscriptionId."
}