Set-AzAdvancedContext.psm1

<#
.SYNOPSIS
    Sets the Azure context using advanced authentication methods.
 
.DESCRIPTION
    The Set-AzAdvancedContext function allows users to set the Azure context using advanced authentication methods, including specifying credentials and tenant/subscription IDs. It provides interactive prompts and error handling for a seamless authentication experience.
 
.PARAMETER Credential
    Specifies a PSCredential object containing Azure authentication details. Mandatory parameter when using AzureEnvironment parameter set.
 
.PARAMETER TenantID
    Specifies the Azure Active Directory tenant ID. Mandatory parameter when using AzureEnvironment parameter set.
 
.PARAMETER SubscriptionID
    Specifies the Azure subscription ID. Mandatory parameter when using AzureEnvironment parameter set.
 
.PARAMETER ContinueOnError
    Specifies whether the function should continue processing in case of errors. By default, it stops on errors.
 
.PARAMETER NoInteractive
    Specifies whether to suppress interactive prompts.
 
.OUTPUTS
    None - Using the -Verbose switch will cause information to be sent to the standard output
 
.NOTES
    File Name : Set-AzAdvancedContext.psm1
    Author : Christoffer Windahl Madsen
    Prerequisite : This function requires the Az PowerShell module.
 
.LINK
    https://github.com/ChristofferWin/codeterraform
 
.EXAMPLE
    //Simply log in using all information => The credential object can either contain SPN ID + secret or username + password
    //If a user account is provided and it requires MFA, a pop-up will show
    Set-AzAdvancedContext -Credential (Get-Credential) -TenantID "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" -SubscriptionID "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
 
.EXAMPLE
    //Using a SPN ID + secret to login and also making sure the call is non-interractive as it must be used in an automatic proces
    Set-AzAdvancedContext -Credential (Get-Credential) -TenantID "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" -SubscriptionID "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" -NoInteractive
 
.EXAMPLE
    //Calling the function without any input causing it to automically search for an active session, if none is found a credential is requested
    Set-AzAdvancedContext
 
.EXAMPLE
    //Calling the function without any input and also forcing it to be non-interractive causing it to throw an error if no context can be automically found
    Set-AzAdvancedContext -NoInteractive
#>

function Set-AzAdvancedContext {
    [cmdletBinding(DefaultParameterSetName = 'ManualSettings')]
    param(
        [Parameter(ParameterSetName = "ManualSettings")][switch]$ContinueOnError,
        [Parameter(ParameterSetName = "ManualSettings")][switch]$NoInteractive,
        [Parameter(ParameterSetName = "AzureEnvironment", Mandatory = $true)][pscredential]$Credential,
        [Parameter(ParameterSetName = "AzureEnvironment", Mandatory = $true)][ValidatePattern('(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?')][string]$TenantID,
        [Parameter(ParameterSetName = "AzureEnvironment", Mandatory = $true)][ValidatePattern('(\{|\()?[A-Za-z0-9]{4}([A-Za-z0-9]{4}\-?){4}[A-Za-z0-9]{12}(\}|\()?')][string]$SubscriptionID
    )
    $AlreadyLoggedIn = (Get-AzContext) -notin $null
    if($Credential.Count -eq 0 -and !$NoInteractive -and !$AlreadyLoggedIn){
        Write-Warning "No context could be found. Please provide a credential object... "
        Write-Output "Either username/password or app id/secret"
        try{
            $Credential = Get-Credential -ErrorAction Stop
        }
        catch{
            Write-Warning "Information for the credential object is lacking, function stopping..."
            return
        }
    }
    elseif($AlreadyLoggedIn){
        #Simply continue
    }
    elseif($Credential.Count -gt 0 -and !$NoInteractive){
        #Simply continue
    }
    else{
        Write-Verbose "No Azure context found and no credential is provided..."
        Write-Error "Cannot ask for credential due to flag 'NoInteractive'"
        break
    }
    do{
        if(!$TenantID -and !$AlreadyLoggedIn -and !$NoInteractive){
            try{
                Login-AzAccount -ErrorAction Stop
                $AlreadyLoggedIn = $true
                Continue
            }
            catch{
                if(!$ContinueOnError){
                    Write-Error "Threw an error: $_"
                    break
                }
                Write-Warning "The interactive login failed... retrying..."
            }
        }
        elseif($TenantID -and $SubscriptionID -and !$AlreadyLoggedIn){
            try{
                Login-AzAccount -Tenant $TenantID -Subscription $SubscriptionID -Credential $Credential -ErrorAction Stop
                $AlreadyLoggedIn = $true
                Continue
            }
            catch{
                if($NoInteractive -or !$ContinueOnError){
                    if($_.Exception.Message -like "*ROPC does not support MSA accounts*" -and $NoInteractive){
                        Write-Error "The account: $($Credential.Username) must use interactive authentication..."
                    }
                    elseif($_.Exception.Message -like "*validating credentials due to invalid username or password*" -or $_.Message -like "*password is expired*" -or $_.Message -like "*user account is disabled*" -or $_.Message -like "*does not have access to subscription*" -or $_.Message -like "*must use multi-factor authentication*"){
                        Write-Error "Username or password is incorrect for tenant: $TenantID"
                    }
                    else{
                        Write-Error "An error occured while trying to login using the provided credential:`n$_"
                    }
                }
                else{
                    if($_.Exception.Message -like "*Tenant* not found*"){
                        Write-Warning "The Azure Tenant provided: $TenantID was not found..."
                        do{
                            try{
                                $TenantID = Read-Host "Please provide a valid TenantID..." -ErrorAction Stop
                                $OK = $true
                            }
                            catch{
                                Write-Warning "The TenantID provided is not a valid GUID..."
                            }
                        }
                       while(!$OK)
                    }
                    $OK = $false
                    $CredentialOK = $false
                    if($_.Exception.Message -like "*validating credentials due to invalid username or password*" -or $_.Exception.Message -like "*password is expired*" -or $_.Exception.Message -like "*user account is disabled*" -or $_.Exception.Message -like "*must use multi-factor authentication*" -or $_.Exception.Message -like "* Unsupported User Type*" -and !$CredentialOK){
                        Write-Warning "The following error occured while trying to login:`n$_"
                        $Credential = Get-Credential
                    }
                    else{
                        $CredentialOK = $true
                    }
                    try{
                        Login-AzAccount -Tenant $TenantID -Subscription $SubscriptionID -Credential $Credential -ErrorAction Stop -WarningVariable Warnings 3>$null
                    }
                    catch{
                        if($Warnings.Message -like "*The subscription*could not be found*"){
                            Write-Warning "The subscription: $SubscriptionID could not be found in Azure..."
                        }
                        do{
                            try{
                                $SubscriptionID = Read-Host "Please provide a valid SubscriptionID..." -ErrorAction Stop
                                $OK = $true
                            }
                            catch{
                                Write-Warning "The SubscriptionID provided is not a valid GUID..."
                            }
                        }
                        while(!$OK)
                        if($Warnings.Message -like "*does not have authorization to perform action 'Microsoft.Resources/subscriptions/read'*"){
                            if(!$ContinueOnError){
                                Write-Error "The user does not have read access to the subscription: $SubscriptionID..."
                                break
                            }
                            $Warnings = ""
                            Write-Verbose "Running through 10 cycels of 30 seconds each for a total of 5minutes..."
                            for($i = 1; $i -le 10; $i++){
                                Write-Warning "Go to the Azure subscription: $SubscriptionID and provide a minimum of reader for the user: $($Credential.Username)"
                                Start-Sleep -Seconds 30
                                try{
                                    Login-AzAccount -Tenant $TenantID -Subscription $SubscriptionID -Credential $Credential -ErrorAction Stop -WarningVariable Warnings 3>$null
                                }
                                catch{
                                    if($Warnings.Message -like "*does not have authorization to perform action 'Microsoft.Resources/subscriptions/read'*" -and $i -ne 10){
                                        Write-Verbose "Cycle: $i / 10 - $(($i * 30)/60) minutes gone..."
                                        Write-Warning "The user: $($Credential.Username) does still not have the minimum role on the subscription: $SubscriptionID"
                                    }
                                    else{
                                        Write-Error "The following error occured while trying to verify whether the user: $($Credential.Username) has access to subscription: $SubcriptionID, error:`n$_"
                                        break
                                    }
                                }
                            }
                        }
                    }
                }   
            }    
        } 
    }
    while(!$AlreadyLoggedIn)
    $Context = Get-AzContext
    Write-Verbose "User: $($Context.Account) successfully logged in to Azure tenant: $($Context.Tenant.ID) and subscription: $($Context.Subscription.Id)"
    return
}
Export-ModuleMember Set-AzAdvancedContext