Get-BearerToken.ps1

<#
.SYNOPSIS
   This function retrieves an access token (bearer) from the Graph API
.DESCRIPTION
    This function may be used local to retrieve an access token, or by creating a credential in an Azure Automation Account
.NOTES
    Author: Nik Chikersal
    Date: 4/12/2024
    Version: V1.0.0
    Change Log: N/A
.LINK
https://www.powershellgallery.com/packages/Graph/

.EXAMPLE
    Get-BearerToken -ClientID "8c193358-c9c9-4255e-acd8c28f4a" -TenantName "Domain.com" -Secret 'Mysecret'
    This example will allow you to retrieve a bearer token locally, by providing the Client App Secret from Azure AD

    Get-BearerToken -ClientID "8c193358-c9c9-4255e-acd8c28f4a" -TenantName "Domain.com" -RunbookUserName "ClientSecret-Graph"
    This example will retrieve a bearer token from the credential in the Automation Account and autmatically set it on the Runbooks canvas

    Get-BearerToken -UseMSI
    This example will retrieve a bearer token from the MSI being used in the Azure Automation and Runbook
#>

function Get-BearerToken {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = [boolean]$true, ValueFromPipelineByPropertyName = [boolean]$true)]
        [ValidateNotNullOrEmpty()][ValidateLength('30', '36')]
        [string]$ClientID,
        [Parameter(Mandatory = $false, Position = 1, ValueFromPipeline = [boolean]$true, ValueFromPipelineByPropertyName = [boolean]$true )]
        [ValidateNotNullOrEmpty()]
        [string]$TenantName,
        [Parameter(Mandatory = $false, Position = 6)]
        [string]$Secret,
        [Parameter(Mandatory = $false, Position = 4, ValueFromPipeline = [boolean]$true, ValueFromPipelineByPropertyName = [boolean]$true)]
        [string]$RunbookUsername,
        [Parameter(Mandatory = $false, Position = 5)]
        [switch]$UseMSI
    )

    if (-not $PSCmdlet.MyInvocation.BoundParameters["Secret"] -and
       (-not $PSCmdlet.MyInvocation.BoundParameters["RunbookUsername"] -and
       (-not $PSCmdlet.MyInvocation.BoundParameters["UseMSI"]))) {
        throw "You must include at least one of the following parameters: -Secret, -RunbookUserName, -UseMSI"
    }

    switch ($PSCmdlet.MyInvocation.BoundParameters.Keys) {
        "Secret" {
            if (-not $PSCmdlet.MyInvocation.BoundParameters.Keys.Equals("RunbookUserName")) {
                if (-not [string]::IsNullOrEmpty($Secret)) {
                    if ($Secret.Length -gt "30") {

                        [hashtable]$Body = [System.Collections.Specialized.OrderedDictionary]::new()
                        [hashtable]$TokenSplat = [System.Collections.Specialized.OrderedDictionary]::new()

                        [hashtable]$Body.Add("Grant_Type", [string]"client_credentials")
                        [hashtable]$Body.Add("Scope", [string]"https://graph.microsoft.com/.default")
                        [hashtable]$Body.Add("client_Id ", [string]$clientID)
                        [hashtable]$Body.Add("Client_Secret", [string]$Secret)
                        [hashtable]$TokenSplat.Add("Uri", [string]"https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token")
                        [hashtable]$TokenSplat.Add("Method", [string]"POST")
                        [hashtable]$TokenSplat.Add("Body", [hashtable]$Body)

                        try {
                            $global:Token = (Invoke-RestMethod @TokenSplat).access_token
                            return $global:Token   
                        }
                        catch [System.Exception] {
                            throw $global:Error[0].Exception.Message
                        }
                    }
                    else {
                        throw "Secret must be 36 characters"
                    }
                }
                else {
                    throw "Secret must not be null or empty"
                }
            }
        }
        "RunbookUsername" {
            if (-not $PSCmdlet.MyInvocation.BoundParameters.Keys.Equals("Secret")) {

                if (-not (Get-Command -Name 'Get-AutomationPSCredential' -ErrorAction SilentlyContinue)) {
                    throw "Please ensure this command is being used in an Azure Runbook"
                }
            
                [hashtable]$Body = [System.Collections.Specialized.OrderedDictionary]::new()
                [hashtable]$TokenSplat = [System.Collections.Specialized.OrderedDictionary]::new()

                [hashtable]$Body.Add("Grant_Type", [string]"client_credentials")
                [hashtable]$Body.Add("Scope", [string]"https://graph.microsoft.com/.default")
                [hashtable]$Body.Add("client_Id ", [string]$clientID)
                [hashtable]$Body.Add("Client_Secret", (Get-AutomationPSCredential -Name $RunbookUsername).GetNetworkCredential().Password)
                [hashtable]$TokenSplat.Add("Uri", [string]"https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token")
                [hashtable]$TokenSplat.Add("Method", [string]"POST")
                [hashtable]$TokenSplat.Add("Body", [hashtable]$Body)
                
                try {
                    (Invoke-RestMethod @TokenSplat).access_token  
                }
                catch [System.Exception] {
                    throw $global:Error[0].Exception.Message
                }
            }
        }
        "UseMSI" {
            if ($PSCmdlet.MyInvocation.BoundParameters.Keys.Contains("ClientID") -or
               ($PSCmdlet.MyInvocation.BoundParameters.Keys.Contains("TenantName") -or
               ($PSCmdlet.MyInvocation.BoundParameters.Keys.Contains("Secret") -or
               ($PSCmdlet.MyInvocation.BoundParameters.Keys.Contains("RunbookUsername"))))) {
                throw 'You must only use the -UseMSI Parameter while using an MSI in a Runbook'

            }
            else {
                try {
                    function Get-GraphAccessToken {
                        [CmdletBinding()]
                        param (
                            [Parameter(Mandatory = $false)]
                            [ValidateNotNullOrEmpty()]
                            [switch]$UseMSI
                        )
                        
                        if ($UseMSI) {
                            try {
                                [void](Connect-AzAccount -Identity)
                                $ResourceURL = "https://graph.microsoft.com"
                                $global:BearerToken = [string](Get-AzAccessToken -ResourceUrl $ResourceURL).Token 
                                return $global:BearerToken      
                            }
                            catch {
                                Write-Warning $Error.Exception[0]
                            }
                        }
                        else {
                            try {
                                if (Get-Command -Name Connect-AzAccount) {
                                    [void](Connect-AzAccount)
                                    $ResourceURL = "https://graph.microsoft.com"
                                    $global:BearerToken = [string](Get-AzAccessToken -ResourceUrl $ResourceURL).Token
                                    return $global:BearerToken    
                                }
                            }
                            catch {
                                Write-Warning $Error.Exception[0]
                            }
                        }
                    }
                    [string](Get-GraphAccessToken -UseMSI) #This cmlet runs from Azure Secrets Module
                }
                catch [System.Exception] {
                    throw $global:Error[0].Exception.Message
                }
            }
        }
    }
}