Microsoft.PowerApps.AuthModule.psm1
$local:ErrorActionPreference = "Stop" Add-Type -Path (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "Microsoft.Identity.Client.dll") function Get-JwtTokenClaims { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string]$JwtToken ) $tokenSplit = $JwtToken.Split(".") $claimsSegment = $tokenSplit[1].Replace(" ", "+").Replace("-", "+").Replace('_', '/'); $mod = $claimsSegment.Length % 4 if ($mod -gt 0) { $paddingCount = 4 - $mod; for ($i = 0; $i -lt $paddingCount; $i++) { $claimsSegment += "=" } } $decodedClaimsSegment = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($claimsSegment)) return ConvertFrom-Json $decodedClaimsSegment } function Get-DefaultAudienceForEndPoint { [CmdletBinding()] Param( [string] $Endpoint ) $audienceMapping = @{ "prod" = "https://service.powerapps.com/"; "preview" = "https://service.powerapps.com/"; "tip1"= "https://service.powerapps.com/"; "tip2"= "https://service.powerapps.com/"; "usgov"= "https://gov.service.powerapps.us/"; "usgovhigh"= "https://high.service.powerapps.us/"; "dod" = "https://service.apps.appsplatform.us/"; "china" = "https://service.powerapps.cn/"; } if ($null -ne $audienceMapping[$Endpoint]) { return $audienceMapping[$Endpoint]; } Write-Verbose "Unknown endpoint $Endpoint. Using https://service.powerapps.com/ as a default"; return "https://service.powerapps.com/"; } function RemoveHttpHttps { param ( [string] $inputString ) foreach($removalTarget in @('http://','https://')) { if($inputString.StartsWith($removalTarget)) { $inputString = $inputString.Remove(0, $removalTarget.Length) } } return $inputString } function Await-Task { param ( [Parameter(ValueFromPipeline=$true, Mandatory=$true)] $task ) process { while (-not $task.AsyncWaitHandle.WaitOne(200)) { } $task.GetAwaiter().GetResult() } } function Add-PowerAppsAccount { <# .SYNOPSIS Add PowerApps account. .DESCRIPTION The Add-PowerAppsAccount cmdlet logins the user or application account and save login information to cache. Use 'Get-Help Add-PowerAppsAccount -Detailed' for descriptions of the parameters and example usages. .PARAMETER Audience The service audience which is used for login. .PARAMETER Endpoint The serivce endpoint which to call. The value can be "prod", "preview", "tip1", "tip2", "usgov", "dod", "usgovhigh", or "china". Can't be used if providing endpoint overrides .PARAMETER Username The user name used for login. .PARAMETER Password The password for the user. .PARAMETER TenantID The tenant Id of the user or application. .PARAMETER CertificateThumbprint The certificate thumbprint of the application. .PARAMETER ClientSecret The client secret of the application. .PARAMETER ApplicationId The application Id. .PARAMETER AudienceOverride Must be provided if giving endpoint overrides; this audience will be used for all subsequent auth calls, ignoring normally derived audiences .PARAMETER AuthBaseUriOverride Must be provided if giving endpoint overrides .PARAMETER FlowEndpointOverride Must be provided if giving endpoint overrides .PARAMETER PowerAppsEndpointOverride Must be provided if giving endpoint overrides .PARAMETER BapEndpointOverride Must be provided if giving endpoint overrides .PARAMETER GraphEndpointOverride Must be provided if giving endpoint overrides .PARAMETER CdsOneEndpointOverride Can be provided if giving endpoint overrides .PARAMETER PvaEndpointOverride Can be provided if giving endpoint overrides .EXAMPLE Add-PowerAppsAccount Login to "prod" endpoint. .EXAMPLE Add-PowerAppsAccount -Endpoint "prod" -Username "username@test.onmicrosoft.com" -Password "password" Login to "prod" for user "username@test.onmicrosoft.com" by using password "password" .EXAMPLE Add-PowerAppsAccount ` -Endpoint "tip1" ` -TenantID 1a1fbe33-1ff4-45b2-90e8-4628a5112345 ` -ClientSecret ABCDE]NO_8:YDLp0J4o-:?=K9cmipuF@ ` -ApplicationId abcdebd6-e62c-4f68-ab74-b046579473ad Login to "tip1" for application abcdebd6-e62c-4f68-ab74-b046579473ad in tenant 1a1fbe33-1ff4-45b2-90e8-4628a5112345 by using client secret. .EXAMPLE Add-PowerAppsAccount ` -Endpoint "tip1" ` -TenantID 1a1fbe33-1ff4-45b2-90e8-4628a5112345 ` -CertificateThumbprint 12345137C1B2D4FED804DB353D9A8A18465C8027 ` -ApplicationId 08627eb8-8eba-4a9a-8c49-548266012345 Login to "tip1" for application 08627eb8-8eba-4a9a-8c49-548266012345 in tenant 1a1fbe33-1ff4-45b2-90e8-4628a5112345 by using certificate. .EXAMPLE Add-PowerAppsAccount ` -AudienceOverride: "https://service.powerapps.com/" ` -AuthBaseUriOverride: "https://login.microsoftonline.com" ` -BapEndpointOverride: "api.bap.microsoft.com" ` -CdsOneEndpointOverride: "api.cds.microsoft.com" ` -FlowEndpointOverride: "api.flow.microsoft.com" ` -GraphEndpointOverride: "graph.windows.net" ` -PowerAppsEndpointOverride: "api.powerapps.com" ` -PvaEndpointOverride: "powerva.microsoft.com" Login to an environment with the provided endpoints (examples above are for 'PROD') .EXAMPLE $Inputs | Add-PowerAppsAccount Login to an environment with the endpionts stored in a PS Custom Object variable; where its content is defined as: $Inputs = [pscustomobject]@{ ` "AudienceOverride" = "https://service.powerapps.com/"; ` "AuthBaseUriOverride" = "https://login.microsoftonline.com"; ` "BapEndpointOverride" = "api.bap.microsoft.com"; ` "CdsOneEndpointOverride" = "api.cds.microsoft.com"; ` "FlowEndpointOverride" = "api.flow.microsoft.com"; ` "GraphEndpointOverride" = "graph.windows.net"; ` "PowerAppsEndpointOverride" = "api.powerapps.com"; ` "PvaEndpointOverride" = "powerva.microsoft.com" } .EXAMPLE Get-Content -Raw ".\OverrideEndpoints.json" | ConvertFrom-Json | Add-PowerAppsAccount Login to an environment with the endpoints stored in 'OverrideEndpoints.json'; where its content is of the form: { "AudienceOverride": "https://service.powerapps.com/", "AuthBaseUriOverride": "https://login.microsoftonline.com", "BapEndpointOverride": "api.bap.microsoft.com", "CdsOneEndpointOverride": "api.cds.microsoft.com", "FlowEndpointOverride": "api.flow.microsoft.com", "GraphEndpointOverride": "graph.windows.net", "PowerAppsEndpointOverride": "api.powerapps.com", "PvaEndpointOverride": "powerva.microsoft.com" } #> [CmdletBinding()] param ( [Parameter(Mandatory = $false, ParameterSetName="DerivedEndpoints")] [string] $Audience = "https://service.powerapps.com/", [Parameter(Mandatory = $false, ParameterSetName="DerivedEndpoints")] [ValidateSet("prod","preview","tip1", "tip2", "usgov", "usgovhigh", "dod", "china")] [string]$Endpoint = "prod", [string]$Username = $null, [SecureString]$Password = $null, [string]$TenantID = $null, [string]$CertificateThumbprint = $null, [string]$ClientSecret = $null, [string]$ApplicationId = "1950a258-227b-4e31-a9cf-717495945fc2", [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $AudienceOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $AuthBaseUriOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $FlowEndpointOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $PowerAppsEndpointOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $BapEndpointOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $GraphEndpointOverride, [Parameter(Mandatory = $false,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $CdsOneEndpointOverride = "unsupported", [Parameter(Mandatory = $false,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $PvaEndpointOverride = "unsupported" ) if ($Audience -eq "https://service.powerapps.com/" -and -not $PSBoundParameters.ContainsKey('AudienceOverride')) { # It's the default audience - we should remap based on endpoint as needed $Audience = Get-DefaultAudienceForEndPoint($Endpoint) $PSBoundParameters['Audience'] = $Audience } $global:currentSession = $null Add-PowerAppsAccountInternal @PSBoundParameters } function Add-PowerAppsAccountInternal { param ( [Parameter(Mandatory = $false, ParameterSetName="DerivedEndpoints")] [string] $Audience = "https://service.powerapps.com/", [Parameter(Mandatory = $false, ParameterSetName="DerivedEndpoints")] [ValidateSet("prod","preview","tip1", "tip2", "usgov", "usgovhigh", "dod", "china")] [string]$Endpoint = "prod", [string]$Username = $null, [SecureString]$Password = $null, [string]$TenantID = $null, [string]$CertificateThumbprint = $null, [string]$ClientSecret = $null, [string]$ApplicationId = "1950a258-227b-4e31-a9cf-717495945fc2", [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $AudienceOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $AuthBaseUriOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $FlowEndpointOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $PowerAppsEndpointOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $BapEndpointOverride, [Parameter(Mandatory = $true,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $GraphEndpointOverride, [Parameter(Mandatory = $false,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $CdsOneEndpointOverride = "unsupported", [Parameter(Mandatory = $false,ParameterSetName="ProvidedEndpoints", ValueFromPipelineByPropertyName)] [string] $PvaEndpointOverride = "unsupported" ) $InputEndpoint = $Endpoint #Enforce format requirements for endpoint overrides and force their usage if ($PSBoundParameters.ContainsKey('AudienceOverride') -and $PSBoundParameters.ContainsKey('AuthBaseUriOverride') -and $PSBoundParameters.ContainsKey('FlowEndpointOverride') -and $PSBoundParameters.ContainsKey('PowerAppsEndpointOverride') -and $PSBoundParameters.ContainsKey('BapEndpointOverride') -and $PSBoundParameters.ContainsKey('GraphEndpointOverride')) { $InputEndpoint = 'override' Write-Verbose "Overrides were passed and will be used in place of any derived audiences or endpoints; run this command again to change configured overrides" #Ensure exactly 1 trailing '/' $AudienceOverride = $AudienceOverride.TrimEnd('/') + '/'; $Audience = $AudienceOverride #Ensure no trailing '/' $AuthBaseUriOverride = $AuthBaseUriOverride.TrimEnd('/'); #Ensure no leading 'http://' or 'https://' and no trailing '/' $FlowEndpointOverride = RemoveHttpHttps $FlowEndpointOverride.TrimEnd('/') $PowerAppsEndpointOverride = RemoveHttpHttps $PowerAppsEndpointOverride.TrimEnd('/') $BapEndpointOverride = RemoveHttpHttps $BapEndpointOverride.TrimEnd('/') $GraphEndpointOverride = RemoveHttpHttps $GraphEndpointOverride.TrimEnd('/') $CdsOneEndpointOverride = RemoveHttpHttps $CdsOneEndpointOverride.TrimEnd('/') $PvaEndpointOverride = RemoveHttpHttps $PvaEndpointOverride.TrimEnd('/') } elseif ($global:currentSession.audienceOverride -ne $null -and $global:currentSession.audienceOverride -ne '') { Write-Debug "Provided Audience '$Audience' is being replaced with previously provided override value '$($global:currentSession.audienceOverride)'" $Audience = $global:currentSession.audienceOverride } [string[]]$scopes = "$Audience/.default" if ([string]::IsNullOrWhiteSpace($ApplicationId)) { $ApplicationId = "1950a258-227b-4e31-a9cf-717495945fc2" } Write-Debug "Using appId, $ApplicationId" [Microsoft.Identity.Client.IClientApplicationBase]$clientBase = $null [Microsoft.Identity.Client.AuthenticationResult]$authResult = $null if ($global:currentSession.loggedIn -eq $true -and $global:currentSession.recursed -ne $true) { Write-Debug "Already logged in, checking for token for resource $Audience" $authResult = $null if ($global:currentSession.resourceTokens[$Audience] -ne $null) { if ($global:currentSession.resourceTokens[$Audience].accessToken -ne $null -and ` $global:currentSession.resourceTokens[$Audience].expiresOn -ne $null -and ` $global:currentSession.resourceTokens[$Audience].expiresOn -gt (Get-Date)) { Write-Debug "Token found and value, returning for audience $Audience" return } else { # Already logged in with an account, silently asking for a token from MSAL which should refresh try { Write-Debug "Already logged in, silently requesting token from MSAL" $authResult = $global:currentSession.msalClientApp.AcquireTokenSilent($scopes, $global:currentSession.msalAccount).ExecuteAsync() | Await-Task } catch [Microsoft.Identity.Client.MsalUiRequiredException] { Write-Debug ('{0}: {1}' -f $_.Exception.GetType().Name, $_.Exception.Message) } } } if ($authResult -eq $null) { Write-Debug "No token found, reseting audience and recursing: $Audience" # Reset the current audience values and call Add-PowerAppsAccount again $global:currentSession.resourceTokens[$Audience] = $null $global:currentSession.recursed = $true $PSBoundParameters['Audience'] = $Audience # the override endpoint is set automatically when required params are passed if ($global:currentSession.endpoint -ne 'override') { $PSBoundParameters['Endpoint'] = $global:currentSession.endpoint } $PSBoundParameters['Username'] = $global:currentSession.username $PSBoundParameters['Password'] = $global:currentSession.password $PSBoundParameters['TenantID'] = $global:currentSession.InitialTenantId $PSBoundParameters['CertificateThumbprint'] = $global:currentSession.certificateThumbprint $PSBoundParameters['ClientSecret'] = $global:currentSession.clientSecret $PSBoundParameters['ApplicationId'] = $global:currentSession.applicationId Add-PowerAppsAccountInternal @PSBoundParameters $global:currentSession.recursed = $false # Afer recursing we can early return return } } else { [string] $jwtTokenForClaims = $null if ($InputEndpoint -ne "override") { [Microsoft.Identity.Client.AzureCloudInstance] $authBaseUri = switch ($InputEndpoint) { "usgov" { [Microsoft.Identity.Client.AzureCloudInstance]::AzurePublic } "usgovhigh" { [Microsoft.Identity.Client.AzureCloudInstance]::AzureUsGovernment } "dod" { [Microsoft.Identity.Client.AzureCloudInstance]::AzureUsGovernment } "china" { [Microsoft.Identity.Client.AzureCloudInstance]::AzureChina } default { [Microsoft.Identity.Client.AzureCloudInstance]::AzurePublic } }; } else { [string] $authBaseUri = $AuthBaseUriOverride } if ($Username -ne $null -and $Password -ne $null) { $authUriWithAudience = $AuthBaseUriOverride + "/organizations/" [Microsoft.Identity.Client.AadAuthorityAudience] $aadAuthAudience = [Microsoft.Identity.Client.AadAuthorityAudience]::AzureAdMultipleOrgs } else { $authUriWithAudience = $AuthBaseUriOverride + "/common/" [Microsoft.Identity.Client.AadAuthorityAudience] $aadAuthAudience = [Microsoft.Identity.Client.AadAuthorityAudience]::AzureAdAndPersonalMicrosoftAccount } Write-Debug "Using $Audience : $ApplicationId : $aadAuthAudience : $authUriWithAudience" if (![string]::IsNullOrWhiteSpace($TenantID) -and ` (![string]::IsNullOrWhiteSpace($ClientSecret) -or ![string]::IsNullOrWhiteSpace($CertificateThumbprint))) { $options = New-Object -TypeName Microsoft.Identity.Client.ConfidentialClientApplicationOptions $options.ClientId = $ApplicationId $options.TenantId = $TenantID [Microsoft.Identity.Client.IConfidentialClientApplication ]$ConfidentialClientApplication = $null if (![string]::IsNullOrWhiteSpace($CertificateThumbprint)) { Write-Debug "Using certificate for token acquisition" $clientCertificate = Get-Item -Path Cert:\CurrentUser\My\$CertificateThumbprint $ConfidentialClientApplication = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ApplicationId).WithCertificate($clientCertificate).WithAuthority($authBaseUri, $TenantID, $true).Build() } else { Write-Debug "Using clientSecret for token acquisition" $ConfidentialClientApplication = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create($ApplicationId).WithClientSecret($ClientSecret).WithAuthority($authBaseUri, $TenantID, $true).Build() } $authResult = $ConfidentialClientApplication.AcquireTokenForClient($scopes).ExecuteAsync() | Await-Task $clientBase = $ConfidentialClientApplication } else { if ($InputEndpoint -eq "override") { $PublicClientApplication = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($ApplicationId).WithAuthority($authUriWithAudience, $true).WithDefaultRedirectUri().Build() } else { $PublicClientApplication = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($ApplicationId).WithAuthority($authBaseUri, $aadAuthAudience, $true).WithDefaultRedirectUri().Build() } if ($Username -ne $null -and $Password -ne $null) { Write-Debug "Using username, password" $authResult = $PublicClientApplication.AcquireTokenByUsernamePassword($scopes, $UserName, $Password).ExecuteAsync() | Await-Task } else { Write-Debug "Using interactive login" $authResult = $PublicClientApplication.AcquireTokenInteractive($scopes).ExecuteAsync() | Await-Task } $clientBase = $PublicClientApplication } } if ($authResult -ne $null) { if (![string]::IsNullOrWhiteSpace($authResult.IdToken)) { $jwtTokenForClaims = $authResult.IdToken } else { $jwtTokenForClaims = $authResult.AccessToken } $claims = Get-JwtTokenClaims -JwtToken $jwtTokenForClaims if ($global:currentSession.loggedIn -eq $true) { Write-Debug "Adding new audience '$Audience' to resourceToken map. Expires $($authResult.ExpiresOn)" # addition of a new token for a new audience $global:currentSession.resourceTokens[$Audience] = @{ accessToken = $authResult.AccessToken; expiresOn = $authResult.ExpiresOn; }; if ($AudienceOverride -ne $null -and $AudienceOverride -ne '') { Write-Debug "A new audience override '$AudienceOverride' was provided and is in use, instead of the previous audience override '$($global:currentSession.audienceOverride)', for all token aquisitions" $global:currentSession.audienceOverride = $AudienceOverride } } else { Write-Debug "Adding first audience '$Audience' to resourceToken map. Expires $($authResult.ExpiresOn)" $global:currentSession = @{ audienceOverride = $AudienceOverride; loggedIn = $true; recursed = $false; endpoint = $InputEndpoint; msalClientApp = $clientBase; msalAccount = $authResult.Account; upn = $claims.upn; InitialTenantId = $TenantID; tenantId = $claims.tid; userId = $claims.oid; applicationId = $ApplicationId; username = $Username; password = $Password; certificateThumbprint = $CertificateThumbprint; clientSecret = $ClientSecret; resourceTokens = @{ $Audience = @{ accessToken = $authResult.AccessToken; expiresOn = $authResult.ExpiresOn; } }; selectedEnvironment = "~default"; flowEndpoint = switch ($InputEndpoint) { "override" { $FlowEndpointOverride } "prod" { "api.flow.microsoft.com" } "usgov" { "gov.api.flow.microsoft.us" } "usgovhigh" { "high.api.flow.microsoft.us" } "dod" { "api.flow.appsplatform.us" } "china" { "api.powerautomate.cn" } "preview" { "preview.api.flow.microsoft.com" } "tip1" { "tip1.api.flow.microsoft.com"} "tip2" { "tip2.api.flow.microsoft.com" } default { throw "Unsupported endpoint '$InputEndpoint'"} }; powerAppsEndpoint = switch ($InputEndpoint) { "override" { $PowerAppsEndpointOverride } "prod" { "api.powerapps.com" } "usgov" { "gov.api.powerapps.us" } "usgovhigh" { "high.api.powerapps.us" } "dod" { "api.apps.appsplatform.us" } "china" { "api.powerapps.cn" } "preview" { "preview.api.powerapps.com" } "tip1" { "tip1.api.powerapps.com"} "tip2" { "tip2.api.powerapps.com" } default { throw "Unsupported endpoint '$InputEndpoint'"} }; bapEndpoint = switch ($InputEndpoint) { "override" { $BapEndpointOverride } "prod" { "api.bap.microsoft.com" } "usgov" { "gov.api.bap.microsoft.us" } "usgovhigh" { "high.api.bap.microsoft.us" } "dod" { "api.bap.appsplatform.us" } "china" { "api.bap.partner.microsoftonline.cn" } "preview" { "preview.api.bap.microsoft.com" } "tip1" { "tip1.api.bap.microsoft.com"} "tip2" { "tip2.api.bap.microsoft.com" } default { throw "Unsupported endpoint '$InputEndpoint'"} }; graphEndpoint = switch ($InputEndpoint) { "override" { $GraphEndpointOverride } "prod" { "graph.windows.net" } "usgov" { "graph.windows.net" } "usgovhigh" { "graph.windows.net" } "dod" { "graph.windows.net" } "china" { "graph.windows.net" } "preview" { "graph.windows.net" } "tip1" { "graph.windows.net"} "tip2" { "graph.windows.net" } default { throw "Unsupported endpoint '$InputEndpoint'"} }; cdsOneEndpoint = switch ($InputEndpoint) { "override" { $CdsOneEndpointOverride } "prod" { "api.cds.microsoft.com" } "usgov" { "gov.api.cds.microsoft.us" } "usgovhigh" { "high.api.cds.microsoft.us" } "dod" { "dod.gov.api.cds.microsoft.us" } "china" { "unsupported" } "preview" { "preview.api.cds.microsoft.com" } "tip1" { "tip1.api.cds.microsoft.com"} "tip2" { "tip2.api.cds.microsoft.com" } default { throw "Unsupported endpoint '$InputEndpoint'"} }; pvaEndpoint = switch ($InputEndpoint) { "override" { $PvaEndpointOverride } "prod" { "powerva.microsoft.com" } "usgov" { "gcc.api.powerva.microsoft.us" } "usgovhigh" { "high.api.powerva.microsoft.us" } "dod" { "powerva.api.appsplatform.us" } "china" { "unsupported" } "preview" { "bots.sdf.customercareintelligence.net" } "tip1" { "bots.ppe.customercareintelligence.net"} "tip2" { "bots.int.customercareintelligence.net"} default { throw "Unsupported endpoint '$InputEndpoint'"} }; }; } } } function Test-PowerAppsAccount { <# .SYNOPSIS Test PowerApps account. .DESCRIPTION The Test-PowerAppsAccount cmdlet checks cache and calls Add-PowerAppsAccount if user account is not in cache. Use Get-Help Test-PowerAppsAccount -Examples for more detail. .EXAMPLE Test-PowerAppsAccount Check if user account is cached. #> [CmdletBinding()] param ( ) if (-not $global:currentSession -or $global:currentSession.loggedIn -ne $true) { Add-PowerAppsAccountInternal } } function Remove-PowerAppsAccount { <# .SYNOPSIS Remove PowerApps account. .DESCRIPTION The Remove-PowerAppsAccount cmdlet removes the user or application login information from cache. Use Get-Help Remove-PowerAppsAccount -Examples for more detail. .EXAMPLE Remove-PowerAppsAccount Removes the login information from cache. #> [CmdletBinding()] param ( ) if ($global:currentSession -ne $null -and $global:currentSession.upn -ne $null) { Write-Verbose "Logging out $($global:currentSession.upn)" } else { Write-Verbose "No user logged in" } $global:currentSession = @{ loggedIn = $false; }; } function Get-JwtToken { <# .SYNOPSIS Get user login token. .DESCRIPTION The Get-JwtToken cmdlet get the user or application login information from cache. It will call Add-PowerAppsAccount if login token expired. Use Get-Help Get-JwtToken -Examples for more detail. .EXAMPLE Get-JwtToken "https://service.powerapps.com/" Get login token for PowerApps "prod". #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $Audience ) if ($global:currentSession -eq $null) { $global:currentSession = @{ loggedIn = $false; }; } elseif($global:currentSession.audienceOverride -ne $null -and $global:currentSession.audienceOverride -ne '') { Write-Verbose "The provided audience '$Audience' will be ignored in place of the AudienceOverride '$($global:currentSession.audienceOverride)' provided in the most recent call to Add-PowerAppsAccount" $Audience = $global:currentSession.audienceOverride } Add-PowerAppsAccountInternal -Audience $Audience return $global:currentSession.resourceTokens[$Audience].accessToken; } function Invoke-OAuthDialog { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $ConsentLinkUri ) Add-Type -AssemblyName System.Windows.Forms $form = New-Object -TypeName System.Windows.Forms.Form -Property @{ Width=440; Height=640 } $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{ Width=420; Height=600; Url=$ConsentLinkUri } $DocComp = { $Global:uri = $web.Url.AbsoluteUri if ($Global:uri -match "error=[^&]*|code=[^&]*") { $form.Close() } } $web.ScriptErrorsSuppressed = $true $web.Add_DocumentCompleted($DocComp) $form.Controls.Add($web) $form.Add_Shown({$form.Activate()}) $form.ShowDialog() | Out-Null $queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query) $output = @{} foreach($key in $queryOutput.Keys) { $output["$key"] = $queryOutput[$key] } return $output } function Get-TenantDetailsFromGraph { <# .SYNOPSIS Get my organization tenant details from graph. .DESCRIPTION The Get-TenantDetailsFromGraph function calls graph and gets my organization tenant details. Use Get-Help Get-TenantDetailsFromGraph -Examples for more detail. .PARAMETER GraphApiVersion Graph version to call. The default version is "1.6". .EXAMPLE Get-TenantDetailsFromGraph Get my organization tenant details from graph by calling graph service in version 1.6. #> param ( [string]$GraphApiVersion = "1.6" ) process { $TenantIdentifier = "myorganization" $route = "https://{graphEndpoint}/{tenantIdentifier}/tenantDetails`?api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{tenantIdentifier}" -Value $TenantIdentifier ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $graphResponse = InvokeApi -Method GET -Route $route if ($graphResponse.value -ne $null) { CreateTenantObject -TenantObj $graphResponse.value } else { return $graphResponse } } } #Returns users or groups from Graph #wrapper on top of https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/users-operations & https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/groups-operations function Get-UsersOrGroupsFromGraph( ) { <# .SYNOPSIS Returns users or groups from Graph. .DESCRIPTION The Get-UsersOrGroupsFromGraph function calls graph and gets users or groups from Graph. Use Get-Help Get-UsersOrGroupsFromGraph -Examples for more detail. .PARAMETER ObjectId User objec Id. .PARAMETER SearchString Search string. .PARAMETER GraphApiVersion Graph version to call. The default version is "1.6". .EXAMPLE Get-UsersOrGroupsFromGraph -ObjectId "12345ba9-805f-43f8-98f7-34fa34aa51a7" Get user with user object Id "12345ba9-805f-43f8-98f7-34fa34aa51a7" from graph by calling graph service in version 1.6. .EXAMPLE Get-UsersOrGroupsFromGraph -SearchString "gfd" Get users who's UserPrincipalName starting with "gfd" from graph by calling graph service in version 1.6. #> [CmdletBinding(DefaultParameterSetName="Id")] param ( [Parameter(Mandatory = $true, ParameterSetName = "Id")] [string]$ObjectId, [Parameter(Mandatory = $true, ParameterSetName = "Search")] [string]$SearchString, [Parameter(Mandatory = $false, ParameterSetName = "Search")] [Parameter(Mandatory = $false, ParameterSetName = "Id")] [string]$GraphApiVersion = "1.6" ) Process { if (-not [string]::IsNullOrWhiteSpace($ObjectId)) { $userGraphUri = "https://graph.windows.net/myorganization/users/{userId}`?&api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{userId}" -Value $ObjectId ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $userGraphResponse = InvokeApi -Route $userGraphUri -Method GET If($userGraphResponse.StatusCode -eq $null) { CreateUserObject -UserObj $userGraphResponse } $groupsGraphUri = "https://graph.windows.net/myorganization/groups/{groupId}`?api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{groupId}" -Value $ObjectId ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $groupGraphResponse = InvokeApi -Route $groupsGraphUri -Method GET If($groupGraphResponse.StatusCode -eq $null) { CreateGroupObject -GroupObj $groupGraphResponse } } else { $userFilter = "startswith(userPrincipalName,'$SearchString') or startswith(displayName,'$SearchString')" $userGraphUri = "https://graph.windows.net/myorganization/users`?`$filter={filter}&api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{filter}" -Value $userFilter ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $userGraphResponse = InvokeApi -Route $userGraphUri -Method GET foreach($user in $userGraphResponse.value) { CreateUserObject -UserObj $user } $groupFilter = "startswith(displayName,'$SearchString')" $groupsGraphUri = "https://graph.windows.net/myorganization/groups`?`$filter={filter}&api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{filter}" -Value $groupFilter ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $groupsGraphResponse = InvokeApi -Route $groupsGraphUri -Method GET foreach($group in $groupsGraphResponse.value) { CreateGroupObject -GroupObj $group } } } } function CreateUserObject { param ( [Parameter(Mandatory = $true)] [object]$UserObj ) return New-Object -TypeName PSObject ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectType -Value $UserObj.objectType ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectId -Value $UserObj.objectId ` | Add-Member -PassThru -MemberType NoteProperty -Name UserPrincipalName -Value $UserObj.userPrincipalName ` | Add-Member -PassThru -MemberType NoteProperty -Name Mail -Value $UserObj.mail ` | Add-Member -PassThru -MemberType NoteProperty -Name DisplayName -Value $UserObj.displayName ` | Add-Member -PassThru -MemberType NoteProperty -Name AssignedLicenses -Value $UserObj.assignedLicenses ` | Add-Member -PassThru -MemberType NoteProperty -Name AssignedPlans -Value $UserObj.assignedLicenses ` | Add-Member -PassThru -MemberType NoteProperty -Name Internal -Value $UserObj; } function CreateGroupObject { param ( [Parameter(Mandatory = $true)] [object]$GroupObj ) return New-Object -TypeName PSObject ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectType -Value $GroupObj.objectType ` | Add-Member -PassThru -MemberType NoteProperty -Name Objectd -Value $GroupObj.objectId ` | Add-Member -PassThru -MemberType NoteProperty -Name Mail -Value $GroupObj.mail ` | Add-Member -PassThru -MemberType NoteProperty -Name DisplayName -Value $GroupObj.displayName ` | Add-Member -PassThru -MemberType NoteProperty -Name Internal -Value $GroupObj; } function CreateTenantObject { param ( [Parameter(Mandatory = $true)] [object]$TenantObj ) return New-Object -TypeName PSObject ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectType -Value $TenantObj.objectType ` | Add-Member -PassThru -MemberType NoteProperty -Name TenantId -Value $TenantObj.objectId ` | Add-Member -PassThru -MemberType NoteProperty -Name Country -Value $TenantObj.countryLetterCode ` | Add-Member -PassThru -MemberType NoteProperty -Name Language -Value $TenantObj.preferredLanguage ` | Add-Member -PassThru -MemberType NoteProperty -Name DisplayName -Value $TenantObj.displayName ` | Add-Member -PassThru -MemberType NoteProperty -Name Domains -Value $TenantObj.verifiedDomains ` | Add-Member -PassThru -MemberType NoteProperty -Name Internal -Value $TenantObj; } # SIG # Begin signature block # MIIoHgYJKoZIhvcNAQcCoIIoDzCCKAsCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCE6pkogaoMV/r5 # n6wWeEeWyv0oYE2iW+hJ6YqE+wxnxKCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0 # Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz # NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo # DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3 # a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF # HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy # 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC # Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj # L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp # h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3 # cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X # dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL # E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi # u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1 # sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq # 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb # DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/ # V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGf4wghn6AgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB # BQCggaAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBMIYlHh64tx0HuN5kDhQhis # D8uFb3uohr8QM4GMpvfmMDQGCisGAQQBgjcCAQwxJjAkoBKAEABUAGUAcwB0AFMA # aQBnAG6hDoAMaHR0cDovL3Rlc3QgMA0GCSqGSIb3DQEBAQUABIIBAI/n1ceZ/DhY # S04noOedCWws9dstWdBNuQx5s1BVJwDJGN/ilUkLyR5LS+v0dngc5r3viFvZaR1m # 49oktVAuWb/9u2Mxn0o8zYbRsSuNW/EA5Gw7Vit+kInPfzL7BYEo9lZTH7uwa8s0 # Rg3Rpg/zPXEKnqZt15SLwuIU2ZFJ1kWDKgXtVH6xIzTtY7Ne/jBce8sBkRWD709a # FBIsP6pVSlZrmnx/59SDoH5YzKeCOiveqcJ1jLpaJs/HEgdOchK8vyYjVUsyJFLZ # 2DCrzU14EQXUm3Grbkhx1uYumFxeu76MeJdYQFZppPEHyyvsw6YPDGtj5dG8cHxX # 01pxNZbOHlqhgheWMIIXkgYKKwYBBAGCNwMDATGCF4Iwghd+BgkqhkiG9w0BBwKg # ghdvMIIXawIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBUgYLKoZIhvcNAQkQAQSgggFB # BIIBPTCCATkCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgkEf0EjH8 # 2Y8c1QVJWDPfN5imVuk0eeA7kJrlbVlzMTICBmcacllY4RgTMjAyNDEwMjQxODM0 # NTkuODk3WjAEgAIB9KCB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9u # czEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE0MDAtMDVFMC1EOTQ3MSUwIwYD # VQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIR7DCCByAwggUIoAMC # AQICEzMAAAHs4CukgtCRUoAAAQAAAewwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjMxMjA2MTg0NTM4WhcNMjUwMzA1MTg0 # NTM4WjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMG # A1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMeblNo # aWVsZCBUU1MgRVNOOkE0MDAtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQg # VGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC # AgEAsEf0bgk24MVFlZv1XbpdtrsHRGZtCKABbOqCK9/VSvyLT/NHJ/vE5rT+u4mm # weA5gCifRh+nSRoRDyaWOL0ykUjsK0TcVSCqDz3lBd3+FchxHKP7tUFGnZcA9d9j # bmQsW54ejItpSxu6Q77M2ajBu0tzAotm5Np77RinXgCC/h++4C+K9NU0lm+67BNi # W9T/zemP1tQqg4tfyG9/80all7eM8b3SBnD40uGSskBBd0hGQKuFyI4sqMDx2qjW # 2cXX9pFjv2o3X01PObfd+AlwIp29KPrkPSrWijS1VXDX+UKUuH+vzLFzryBbgmDE # XSg46Zr6MAHi/tY9u2wsQgaQ0B61pHz82af1/m7fQuxOYTz+h1UaKgWEe7tYFH+R # hKvua9RwNI2o59EOjr32HJBNB3Tr+ilmvrAJiRuzw702Wnu+4aJs8eiD6oIFaTWb # gpO/Un1ZpyrvRefFAJ1OfE6gxxMxrEJzFECrLUt845+klNDSxBTQnrZbmipKlg0V # SxFm7t9vSBId7alz138ukYf8Am8HvUgiSKKrQXsQaz8kGANl2s9XyvcrE7MdJAPV # dScFVeOCGvXPjMLQEerKinQIEaP27P17vILmvCw3uilsrve+HvZhlu2TvJ2qwxaw # E9RFxhw7nsoEir79iu8AfJQIDBiY+9wkL6/o6qFsMel3cnkCAwEAAaOCAUkwggFF # MB0GA1UdDgQWBBT0WtBHZP4r9cIWELFfFIBH+EyFhjAfBgNVHSMEGDAWgBSfpxVd # AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIw # UENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYG # A1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B # AQsFAAOCAgEAkrzEpDEq745Qz2oPAEW9DhawELUizA6TdFGNxY7z4cBig664sZp7 # jH465lY0atbvCIZA7xhf2332xU6/iAJw0noPEwfc3xv+Mm5J7qKZJW3ho27ezC8a # X4aJQhEchHNtDzGSic/Ur837jtZ+ca6yzi/JtJ5r+ZAXL/stQFyeUHC4nJoXtiKd # /w+uxHeqD6kCNN5g42GktTUIQTbbue8Dyl2dRKDU6AZPGwOvN/cNdfW/mvVk6KiL # JHURqD+cYwyL/pnNLwR4WRpCVb3yIZuAKfM6bQu8VQJctI3jr+XVBjAmIGY76E5o # HeOW6gMLp3Zj5Rrq+3pXlmHnS0H+7Ny+fqn2mP8RIf/bqNe0pzP4B1UhgM7563ho # Tqwdi7XSqFUnuS22KYoV3LQ3u+omLS/pocVzxKc3Wt2yZYT0zkNyjhGQKVREQaOc # pbVozwlpV8cgqZeY4/Z2NJ33dO9W3pp6LvAN61Ga3YCiGrrbB+0hzojnm2Rqjbvu # ttrybWt3gGLAgGsQHAfQYiT5Wu12nfaq02HU+OVZQmE7QUmOKFUbHnUgA7/fY7/4 # mCABstWwsrbmtKP0Kr/Xqyps0Ak1TF2g3NuQ0y3DBia0bmtytMYr3bZ6AXsc1Sa+ # sl6jPgWtsISFUbxnK4gZCl9BSRXlu69vV1/pNHuA5xuogRykI3nOlTcwggdxMIIF # WaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNy # b3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAx # ODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy # MDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL # 1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5K # Wv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTeg # Cjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv62 # 6GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SH # JMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss25 # 4o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/Nme # Rd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afo # mXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLi # Mxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb # 0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W2 # 9R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQF # AgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1Ud # DgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdM # g30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJ # KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF # MAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8w # TTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVj # dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBK # BggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9N # aWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1V # ffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1 # OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce57 # 32pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihV # J9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZ # UnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW # 9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k # +SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pF # EUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L # +DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1 # ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6 # CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDTzCCAjcCAQEwgfmhgdGk # gc4wgcsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNV # BAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGll # bGQgVFNTIEVTTjpBNDAwLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRp # bWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAjhz7YFXc/RFtIjzS/wV6 # iaKlTH+ggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkq # hkiG9w0BAQsFAAIFAOrE8NcwIhgPMjAyNDEwMjQxNjE0MTVaGA8yMDI0MTAyNTE2 # MTQxNVowdjA8BgorBgEEAYRZCgQBMS4wLDAKAgUA6sTw1wIBADAJAgEAAgELAgH/ # MAcCAQACAhLtMAoCBQDqxkJXAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQB # hFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEB # ACoJf1bZcw3manjyMqbvddNj2oGH+S99x4iCUpqFWfUDgnvCHxokciwCfXR7qGTi # WLZjS/4dsKSNsWXMth7AScID9Dcqt+nLjs+Z4QlmrVhLWKunyVtGjC0j7BhBk4ij # jm0rMPQLYUfnk90VV8RQsTNzX/vs5diVMgaAzJ6RQGLD+D5ckysQS0bTL8uXCiDz # wHo9h5lphdVnMjpg7UyaFquusBAVG0gwRJMLHiwVkdhnVlQkRt22ql1rc9Sz1fWp # xD48mJ+dD7mKM5sXipwQqKGKFVn8LSEhw+2hJ6wZE5OrbTzbBZV2i7TS6pFnRcDy # 5Gj1bY1HzRlxkn4Dlq2XGoMxggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMAITMwAAAezgK6SC0JFSgAABAAAB7DANBglghkgBZQMEAgEF # AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi # BCAAH+Q1KI9KMV0TZJ94Tu2jmmsnFGT4kqqSeHnlQHBOjjCB+gYLKoZIhvcNAQkQ # Ai8xgeowgecwgeQwgb0EICcJ5vVqfTfIhx21QBBbKyo/xciQIXaoMWULejAE1QqD # MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO # BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm # MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAHs4Cuk # gtCRUoAAAQAAAewwIgQgx23NA8wsFWok5LguEG7JUMdnmod88YTNZdmY0BMwvm0w # DQYJKoZIhvcNAQELBQAEggIAjCJBArq86RbTHk/xrWCL5JTm+4wGwtAoxvfX4y6K # O+u91OGsbRQTJ+8DwJim0921URqT0BLPcdk4PBJciFwSEvH+92jTHMi0YlEKvAXx # urqoOMiPuT2CxBLiFEEzEGBXk1q+FwO5uN5/tFMhDn9iK5PueKaHB4oAH3fEIgBC # yf5D5Q9tpQiBLPRjP3QmmQ0ogaEpqlSbWcFpSTqQz/2tjNUFDNdoarm8CxVZmBzO # 3HWjRN13E7xNaoq16FBFVyjcT+3aS3AR14HH6PCSXwhyojSjkHHWFJkeVZwYaxGr # Pyq+2cwCtInNXwSDoWYAhnnq3+MaVzgux5QTpunt3AocZ8hpXthvHiP256ctpbVi # G+dQyUn3/A95z3u9m9MfO3k5JMWGiHnoZ5WxK9I3eMvHI+AM/Sk8C49nxsjpc8L2 # GxZmzA8TQ79ldorpW19YUq0MSNNCTTDJM3mEwM1oFOpgkK7OA6s26IIKKD/K/TPJ # vJb0B/SyEFH7fl9RazjLmTV5Pf2QZGCTSOEkRIhGr1p+5TbjCHKWZMrcnGI3Jmtj # 7Y1jWc3P4wQx6KaMFKdOJaGyFAHjVFlUA9bnAg1WL7DvoEHtLRPbw1QKwJPka2Wo # l/i7rXJ4aHYiIzX0DitsFiSPPzSymG2Fiu6aVDrlSjtHpoUMRImc62T6g4jeBq7o # SOU= # SIG # End signature block |