MSAL.PS.psm1

Set-StrictMode -Version 2.0

## Global Variables
[Microsoft.Identity.Client.TokenCache] $TokenCache = New-Object Microsoft.Identity.Client.TokenCache
[System.Collections.Generic.Dictionary[string,Microsoft.Identity.Client.PublicClientApplication]] $PublicClientApplications = New-Object 'System.Collections.Generic.Dictionary[string,Microsoft.Identity.Client.PublicClientApplication]'
[System.Collections.Generic.Dictionary[string,Microsoft.Identity.Client.ConfidentialClientApplication]] $ConfidentialClientApplications = New-Object 'System.Collections.Generic.Dictionary[string,Microsoft.Identity.Client.ConfidentialClientApplication]'

function ConvertFrom-SecureStringAsPlainText {
    [CmdletBinding()]
    param (
        # Secure String Value
        [Parameter(Mandatory=$true)]
        [securestring] $SecureString
    )

    try
    {
        [IntPtr] $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
        Write-Output ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR))
    }
    finally
    {
        [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
    }
}

function Get-MSALClientApplication {
    [CmdletBinding(DefaultParameterSetName='PublicClient')]
    param
    (
        # Identifier of the client requesting the token.
        [parameter(Mandatory=$true)]
        [string] $ClientId,
        # Secure secret of the client requesting the token.
        [parameter(Mandatory=$true, ParameterSetName="ConfidentialClientSecret")]
        [securestring] $ClientSecret,
        # Client assertion certificate of the client requesting the token.
        [parameter(Mandatory=$true, ParameterSetName="ConfidentialClientAssertionCertificate")]
        [System.Security.Cryptography.X509Certificates.X509Certificate2] $ClientAssertionCertificate,
        # Address to return to upon receiving a response from the authority.
        [parameter(Mandatory=$false)]
        [uri] $RedirectUri,
        # Address of the authority to issue token.
        [parameter(Mandatory=$false)]
        [string] $Authority = 'https://login.microsoftonline.com/common'
    )

    if ($PSCmdlet.ParameterSetName -eq "PublicClient") {
        if (!$PublicClientApplications.ContainsKey($ClientId)) {
            $PublicClientApplications[$ClientId] = New-Object Microsoft.Identity.Client.PublicClientApplication -ArgumentList $ClientId, $Authority, $TokenCache
        }
        if ($RedirectUri) { $PublicClientApplications[$ClientId].RedirectUri = $RedirectUri }
        return $PublicClientApplications[$ClientId]
    }
    else {
        if (!$ConfidentialClientApplications.ContainsKey($ClientId)) {
            switch ($PSCmdlet.ParameterSetName)
            {
                'ConfidentialClientSecret' {
                    [Microsoft.Identity.Client.ClientCredential] $ClientCredential = New-MSALClientCredential -ClientSecret $ClientSecret
                }
                'ConfidentialClientAssertionCertificate' {
                    [Microsoft.Identity.Client.ClientCredential] $ClientCredential = New-MSALClientCredential -ClientAssertionCertificate $ClientAssertionCertificate
                }
            }
            $ConfidentialClientApplications[$ClientId] = New-Object Microsoft.Identity.Client.ConfidentialClientApplication -ArgumentList $ClientId, $Authority, $RedirectUri, $ClientCredential, $TokenCache, $TokenCache
        }
        if ($RedirectUri) { $ConfidentialClientApplications[$ClientId].RedirectUri = $RedirectUri }
        return $ConfidentialClientApplications[$ClientId]
    }
}

function Get-MSALAccount {
    param
    (
        #
        [parameter(Mandatory=$true, ParameterSetName='ClientApplication')]
        [Microsoft.Identity.Client.IClientApplicationBase] $ClientApplication,
        # Information of a single account.
        [parameter(Mandatory=$true, ParameterSetName='Accounts')]
        [Microsoft.Identity.Client.IAccount[]] $Accounts,
        # The username in UserPrincipalName (UPN) format.
        [parameter(Mandatory=$false)]
        [string] $Username
    )

    if ($PSCmdlet.ParameterSetName -eq 'ClientApplication') {
        [Microsoft.Identity.Client.IAccount[]] $Accounts = $ClientApplication.GetAccountsAsync().GetAwaiter().GetResult()
    }

    if ($Username) {
        return $Accounts | Where-Object Username -eq $Username
    }
    else {
        return $Accounts
    }
}

function New-MSALClientCredential {
    [CmdletBinding(DefaultParameterSetName='ClientSecret')]
    param
    (
        # Secure secret or client assertion certificate of the client requesting the token.
        [parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName='InputObject', Position=1)]
        [object] $InputObject,
        # Secure secret of the client requesting the token.
        [parameter(Mandatory=$true, ParameterSetName='ClientSecret', Position=1)]
        [securestring] $ClientSecret,
        # Client assertion certificate of the client requesting the token.
        [parameter(Mandatory=$true, ParameterSetName="ClientAssertionCertificate", Position=1)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2] $ClientAssertionCertificate
    )

    ## InputObject Casting
    if($InputObject -is [System.Security.Cryptography.X509Certificates.X509Certificate2]) {
        [System.Security.Cryptography.X509Certificates.X509Certificate2] $ClientAssertionCertificate = $InputObject
    }
    elseif($InputObject -is [pscredential]) {
        [securestring] $ClientSecret = $InputObject.Password
    }
    elseif($InputObject -is [System.Net.NetworkCredential]) {
        [securestring] $ClientSecret = $InputObject.SecurePassword
    }
    elseif ($InputObject -is [securestring]) {
        [securestring] $ClientSecret = $InputObject
    }

    ## New ClientCredential
    if ($ClientSecret) {
        [Microsoft.Identity.Client.ClientCredential] $ClientCredential = (New-Object Microsoft.Identity.Client.ClientCredential -ArgumentList (ConvertFrom-SecureStringAsPlainText $ClientSecret))
    }
    elseif ($ClientAssertionCertificate) {
        [Microsoft.Identity.Client.ClientCredential] $ClientCredential = (New-Object Microsoft.Identity.Client.ClientCredential -ArgumentList $ClientAssertionCertificate)
    }

    return $ClientCredential
}

function Get-MSALToken {
    [CmdletBinding(DefaultParameterSetName='Implicit')]
    param
    (
        # Tenant identifier of the authority to issue token.
        [parameter(Mandatory=$false)]
        [string] $TenantId = "common",

        # Address of the authority to issue token. This value overrides TenantId.
        [parameter(Mandatory=$false)]
        [string] $Authority = "https://login.microsoftonline.com/$TenantId",

        # Identifier of the client requesting the token.
        [parameter(Mandatory=$true)]
        [string] $ClientId,

        # Secure secret of the client requesting the token.
        [parameter(Mandatory=$true, ParameterSetName='ClientSecret')]
        [parameter(Mandatory=$true, ParameterSetName='ClientSecret-AuthorizationCode')]
        [parameter(Mandatory=$true, ParameterSetName='ClientSecret-OnBehalfOf')]
        [securestring] $ClientSecret,

        # Client assertion certificate of the client requesting the token.
        [parameter(Mandatory=$true, ParameterSetName='ClientAssertionCertificate')]
        [parameter(Mandatory=$true, ParameterSetName='ClientAssertionCertificate-AuthorizationCode')]
        [parameter(Mandatory=$true, ParameterSetName='ClientAssertionCertificate-OnBehalfOf')]
        [System.Security.Cryptography.X509Certificates.X509Certificate2] $ClientAssertionCertificate,

        # The authorization code received from service authorization endpoint.
        [parameter(Mandatory=$true, ParameterSetName='ClientSecret-AuthorizationCode')]
        [parameter(Mandatory=$true, ParameterSetName='ClientAssertionCertificate-AuthorizationCode')]
        [string] $AuthorizationCode,

        # Assertion representing the user.
        [parameter(Mandatory=$true, ParameterSetName='ClientSecret-OnBehalfOf')]
        [parameter(Mandatory=$true, ParameterSetName='ClientAssertionCertificate-OnBehalfOf')]
        [string] $UserAssertion,

        # Type of the assertion representing the user.
        [parameter(Mandatory=$false, ParameterSetName='ClientSecret-OnBehalfOf')]
        [parameter(Mandatory=$false, ParameterSetName='ClientAssertionCertificate-OnBehalfOf')]
        [string] $UserAssertionType,

        # Address to return to upon receiving a response from the authority.
        [Parameter(Mandatory=$false, ParameterSetName='Implicit')]
        [parameter(Mandatory=$false, ParameterSetName='ClientSecret-AuthorizationCode')]
        [parameter(Mandatory=$false, ParameterSetName='ClientAssertionCertificate-AuthorizationCode')]
        [uri] $RedirectUri = 'urn:ietf:wg:oauth:2.0:oob',

        # Array of scopes requested for resource
        [Parameter(Mandatory=$true)]
        [string[]] $Scopes,

        # Array of scopes for which a developer can request consent upfront.
        [Parameter(Mandatory=$false, ParameterSetName='Implicit')]
        [string[]] $ExtraScopesToConsent,

        # Identifier of the user. Generally a UPN.
        [Parameter(Mandatory=$false, ParameterSetName='Implicit')]
        [string] $LoginHint,

        # Indicates how AcquireToken should prompt the user.
        [Parameter(Mandatory=$false, ParameterSetName='Implicit')]
        [Microsoft.Identity.Client.UIBehavior] $UIBehavior,

        # This parameter will be appended as is to the query string in the HTTP authentication request to the authority.
        [Parameter(Mandatory=$false, ParameterSetName='Implicit')]
        [string] $extraQueryParameters
    )

    switch -Wildcard ($PSCmdlet.ParameterSetName)
    {
        "Implicit" {
            [Microsoft.Identity.Client.PublicClientApplication] $PublicClientApplication = Get-MSALClientApplication -ClientId $ClientId -RedirectUri $RedirectUri
            [Microsoft.Identity.Client.IAccount] $Account = Get-MSALAccount -ClientApplication $PublicClientApplication -Username $LoginHint
            break
        }
        "ClientSecret*" {
            [Microsoft.Identity.Client.ConfidentialClientApplication] $ConfidentialClientApplication = Get-MSALClientApplication -ClientId $ClientId -ClientSecret $ClientSecret -RedirectUri $RedirectUri -Authority $Authority
            break
        }
        "ClientAssertionCertificate*" {
            [Microsoft.Identity.Client.ConfidentialClientApplication] $ConfidentialClientApplication = Get-MSALClientApplication -ClientId $ClientId -ClientAssertionCertificate $ClientAssertionCertificate -RedirectUri $RedirectUri -Authority $Authority
            break
        }
    }

    [Microsoft.Identity.Client.AuthenticationResult] $AuthenticationResult = $null
    switch -Wildcard ($PSCmdlet.ParameterSetName)
    {
        "Implicit" {
            if ($Account) {
                if ($UIBehavior) {
                    $AuthenticationResult = $PublicClientApplication.AcquireTokenAsync($Scopes,$Account,$UIBehavior,$extraQueryParameters,$ExtraScopesToConsent,$Authority).GetAwaiter().GetResult()
                }
                else {
                    $AuthenticationResult = $PublicClientApplication.AcquireTokenSilentAsync($Scopes,$Account,$Authority,$false).GetAwaiter().GetResult()
                }
            }
            else {
                if (!$UIBehavior) { $UIBehavior = [Microsoft.Identity.Client.UIBehavior]::SelectAccount }
                $AuthenticationResult = $PublicClientApplication.AcquireTokenAsync($Scopes,$LoginHint,$UIBehavior,$extraQueryParameters,$ExtraScopesToConsent,$Authority).GetAwaiter().GetResult()
            }
            break
        }
        "ClientSecret" {
            $AuthenticationResult = $ConfidentialClientApplication.AcquireTokenForClientAsync($Scopes).GetAwaiter().GetResult()
            break
        }
        "ClientAssertionCertificate" {
            $AuthenticationResult = $ConfidentialClientApplication.AcquireTokenForClientAsync($Scopes).GetAwaiter().GetResult()
            break
        }
        "*AuthorizationCode" {
            $AuthenticationResult = $ConfidentialClientApplication.AcquireTokenByAuthorizationCodeAsync($AuthorizationCode,$Scopes).GetAwaiter().GetResult()
            break
        }
        "*OnBehalfOf" {
            [Microsoft.Identity.Client.UserAssertion] $UserAssertionObj = New-Object Microsoft.Identity.Client.UserAssertion -ArgumentList $UserAssertion, $UserAssertionType
            $AuthenticationResult = $ConfidentialClientApplication.AcquireTokenOnBehalfOfAsync($Scopes,$UserAssertionObj).GetAwaiter().GetResult()
            break
        }
    }

    return $AuthenticationResult
}

#function Clear-MSALTokenCache {
# $TokenCache.Clear()
#}