Workoho.Automation.Azure/Public/Connect-Auto_AzAccount.ps1

<#
.SYNOPSIS
    Connects to Azure using either a Managed Service Identity or an interactive session.

.DESCRIPTION
    This runbook connects to Azure using either a Managed Service Identity or an interactive session, depending on the execution environment.

    The script also retrieves the following information about the current Azure Automation Account and sets them as environment variables:
    - AZURE_AUTOMATION_AccountId
    - AZURE_AUTOMATION_SubscriptionId
    - AZURE_AUTOMATION_ResourceGroupName
    - AZURE_AUTOMATION_AccountName
    - AZURE_AUTOMATION_IDENTITY_PrincipalId
    - AZURE_AUTOMATION_IDENTITY_TenantId
    - AZURE_AUTOMATION_IDENTITY_Type
    - AZURE_AUTOMATION_RUNBOOK_Name
    - AZURE_AUTOMATION_RUNBOOK_CreationTime
    - AZURE_AUTOMATION_RUNBOOK_LastModifiedTime
    - AZURE_AUTOMATION_RUNBOOK_JOB_CreationTime
    - AZURE_AUTOMATION_RUNBOOK_JOB_StartTime

    This information can be used by other runbooks afterwards to retrieve details about the current runbook and job.
    Please note that this information involves connecting to Microsoft Graph.
    However, due to incompatible modules, it is important that this script connects to Azure first before a connection to Microsoft Graph is established.
    Only then the environment variables can be set correctly. This is why the environment variables are set in a separate step using the parameter SetEnvVarsAfterMgConnect.

.PARAMETER Tenant
    Specifies the Azure AD tenant ID to use for authentication. If not provided, the default tenant will be used.

.PARAMETER Subscription
    Specifies the Azure subscription ID to use. If not provided, the default subscription will be used.

.PARAMETER SetEnvVarsAfterMgConnect
    Specifies whether to set environment variables after connecting to Microsoft Graph. Default is $false.

.EXAMPLE
    PS> Connect-Auto_AzAccount -Tenant 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' -Subscription 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
    Connects to Azure using the specified tenant and subscription.

.EXAMPLE
    PS> Connect-Auto_AzAccount
    Connects to Azure using the default tenant and subscription.
#>


function Connect-Auto_AzAccount {
    [CmdletBinding()]
    Param(
        [string]$Tenant,
        [string]$Subscription,
        [bool]$SetEnvVarsAfterMgConnect
    )

    Write-Auto_FunctionBegin $MyInvocation -OnceOnly

    #region [COMMON] ENVIRONMENT ---------------------------------------------------
    Import-Auto_Module @(
        @{ Name = 'Az.Accounts'; MinimumVersion = '3.0.0' }
    )
    #endregion ---------------------------------------------------------------------

    #region [COMMON] FUNCTIONS -----------------------------------------------------
    function Initialize-EnvVarsAfterMgConnect {
        param()

        if ($IsAzureAutomationJob) {
            if (
                $env:AZURE_AUTOMATION_SubscriptionId -and
                $env:AZURE_AUTOMATION_ResourceGroupName -and
                $env:AZURE_AUTOMATION_AccountName -and
                $env:AZURE_AUTOMATION_IDENTITY_PrincipalId -and
                $env:AZURE_AUTOMATION_IDENTITY_TenantId -and
                $env:AZURE_AUTOMATION_IDENTITY_Type -and
                $env:AZURE_AUTOMATION_RUNBOOK_Name -and
                $env:AZURE_AUTOMATION_RUNBOOK_CreationTime -and
                $env:AZURE_AUTOMATION_RUNBOOK_LastModifiedTime -and
                $env:AZURE_AUTOMATION_RUNBOOK_JOB_CreationTime -and
                $env:AZURE_AUTOMATION_RUNBOOK_JOB_StartTime -and
                $env:AZURE_AUTOMATION_RUNBOOK_CreationTime -and
                $env:AZURE_AUTOMATION_RUNBOOK_LastModifiedTime
            ) {
                return
            }

            Write-Verbose '[Connect-Auto_AzAccount]: - Running in Azure Automation - Generating connection environment variables'

            if ([string]::IsNullOrEmpty($env:MG_PRINCIPAL_DISPLAYNAME)) {
                Throw '[Connect-Auto_AzAccount]: - Missing environment variable $env:MG_PRINCIPAL_DISPLAYNAME. Please run Connect-Auto_MgGraph first.'
            }

            #region [COMMON] ENVIRONMENT ---------------------------------------------------
            if ($null -eq (Get-Module -Name Orchestrator.AssetManagement.Cmdlets -ErrorAction SilentlyContinue)) {
                try {
                    $PSModuleAutoloadingPreference = 'All'
                    $null = Get-AutomationVariable -Name DummyVar -ErrorAction SilentlyContinue -WhatIf
                }
                catch {
                    # Do nothing. We just want to trigger auto import of Orchestrator.AssetManagement.Cmdlets
                    Throw '[Connect-Auto_AzAccount]: - Unable to import module Orchestrator.AssetManagement.Cmdlets'
                }
            }

            $apiVersion = '2023-11-01'
            #endregion ---------------------------------------------------------------------

            try {
                $AzAutomationAccount = ((Az.Accounts\Invoke-AzRestMethod -Path "/subscriptions/$((Az.Accounts\Get-AzContext).Subscription.Id)/providers/Microsoft.Automation/automationAccounts?api-version=$apiVersion" -ErrorAction Stop).Content | ConvertFrom-Json).Value | Where-Object { $_.name -eq $env:MG_PRINCIPAL_DISPLAYNAME }
                if ($AzAutomationAccount) {
                    Write-Verbose '[Connect-Auto_AzAccount]: - Retrieved Automation Account details'
                    $null, $null, $subscriptionId, $null, $resourceGroupName, $null, $null, $null, $automationAccountName = $AzAutomationAccount.id -split '/'
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_AccountId', $AzAutomationAccount.id)
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_SubscriptionId', $subscriptionId)
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_ResourceGroupName', $resourceGroupName)
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_AccountName', $automationAccountName)
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_IDENTITY_PrincipalId', $AzAutomationAccount.Identity.PrincipalId)
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_IDENTITY_TenantId', $AzAutomationAccount.Identity.TenantId)
                    [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_IDENTITY_Type', $AzAutomationAccount.Identity.Type)

                    if ($IsAzureAutomationJob) {

                        $AzAutomationJob = (Az.Accounts\Invoke-AzRestMethod -Path "$($AzAutomationAccount.id)/jobs/$($PSPrivateMetadata.JobId)?api-version=$apiVersion" -ErrorAction Stop).Content | ConvertFrom-Json
                        if ($AzAutomationJob) {
                            Write-Verbose '[Connect-Auto_AzAccount]: - Retrieved Automation Job details'
                            [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_RUNBOOK_Name', $AzAutomationJob.properties.runbook.name)
                            [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_RUNBOOK_JOB_CreationTime', [DateTime]::Parse($AzAutomationJob.properties.creationTime).ToUniversalTime())
                            [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_RUNBOOK_JOB_StartTime', [DateTime]::Parse($AzAutomationJob.properties.startTime).ToUniversalTime())

                            $AzAutomationRunbook = (Az.Accounts\Invoke-AzRestMethod -Path "$($AzAutomationAccount.id)/runbooks/$($AzAutomationJob.properties.runbook.name)?api-version=$apiVersion" -ErrorAction Stop).Content | ConvertFrom-Json
                            if ($AzAutomationRunbook) {
                                Write-Verbose '[Connect-Auto_AzAccount]: - Retrieved Automation Runbook details'
                                [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_RUNBOOK_CreationTime', [DateTime]::Parse($AzAutomationRunbook.properties.creationTime).ToUniversalTime())
                                [Environment]::SetEnvironmentVariable('AZURE_AUTOMATION_RUNBOOK_LastModifiedTime', [DateTime]::Parse($AzAutomationRunbook.properties.lastModifiedTime).ToUniversalTime())
                            }
                            else {
                                Throw "[Connect-Auto_AzAccount]: - Unable to find own Automation Runbook details for runbook name '$($AzAutomationJob.properties.runbook.name)'"
                            }
                        }
                        else {
                            Throw "[Connect-Auto_AzAccount]: - Unable to find own Automation Job details for job Id $($PSPrivateMetadata.JobId)"
                        }
                    }
                    else {
                        Throw '[Connect-Auto_AzAccount]: - Missing global variable $PSPrivateMetadata.JobId'
                    }
                }
                else {
                    Throw "[Connect-Auto_AzAccount]: - Unable to find own Automation Account details for '$env:MG_PRINCIPAL_DISPLAYNAME'"
                }
            }
            catch {
                Throw "Error setting Azure Automation environment variables: $($_.Exception.Message)"
            }
        }
        else {
            Write-Verbose '[Connect-Auto_AzAccount]: - Not running in Azure Automation - no connection environment variables set.'
        }
    }
    #endregion ---------------------------------------------------------------------

    if (Az.Accounts\Get-AzContext) {
        if ($SetEnvVarsAfterMgConnect -eq $true) {
            try {
                Initialize-EnvVarsAfterMgConnect
            }
            catch {
                Az.Accounts\Disconnect-AzAccount -ErrorAction SilentlyContinue
                Throw $_
            }
        }
    }
    else {
        $Context = $null
        $params = @{
            Scope       = 'Process'
            ErrorAction = 'Stop'
            Confirm     = $false
            WhatIf      = $false
        }

        if ($IsAzureAutomationJob) {
            Write-Verbose '[Connect-Auto_AzAccount]: - Using system-assigned Managed Service Identity'
            $params.Identity = $true
        }
        elseif ($IsNonUserInteractive) {
            Throw '[Connect-Auto_AzAccount]: - Non-interactive mode is not supported for Azure connection.'
        }
        elseif ($IsContainerized) {
            Write-Verbose '[Connect-Auto_AzAccount]: - Using device code authentication'
            $params.UseDeviceAuthentication = $true
        }
        else {
            Write-Verbose '[Connect-Auto_AzAccount]: - Using interactive sign in'
        }

        try {
            if ($Tenant) {
                if (
                    $Tenant -notmatch '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' -or
                    $Tenant -eq '00000000-0000-0000-0000-000000000000'
                ) {
                    Throw '[Connect-Auto_AzAccount]: - Invalid tenant ID. The tenant ID must be a valid GUID.'
                }
                $params.Tenant = $Tenant
            }
            if ($Subscription) {
                if (
                    $Subscription -notmatch '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' -or
                    $Subscription -eq '00000000-0000-0000-0000-000000000000'
                ) {
                    Throw '[Connect-Auto_AzAccount]: - Invalid subscription ID. The subscription ID must be a valid GUID.'
                }
                $params.Subscription = $Subscription
            }

            if (-not $params.UseDeviceAuthentication) {
                Write-Information 'Connecting to Microsoft Azure ...' -InformationAction Continue
            }
            $Context = (Az.Accounts\Connect-AzAccount @params).context

            if ($null -eq $Context.Subscription) {
                Az.Accounts\Disconnect-AzAccount -ErrorAction SilentlyContinue
                Throw '[Connect-Auto_AzAccount]: - No subscription found, or you do not have access to any subscriptions.'
            }
            if ($params.Subscription -and $params.Subscription -ne $Context.Subscription) {
                Az.Accounts\Disconnect-AzAccount -ErrorAction SilentlyContinue
                Throw "[Connect-Auto_AzAccount]: - Subscription '$($Context.Subscription)' does not match the specified subscription '$($params.Subscription)'."
            }
            $Context = Az.Accounts\Set-AzContext -SubscriptionName $Context.Subscription -DefaultProfile $Context

            if ($SetEnvVarsAfterMgConnect -eq $true) {
                Initialize-EnvVarsAfterMgConnect
            }
        }
        catch {
            Throw $_
        }
    }

    Write-Auto_FunctionEnd $MyInvocation -OnceOnly
}