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" -LocalTest
    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 -ClientID "8c193358-c9c9-4255e-acd8c28f4a" -TenantName "Domain.com" -UseDelegatedPermissions
    This example will retrieve a bearer token from the ClientID using delegated permissions and the configured app registration re-direct URIs for auth
 
    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)]
        [switch]$LocalTest,
        [Parameter(Mandatory = $false, Position = 4, ValueFromPipeline = [boolean]$true, ValueFromPipelineByPropertyName = [boolean]$true)]
        [string]$RunbookUsername,
        [Parameter(Mandatory = $false, Position = 3)]
        [switch]$UseDelegatedPermissions,
        [Parameter(Mandatory = $false, Position = 5)]
        [switch]$UseMSI
    )

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

    switch ($PSCmdlet.MyInvocation.BoundParameters.Keys) {
        "LocalTest" {
            if (-not $PSCmdlet.MyInvocation.BoundParameters.Keys.Equals("RunbookUserName") -and 
               (-not $PSCmdlet.MyInvocation.BoundParameters.Keys.Equals("UseDelegatedPermissions"))) {
                [string]$Secret = Read-Host "Enter Secret from ClientID"
                if (-not [string]::IsNullOrEmpty($Secret)) {
                    if ($Secret.Length -gt "30") {
                        #[System.Security.SecureString](ConvertTo-SecureString -String $Secret -Force -AsPlainText)
                       
                        [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("LocalTest") -and
               (-not $PSCmdlet.MyInvocation.BoundParameters.Keys.Equals("UseDelegatedPermissions"))) {

                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
                }
            }
        }
        "UseDelegatedPermissions" {
            if (-not $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("LocalTest") -and 
               (-not $PSCmdlet.MyInvocation.BoundParameters.ContainsKey("RunbookUsername"))) {
                if (-not (Get-Command Get-MsalToken)) {
                    throw "Ensure you have MSAL.PS Installed. Graph may of not fully installed and loaded the required modules"
                }
                [hashtable]$DelegatedAuthSplat = [System.Collections.Specialized.OrderedDictionary]::new()
                [hashtable]$DelegatedAuthSplat.Add("ClientId", [string]$ClientID)
                [hashtable]$DelegatedAuthSplat.Add("TenantId", [string]$TenantName)
                [hashtable]$DelegatedAuthSplat.Add("Interactive", [boolean]$True)
                try {
                    [string](Get-MsalToken @DelegatedAuthSplat).AccessToken
                }
                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("LocalTest") -or
               ($PSCmdlet.MyInvocation.BoundParameters.Keys.Contains("RunbookUsername") -or
               ($PSCmdlet.MyInvocation.BoundParameters.Keys.Contains("UseDelegatedPermissions")))))) {
                throw 'You must only use the -UseMSI Parameter while using an MSI in a Runbook'

            }
            else{
                try {
                    [string](Get-GraphAccessToken -UseMSI) #This cmlet runs from Azure Secrets Module
                }
                catch [System.Exception] {
                    return $global:Error[0].Exception.Message
                }
            }
        }
    }
}