AzureADAuthMethods.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\AzureADAuthMethods.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = $false
if ($AzureADAuthMethods_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = $false
if ($AzureADAuthMethods_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path "$($script:ModuleRoot)\..\.git") { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }
    
function Import-ModuleFile
{
    <#
        .SYNOPSIS
            Loads files into the module on module import.
         
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
             
            This provides a central location to react to files being imported, if later desired
         
        .PARAMETER Path
            The path to the file to load
         
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
     
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )
    
    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) { . $resolvedPath }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles)
{
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
function Assert-GraphConnection
{
<#
    .SYNOPSIS
        Asserts a valid graph connection has been established.
     
    .DESCRIPTION
        Asserts a valid graph connection has been established.
     
    .PARAMETER Cmdlet
        The $PSCmdlet variable of the calling command.
     
    .EXAMPLE
        PS C:\> Assert-GraphConnection -Cmdlet $PSCmdlet
     
        Asserts a valid graph connection has been established.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Cmdlet
    )
    
    process
    {
        if ($script:msgraphToken) { return }
        
        $exception = [System.InvalidOperationException]::new('Not yet connected to MSGraph. Use Connect-AzureADUserAuthenticationMethod to establish a connection!')
        $errorRecord = [System.Management.Automation.ErrorRecord]::new($exception, "NotConnected", 'InvalidOperation', $null)
        
        $Cmdlet.ThrowTerminatingError($errorRecord)
    }
}

function ConvertTo-AuthHeader {
<#
    .SYNOPSIS
        Generates an authentication header from a graph token.
     
    .DESCRIPTION
        Generates an authentication header from a graph token.
     
    .PARAMETER Token
        The token from which to build the authentication header.
     
    .EXAMPLE
        PS C:\> Get-Token | ConvertTo-AuthHeader
     
        Generates an authentication header from a graph token.
#>

    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline = $true)]
        $Token
    )
    
    process {
        foreach ($tokenObject in $Token) {
            @{
                Authorization = "Bearer $($tokenObject.AccessToken)"
                'Content-Type' = 'application/json'
                'Accept'      = 'application/json, text/plain'
            }
        }
    }
}

function Get-Token
{
<#
    .SYNOPSIS
        Returns the token to use for authentication to MSGraph.
     
    .DESCRIPTION
        Returns the token to use for authentication to MSGraph.
        Automatically refreshes it if it is close to expiration.
     
    .EXAMPLE
        PS C:\> Get-Token
     
        Returns the token to use for authentication to MSGraph.
#>

    [CmdletBinding()]
    Param (
    
    )
    
    process
    {
        if ($script:msgraphToken -and $script:msgraphToken.ExpiresOn.LocalDateTime -gt (Get-Date).AddMinutes(3)) {
            return $script:msgraphToken
        }
        
        $parameters = @{
            TenantId = $script:tenantID
            ClientId = $script:clientID
            ErrorAction = 'Stop'
        }
        if ($script:clientCertificate) {
            $parameters.ClientCertificate = $script:clientCertificate
        }
        else {
            $parameters.RedirectUri = $script:redirectUri
            $parameters.LoginHint = $script:msgraphToken.Account.Username
            $parameters.Silent = $true
        }
        
        try { $token = Get-MsalToken @parameters }
        catch {
            Write-Warning "Failed to re-authenticate to tenant $script:tenantID : $_"
            throw
        }
        $script:msgraphToken = $token
        return $token
    }
}

function Invoke-AzureAdRequest
{
<#
    .SYNOPSIS
        Execute an arbitrary graph call against AzureAD endpoints.
     
    .DESCRIPTION
        Execute an arbitrary graph call against AzureAD endpoints.
        Handles authentication & token refresh transparently.
     
    .PARAMETER Query
        The actual query to execute.
     
    .PARAMETER Method
        The REST method to apply
     
    .PARAMETER Body
        Any body data to pass along as part of the request
     
    .PARAMETER GetValues
        Get the content of the .Value property, rather than the raw response content
     
    .PARAMETER Raw
        Get raw response
     
    .EXAMPLE
        PS C:\> Invoke-AzureAdRequest -Query 'users/3ec9f2ec-aeec-4ad9-ad18-b456288fdb32/authentication/phonemethods' -Method GET
         
        Retrieve the phone authentication settings for the specified user.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Query,
        
        [Parameter(Mandatory = $true)]
        [string]
        $Method,
        
        $Body,
        
        [switch]
        $GetValues,
        
        [switch]
        $Raw
    )
    
    begin
    {
        try { $authHeader = Get-Token | ConvertTo-AuthHeader }
        catch { throw }
        
        $parameters = @{
            Method = $Method
            Uri    = "$($script:baseUri.Trim("/"))/$($Query.TrimStart("/"))"
            Headers = $authHeader
        }
        if ($Body) { $parameters.Body = $Body }
    }
    process
    {
        try { $response = Invoke-RestMethod @parameters -ErrorAction Stop }
        catch { throw }
        
        
        if ($Raw) { return $response }
        if ($GetValues) { return $response.Value }
        $response.Value
        
    }
}


function Connect-AzureADUserAuthenticationMethod {
    [CmdletBinding(DefaultParameterSetName = 'Interactive')]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $TenantId,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Certificate')]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        $Certificate,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'Thumbprint')]
        [string]
        $Thumbprint,
        
        [Parameter(ParameterSetName = 'Thumbprint')]
        [string]
        $CertificateStore = 'Cert:\CurrentUser\My',
        
        [Parameter(ParameterSetName = 'Interactive')]
        [switch]
        $Interactive,

        [Parameter(ParameterSetName = 'Interactive')]
        [switch]
        $DeviceCode,
        
        [string]
        $ClientID = "",
        
        [string]
        $RedirectUri = "urn:ietf:wg:oauth:2.0:oob",
        
        [string]
        $BaseUri = 'https://graph.microsoft.com/beta/',
        
        [switch]
        $PassThru
    )
    
    process {
        if ($Thumbprint) {
            try { $Certificate = Get-Item -Path (Join-Path -Path $CertificateStore -ChildPath $Thumbprint) }
            catch { throw "Unable to find certificate $Thumbprint in certificate store $CertificateStore !" }
        }
        switch ($PSCmdlet.ParameterSetName) {
            'Interactive' {
                try { $token = Get-MsalToken -TenantId $TenantId -ClientId $ClientID -RedirectUri $RedirectUri -Interactive -DeviceCode:$DeviceCode }
                catch {
                    Write-Warning "Failed to authenticate to tenant $TenantID : $_"
                    throw
                }
            }
            default {
                try { $token = Get-MsalToken -TenantId $TenantId -ClientId $ClientID -ClientCertificate $Certificate }
                catch {
                    Write-Warning "Failed to authenticate to tenant $TenantID : $_"
                    throw
                }
            }
        }
        
        $script:msgraphToken = $token
        $script:baseUri = $BaseUri
        $script:tenantID = $TenantId
        $script:clientID = $ClientID
        $script:redirectUri = $RedirectUri
        $script:clientCertificate = $Certificate
        
        if ($PassThru) { $token }
    }
}


function Get-AzureADUserAuthenticationMethod {
    <#
    .SYNOPSIS
        Gets a user's authentication methods.
    .DESCRIPTION
        Gets a user's authentication methods.
        All methods are returned by default. Pass the required method as a switch to only get that method.
    .EXAMPLE
        PS C:\>Get-AzureADUserAuthenticationMethod -ObjectId user@contoso.com -Phone
        Gets the phone authentication methods set for the user.
    .EXAMPLE
        PS C:\>Get-AzureADUser -SearchString user1@contoso.com | Get-AzureADUserAuthenticationMethod
        Gets the phone authentication methods set for the user from the pipeline.
    .EXAMPLE
        PS C:\>Get-AzureADUserAuthenticationMethod -UserPrincipalName user@contoso.com -Phone
        Gets the phone authentication methods set for the user.
    #>

    [CmdletBinding(DefaultParameterSetName = 'allMethods')]
    param (
        [Parameter(Mandatory = $True, ParameterSetName = 'pin')]
        [switch]
        $Pin,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'softwareOath')]
        [switch]
        $softwareOath,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [switch]
        $Phone,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'email')]
        [switch]
        $Email,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'password')]
        [switch]
        $Password,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [switch]
        $SecurityQuestion,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'FIDO2')]
        [switch]
        $FIDO2,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'passwordlessMicrosoftAuthenticator')]
        [switch]
        $PasswordlessMicrosoftAuthenticator,

        [Parameter(Mandatory = $True, ParameterSetName = 'MicrosoftAuthenticator')]
        [switch]
        $MicrosoftAuthenticator,

        [Parameter(Mandatory = $True, ParameterSetName = 'WindowsHelloForBusiness')]
        [switch]
        $WindowsHelloForBusiness,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'default')]
        [switch]
        $Default,
        
        [Alias('UserId', 'UPN', 'UserPrincipalName')]
        [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $ObjectId
    )
    begin {
        Assert-GraphConnection -Cmdlet $PSCmdlet

        $common = @{
            Method = 'GET'
            GetValues = $false
        }
    }
    process {
        $values = switch ($PSCmdlet.ParameterSetName) {
            "phone" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/phoneMethods"
                break
            }
            "email" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/emailMethods"
                break
            }
            "password" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/passwordMethods"
                break
            }
            "FIDO2" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/fido2Methods"
                break
            }
            "passwordlessMicrosoftAuthenticator" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/passwordlessMicrosoftAuthenticatorMethods"
                break
            }
            "MicrosoftAuthenticator" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/MicrosoftAuthenticatorMethods"
                break
            }
            "WindowsHelloForBusiness" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/windowsHelloForBusinessMethods"
                break
            }
            "allMethods" {
                Invoke-AzureAdRequest @common -Query "users/$ObjectId/authentication/methods"
                break
            }
            default {
                throw "Getting the $($PSCmdlet.ParameterSetName) method is not yet supported."
            }
        }
        $values  | Add-Member -NotePropertyName userObjectId -NotePropertyValue $ObjectId -PassThru
    }
}


function New-AzureADUserAuthenticationMethod {
    <#
    .SYNOPSIS
        Creates a new authentication method for the user.
    .DESCRIPTION
        Creates a new authentication method for the user.
        Use to create a new method type for the user. To modify a method, use Update-AzureADUserAuthenticationMethod.
    .EXAMPLE
        PS C:\>New-AzureADUserAuthenticationMethod user@contoso.com -Phone -PhoneNumber '+61412345678' -PhoneType mobile
        Adds a new mobile phone authentication method to the user.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ParameterSetName = 'pin')]
        [switch]
        $Pin,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [switch]
        $Oath,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'password')]
        [switch]
        $Password,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [switch]
        $SecurityQuestion,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'default')]
        [switch]
        $Default,
        
        [Alias('UserId', 'UPN', 'UserPrincipalName')]
        [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $ObjectId,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'pin', Position = 2)]
        [string]
        $NewPin,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $SecretKey,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [int]
        $TimeIntervalInSeconds,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $SerialNumber,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $Manufacturer,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $Model,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [string]
        $PhoneNumber,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [ValidateSet("mobile", "alternateMobile", "office")]
        [string]
        $PhoneType,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'email', Position = 2)]
        [string]
        $EmailAddress,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'password')]
        [string]
        $NewPassword,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [string]
        $Question,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [string]
        $Answer
        
    )
    
    begin {
        Assert-GraphConnection -Cmdlet $PSCmdlet
    }
    process {
        switch ($PSCmdlet.ParameterSetName) {
            "phone" {
                $postParams = @{
                    phoneNumber = $PhoneNumber
                    phoneType = $PhoneType
                }
                $json = $postparams | ConvertTo-Json -Depth 99 -Compress
                Invoke-AzureAdRequest -Method POST -Query "users/$ObjectId/authentication/phoneMethods" -Body $json
                break
            }
            "email" {
                $postParams = @{
                    emailAddress = $EmailAddress
                }
                $json = $postparams | ConvertTo-Json -Depth 99 -Compress
                Invoke-AzureAdRequest -Method POST -Query "users/$ObjectId/authentication/emailMethods" -Body $json
                break
            }
            default {
                throw "Setting the $($PSCmdlet.ParameterSetName) method is not yet supported."
            }
        }
    }
}


function Remove-AzureADUserAuthenticationMethod {
    <#
    .SYNOPSIS
        Removes an authentication method from the user.
    .DESCRIPTION
        Removes an authentication method from the user.
        Use to remove an existing authentication method for the user.
    .EXAMPLE
        PS C:\>Remove-AzureADUserAuthenticationMethod -Phone -PhoneType mobile user@contoso.com
        Removes the mobile phone authentication method for the user.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ParameterSetName = 'pin')]
        [switch]
        $Pin,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [switch]
        $Oath,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [switch]
        $Phone,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'email')]
        [switch]
        $Email,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'password')]
        [switch]
        $Password,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'FIDO2')]
        [switch]
        $FIDO2,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'passwordlessMicrosoftAuthenticator')]
        [switch]
        $PasswordlessMicrosoftAuthenticator,

        [Parameter(Mandatory = $True, ParameterSetName = 'MicrosoftAuthenticator')]
        [switch]
        $MicrosoftAuthenticator,

        [Parameter(Mandatory = $True, ParameterSetName = 'WindowsHelloForBusiness')]
        [switch]
        $WindowsHelloForBusiness,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'FIDO2')]
        [Parameter(Mandatory = $True, ParameterSetName = 'passwordlessMicrosoftAuthenticator')]
        [Parameter(Mandatory = $True, ParameterSetName = 'MicrosoftAuthenticator')]
        [Parameter(Mandatory = $True, ParameterSetName = 'WindowsHelloForBusiness')]
        [string]
        $MethodId,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [switch]
        $SecurityQuestion,
        
        [Alias('UserId', 'UPN', 'UserPrincipalName')]
        [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $ObjectId,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $SerialNumber,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [ValidateSet("mobile", "alternateMobile", "office")]
        [string]
        $PhoneType,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [string]
        $Question
        
    )
    
    begin {
        Assert-GraphConnection -Cmdlet $PSCmdlet
    }
    process {
        switch ($PSCmdlet.ParameterSetName) {
            "phone" {
                $methodId = switch ($PhoneType) {
                    'alternateMobile' { 'b6332ec1-7057-4abe-9331-3d72feddfe41' }
                    'mobile' { '3179e48a-750b-4051-897c-87b9720928f7' }
                    'office' { 'e37fc753-ff3b-4958-9484-eaa9425c82bc' }
                }
                Invoke-AzureAdRequest -Method DELETE -Query "users/$ObjectId/authentication/phoneMethods/$methodId"
                break
            }
            "email" {
                Invoke-AzureAdRequest -Method DELETE -Query "users/$ObjectId/authentication/emailMethods/3ddfcfc8-9383-446f-83cc-3ab9be4be18f"
                break
            }
            "FIDO2" {
                Invoke-AzureAdRequest -Method DELETE -Query "users/$ObjectId/authentication/fido2Methods/$MethodId"
                break
            }
            "passwordlessMicrosoftAuthenticator" {
                Invoke-AzureAdRequest -Method DELETE -Query "users/$ObjectId/authentication/passwordlessMicrosoftAuthenticatorMethods/$MethodId"
                break
            }
            "MicrosoftAuthenticator" {
                Invoke-AzureAdRequest -Method DELETE -Query "users/$ObjectId/authentication/MicrosoftAuthenticatorMethods/$MethodId"
                break
            }
            "WindowsHelloForBusiness" {
                Invoke-AzureAdRequest -Method DELETE -Query "users/$ObjectId/authentication/windowsHelloForBusinessMethods/$MethodId"
                break
            }
            default {
                throw "Removing the $($PSCmdlet.ParameterSetName) method is not yet supported."
            }
        }
    }
}


function Update-AzureADUserAuthenticationMethod {
    <#
.SYNOPSIS
    Modifies an authentication method for the user. Manages SMS Sign In for mobile phone method.
.DESCRIPTION
    Modifies an authentication method for the user. Manages SMS Sign In for mobile phone method.
    Use to modify an existing authentication method for the user. To create a new method, use New-AzureADUserAuthenticationMethod.
.EXAMPLE
    PS C:\>Update-AzureADUserAuthenticationMethod user@contoso.com -Phone -PhoneNumber '+61412345679' -PhoneType mobile
    Modifies the existing mobile phone number for the user.
.EXAMPLE
    PS C:\>Update-AzureADUserAuthenticationMethod -Phone -UPN user1@contoso.com -EnableSmsSignIn
    Enables SMS sign-in for the existing mobile phone authentication method for the user.
.EXAMPLE
    PS C:\>Update-AzureADUserAuthenticationMethod user@contoso.com -Password -NewPassword "password"
    Sets "password" as a new password for the user. Doesn't return the operation result.
.EXAMPLE
    PS C:\>Update-AzureADUserAuthenticationMethod user@contoso.com -Password -NewPassword "password" -ReturnResult
    Sets "password" as a new password for the user and waits 5 seconds for the operation result.
.EXAMPLE
    PS C:\>Update-AzureADUserAuthenticationMethod clouduser@contoso.com -Password
    Sets new system generated password for the user. Not available for syncronised users.
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $True, ParameterSetName = 'pin')]
        [switch]
        $Pin,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [switch]
        $Oath,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'password')]
        [switch]
        $Password,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [switch]
        $SecurityQuestion,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'default')]
        [switch]
        $Default,
        
        [Alias('UserId', 'UPN', 'UserPrincipalName')]
        [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]
        $ObjectId,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'pin', Position = 2)]
        [string]
        $NewPin,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $SecretKey,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [int]
        $TimeIntervalInSeconds,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $SerialNumber,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $Manufacturer,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'oath')]
        [string]
        $Model,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [string]
        $PhoneNumber,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'phone')]
        [ValidateSet("mobile", "alternateMobile", "office")]
        [string]
        $PhoneType,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'email', Position = 2)]
        [string]
        $EmailAddress,
        
        [Parameter(Mandatory = $False, ParameterSetName = 'password')]
        [string]
        $NewPassword,
        
        [Parameter(Mandatory = $False, ParameterSetName = 'password')]
        [switch]
        $ReturnResult,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [string]
        $Question,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'securityQuestion')]
        [string]
        $Answer,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'default')]
        [string]
        $DefaultMethod,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'enableSmsSignIn')]
        [switch]
        $EnableSmsSignIn,
        
        [Parameter(Mandatory = $True, ParameterSetName = 'disableSmsSignIn')]
        [switch]
        $DisableSmsSignIn
    )
    
    begin {
        Assert-GraphConnection -Cmdlet $PSCmdlet
    }
    process {
        switch ($PSCmdlet.ParameterSetName) {
            "phone" {
                $methodId = switch ($PhoneType) {
                    'alternateMobile' { 'b6332ec1-7057-4abe-9331-3d72feddfe41' }
                    'mobile' { '3179e48a-750b-4051-897c-87b9720928f7' }
                    'office' { 'e37fc753-ff3b-4958-9484-eaa9425c82bc' }
                }
                
                $postParams = @{
                    phoneNumber = $PhoneNumber
                    phoneType   = $PhoneType
                }
                $json = $postparams | ConvertTo-Json -Depth 99 -Compress
                Invoke-AzureAdRequest -Method PUT -Query "users/$ObjectId/authentication/phoneMethods/$methodId" -Body $json
                break
            }
            "email" {
                $postParams = @{
                    emailAddress = $EmailAddress
                }
                $json = $postparams | ConvertTo-Json -Depth 99 -Compress
                Invoke-AzureAdRequest -Method PUT -Query "users/$ObjectId/authentication/emailMethods/3ddfcfc8-9383-446f-83cc-3ab9be4be18f" -Body $json
                break
            }
            "password" {
                $parameters = @{
                    Method = 'POST'
                    Query = "users/$ObjectId/authentication/passwordMethods/28c10230-6103-485e-b985-444c60001490/resetPassword"
                }
                if ($NewPassword) {
                    $parameters.Body = @{
                        newPassword = $NewPassword
                    } | ConvertTo-Json -Depth 99 -Compress
                }
                $response = Invoke-AzureAdRequest @parameters -Raw
                if (-not $ReturnResult) { return $response }
                
                # TODO: If RAW, use invoke-webrequest to get response headers.
                # Check password reset result
                if ($response.StatusCode -eq "202") {
                    Write-Host "Waiting for a response..."
                    Start-Sleep -Seconds 5
                    (Invoke-WebRequest -UseBasicParsing -Headers (Get-Token | ConvertTo-AuthHeader) -Uri $response.Headers.Location -Method Get).Content
                }
                
                return $response.Content
            }
            "enableSmsSignIn" {
                Invoke-AzureAdRequest -Method PUT -Query "users/$ObjectId/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7/enableSmsSignIn" -Body $json
                break
            }
            "disableSmsSignIn" {
                Invoke-AzureAdRequest -Method PUT -Query "users/$ObjectId/authentication/phoneMethods/3179e48a-750b-4051-897c-87b9720928f7/disableSmsSignIn" -Body $json
                break
            }
            default {
                throw "Setting the $($PSCmdlet.ParameterSetName) method is not yet supported."
            }
        }
    }
}


# Graph Token to use for queries
$script:msgraphToken = $null

# The API base URI to use for requests
$script:baseUri = 'https://graph.microsoft.com/beta/'

# Certificate used for authenticating inapplication authentication workflows
$script:clientCertificate = $null

# Connection Metadata
$script:tenantID = ''
$script:clientID = ''
$script:redirectUri = 'urn:ietf:wg:oauth:2.0:oob'
#endregion Load compiled code
# SIG # Begin signature block
# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBuNJj4/ZuNldDF
# x307fPah37VDc42g9bVE4jRMl2l5hKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX
# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB
# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH
# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d
# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ
# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV
# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy
# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K
# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV
# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr
# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx
# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe
# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g
# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf
# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI
# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5
# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea
# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg6A2SZ6GX
# l8YWuWFuTjwacEGAQ570VarfLLAtYhUVyJUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQAmK8GjqpGR3R97xVi/azFo/o2cOdVuJOKyVCVHhqY3
# 3vxVKrbmXHxSe6Jgmqs6WH8sTFGHyfA29OUWbHOMcf7e5fl0glwRZKChNSlvx9K5
# 946AuzYforMZt8l0u6HG5rzYT8gSC+FR699Bd6cdXmRDBV79oxi6DMZKlidnO/OA
# uHvoLqfluZDFQ4E0aqJwre5DO92p2+1lqutZQ74fHmN3WtSSxqPRDNpzwQ8SZUqe
# hdHjklZTaX+eD+3Lu+buT0IxHr/gEm46C++G99s0Z7T2Tf9D3noVz5q1o88i9IKt
# lzxiydcjnbwAwQhTaFWVXuPtwb9DAbdlotR1NUsxKDCpoYIS8DCCEuwGCisGAQQB
# gjcDAwExghLcMIIS2AYJKoZIhvcNAQcCoIISyTCCEsUCAQMxDzANBglghkgBZQME
# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIDaSrQsVDvRBpL8RkwhyLWVArxMtAOJKkQ4BbPhW
# oHaiAgZf25gPgJ4YEjIwMjEwMTE1MDAxOTMxLjIzWjAEgAIB9KCB1KSB0TCBzjEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj
# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU
# U1MgRVNOOkQ5REUtRTM5QS00M0ZFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T
# dGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAEtLk1BymNlM6AAAAAA
# AS0wDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# HhcNMTkxMjE5MDExNTA0WhcNMjEwMzE3MDExNTA0WjCBzjELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh
# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQ5REUt
# RTM5QS00M0ZFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqWGN9HVJOphFNLezSLiM
# hxIxX4bg3ShiDCVAr6mXgNUwWavWJNkcUuPdO3tXoX4z8FfHSH2LS67XIGeoKo06
# S4kRFNtWczi7cm9HHOxB8KMF+oP8I3Cgw16SjKUgHPE/nRKSOnWk+ydAEodoI/y2
# C69hXOhNDnirZWlgQ//45hNB4Q+pYWLxaCC+xyS56txQaSFhYzwNX/glTs3+tsOu
# 1qZht7wu2RWJNBhuKBRIICiv0OG0Bm+rwDQDvdcUfZ1/bAOUu0CcoJyxW9dKZnfl
# sCqd43i4RBXLw1B1F4YjW0jpTGgLteeMa8rgxwN0qFq80nsMCdI/n4b8NOR1YP6U
# 3wIDAQABo4IBGzCCARcwHQYDVR0OBBYEFNlsZHxCASH4Tg6K/y9DvjTynYbNMB8G
# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG
# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp
# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH
# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh
# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB
# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAH1rA2T3Tqt5wG6g7sTOrsHxQa70SgVe
# IvxpzRtuxkMFj3P/enxq1VlELEc3jdrdYZsIxmgpjFIEfpQKRwNzBch80oRtUzOc
# XWaOlAQZuqFH6s82oddmi1JX6+fzkDGZ58Azdtwu35Y0GkS45lniQ9lVwW0yjhjJ
# CPGg1E+LCan4HQeSFCz+X9UiDRcljqTkVEoT8kURxVjjbno12pRn7eOi7dvg4CS0
# Ta6uvhXpVHDV9986XFlvwzK8Tmaq9NUk5K1XEK6G6qDWsh7yVrWBnmKK4jJZ5+04
# 18zDSdJlyEYuKAN4ifDpd/DCAWcLlCXw9t/aM7EJSW4BhvPKQj4ycGIwggZxMIIE
# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v
# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y
# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN
# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU
# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE
# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50
# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd
# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR
# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB
# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8
# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB
# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO
# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w
# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr
# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB
# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF
# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt
# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh
# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7
# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR
# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9
# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8
# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+
# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh
# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy
# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo
# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx
# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341
# Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w
# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkQ5
# REUtRTM5QS00M0ZFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNloiMKAQEwBwYFKw4DAhoDFQCfzl/Hfod7sXS+CbJSXPbDzaXQsaCBgzCBgKR+
# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT
# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA
# 46r/bDAiGA8yMDIxMDExNDIxMzU0MFoYDzIwMjEwMTE1MjEzNTQwWjB3MD0GCisG
# AQQBhFkKBAExLzAtMAoCBQDjqv9sAgEAMAoCAQACAhOXAgH/MAcCAQACAhEeMAoC
# BQDjrFDsAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA
# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAapEmDNvHKFoz9GoP
# IDX+ENGc3fybhb7HU8fbiyg2rHjnW9RPy3xgRMGD8Yr3u10luc7vxAfzTuTt2RuR
# Tk1OnPD3X5aAPsCIPyni/bN5YTBXrCt1Y1VUnRjnCzZOrFpnNA+MURQcZuCIp0at
# H/+Hrj+keUC9XHptrhgzFoOCCTkxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAAS0uTUHKY2UzoAAAAAABLTANBglghkgBZQME
# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ
# BDEiBCB8nFN0EE7L6SKQE3XzKftnL4x9smeTNSI9/4gTmxrwITCB+gYLKoZIhvcN
# AQkQAi8xgeowgecwgeQwgb0EII7xWnJyfSAHj+KVEA88NtL4KZuqP+4LTXWahzmh
# 4YPBMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEt
# Lk1BymNlM6AAAAAAAS0wIgQg5y+28yXnVf9L9ulfLzY0IRx45rxPj5tzoc0Ug2ZX
# 17cwDQYJKoZIhvcNAQELBQAEggEAb2VlL745I8KU/wKwaS0zs8FmCOgDhbhaJp5R
# 70niUeDsz+58eb8UmPX0nQQTm2ugIwI33rkCYkPJcku5OTKJVR4CGaAWYukFZ9/d
# UOvPhIqJJkD2JIlVnnftcfVSWHslUYFvm60vm1cdqnLIgcWBxAqvzA60aKHLXIrA
# xEhiVCBqD7U2E76X5HT/LwoSXYj1x3a+lHB7G9Q8yakSpyolsbOcTSCHdOLx0WTR
# P37wpExh9ZmmNbrGNNRqH7jsowfQq/rPJRD7YMTrXtwfnOWRsKbqqC4QpJl6WGye
# Zaycd/lPPX12Lee5ebBGYJTCJudSKyTvhL4d8WCrsXX1sHocTQ==
# SIG # End signature block