MSCloudLoginAssistant.psm1
function Connect-M365Tenant { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateSet('Azure','AzureDevOPS', 'ExchangeOnline', 'Fabric', ` 'SecurityComplianceCenter', 'PnP', 'PowerPlatforms', ` 'MicrosoftTeams', 'MicrosoftGraph', 'SharePointOnlineREST', 'Tasks', 'DefenderForEndpoint')] [System.String] $Workload, [Parameter()] [System.String] $Url, [Parameter()] [Alias('o365Credential')] [System.Management.Automation.PSCredential] $Credential, [Parameter()] [System.String] $ApplicationId, [Parameter()] [System.String] $TenantId, [Parameter()] [System.String] $ApplicationSecret, [Parameter()] [System.String] $CertificateThumbprint, [Parameter()] [Switch] $UseModernAuth, [Parameter()] [SecureString] $CertificatePassword, [Parameter()] [System.String] $CertificatePath, [Parameter()] [System.Boolean] $SkipModuleReload = $false, [Parameter()] [Switch] $Identity, [Parameter()] [System.String[]] $AccessTokens, [Parameter()] [System.Collections.Hashtable] $Endpoints ) $VerbosePreference = 'SilentlyContinue' $workloadInternalName = $Workload if ($Workload -eq 'MicrosoftTeams') { $workloadInternalName = 'Teams' } elseif ($Workload -eq 'PowerPlatforms') { $workloadInternalName = 'PowerPlatform' } if ($null -eq $Global:MSCloudLoginConnectionProfile) { $Global:MSCloudLoginConnectionProfile = New-Object MSCloudLoginConnectionProfile } # Only validate the parameters if we are not already connected elseif ( $Global:MSCloudLoginConnectionProfile.$workloadInternalName.Connected ` -and (Compare-InputParametersForChange -CurrentParamSet $PSBoundParameters)) { Write-Verbose -Message 'Resetting connection profile' $Global:MSCloudLoginConnectionProfile.$workloadInternalName.Connected = $false } Write-Verbose -Message "Trying to connect to platform {$Workload}" switch ($Workload) { 'Azure' { $Global:MSCloudLoginConnectionProfile.Azure.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.Azure.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.Azure.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.Azure.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.Azure.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.Azure.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.Azure.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.Azure.Connected = $false $Global:MSCloudLoginConnectionProfile.Azure.Connect() } 'AzureDevOPS' { $Global:MSCloudLoginConnectionProfile.AzureDevOPS.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.AzureDevOPS.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.AzureDevOPS.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.AzureDevOPS.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.AzureDevOPS.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.AzureDevOPS.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.AzureDevOPS.Identity = $Identity $Global:MSCloudLoginConnectionProfile.AzureDevOPS.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.AzureDevOPS.Connect() } 'DefenderForEndpoint' { $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.Identity = $Identity $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.DefenderForEndpoint.Connect() } 'ExchangeOnline' { $Global:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.ExchangeOnline.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.ExchangeOnline.SkipModuleReload = $SkipModuleReload $Global:MSCloudLoginConnectionProfile.ExchangeOnline.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.ExchangeOnline.Identity = $Identity $Global:MSCloudLoginConnectionProfile.ExchangeOnline.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.ExchangeOnline.Connect() } 'Fabric' { $Global:MSCloudLoginConnectionProfile.Fabric.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.Fabric.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.Fabric.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.Fabric.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.Fabric.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.Fabric.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.Fabric.Identity = $Identity $Global:MSCloudLoginConnectionProfile.Fabric.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.Fabric.Connect() } 'MicrosoftGraph' { $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.Identity = $Identity $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.Connect() } 'MicrosoftTeams' { $Global:MSCloudLoginConnectionProfile.Teams.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.Teams.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.Teams.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.Teams.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.Teams.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.Teams.CertificatePath = $CertificatePath $Global:MSCloudLoginConnectionProfile.Teams.CertificatePassword = $CertificatePassword $Global:MSCloudLoginConnectionProfile.Teams.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.Teams.Identity = $Identity $Global:MSCloudLoginConnectionProfile.Teams.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.Teams.Connect() } 'PnP' { $Global:MSCloudLoginConnectionProfile.PnP.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.PnP.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.PnP.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.PnP.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.PnP.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.PnP.CertificatePath = $CertificatePath $Global:MSCloudLoginConnectionProfile.PnP.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.PnP.Identity = $Identity $Global:MSCloudLoginConnectionProfile.PnP.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.PnP.CertificatePassword = $CertificatePassword # Mark as disconnected if we are trying to connect to a different url then we previously connected to. if ($Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl -ne $Url -or ` -not $Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl -and ` $Url -or (-not $Url -and -not $Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl)) { $ForceRefresh = $false if ($Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl -ne $Url -and ` -not [System.String]::IsNullOrEmpty($url)) { $ForceRefresh = $true } $Global:MSCloudLoginConnectionProfile.PnP.Connected = $false $Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl = $Url $Global:MSCloudLoginConnectionProfile.PnP.Connect($ForceRefresh) } else { try { $contextUrl = (Get-PnPContext).Url if ([System.String]::IsNullOrEmpty($url)) { $Url = $Global:MSCloudLoginConnectionProfile.PnP.AdminUrl if (-not $Url.EndsWith('/') -and $contextUrl.EndsWith('/')) { $Url += '/' } } if ($contextUrl -ne $Url) { $ForceRefresh = $true $Global:MSCloudLoginConnectionProfile.PnP.Connected = $false if ($url) { $Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl = $Url } else { $Global:MSCloudLoginConnectionProfile.PnP.ConnectionUrl = $Global:MSCloudLoginConnectionProfile.PnP.AdminUrl } $Global:MSCloudLoginConnectionProfile.PnP.Connect($ForceRefresh) } } catch { Write-Information -MessageData "Couldn't acquire PnP Context" } } # If the AdminUrl is empty and a URL was provided, assume that the url # provided is the admin center; if (-not $Global:MSCloudLoginConnectionProfile.PnP.AdminUrl -and $Url) { $Global:MSCloudLoginConnectionProfile.PnP.AdminUrl = $Url } } 'PowerPlatforms' { $Global:MSCloudLoginConnectionProfile.PowerPlatform.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.PowerPlatform.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.PowerPlatform.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.PowerPlatform.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.PowerPlatform.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.PowerPlatform.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.PowerPlatform.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.PowerPlatform.Connect() } 'SecurityComplianceCenter' { $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.CertificatePath = $CertificatePath $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.CertificatePassword = $CertificatePassword $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.SkipModuleReload = $SkipModuleReload $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.SecurityComplianceCenter.Connect() } 'SharePointOnlineREST' { $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.Identity = $Identity $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.Connected = $false $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.ConnectionUrl = $Url $Global:MSCloudLoginConnectionProfile.SharePointOnlineREST.Connect() # If the AdminUrl is empty and a URL was provided, assume that the url # provided is the admin center; if (-not $Global:MSCloudLoginConnectionProfile.PnP.AdminUrl -and $Url) { $Global:MSCloudLoginConnectionProfile.PnP.AdminUrl = $Url } } 'Tasks' { $Global:MSCloudLoginConnectionProfile.Tasks.Credentials = $Credential $Global:MSCloudLoginConnectionProfile.Tasks.ApplicationId = $ApplicationId $Global:MSCloudLoginConnectionProfile.Tasks.ApplicationSecret = $ApplicationSecret $Global:MSCloudLoginConnectionProfile.Tasks.TenantId = $TenantId $Global:MSCloudLoginConnectionProfile.Tasks.CertificateThumbprint = $CertificateThumbprint $Global:MSCloudLoginConnectionProfile.Tasks.CertificatePath = $CertificatePath $Global:MSCloudLoginConnectionProfile.Tasks.CertificatePassword = $CertificatePassword $Global:MSCloudLoginConnectionProfile.Tasks.AccessTokens = $AccessTokens $Global:MSCloudLoginConnectionProfile.Tasks.Endpoints = $Endpoints $Global:MSCloudLoginConnectionProfile.Tasks.Connect() } } } <# .SYNOPSIS This functions compares the authentication parameters for a change compared to the currently used parameters. .DESCRIPTION This functions compares the authentication parameters for a change compared to the currently used parameters. It is used to determine if a new connection needs to be made. .OUTPUTS Boolean. Compare-InputParametersForChange returns $true if something changed, $false otherwise. .EXAMPLE Compare-InputParametersForChange -CurrentParamSet $PSBoundParameters #> function Compare-InputParametersForChange { param ( [Parameter()] [System.Collections.Hashtable] $CurrentParamSet ) $currentParameters = $currentParamSet if ($null -ne $currentParameters['Credential'].UserName) { $currentParameters.Add('UserName', $currentParameters['Credential'].UserName) } $currentParameters.Remove('Credential') | Out-Null $currentParameters.Remove('SkipModuleReload') | Out-Null $currentParameters.Remove('UseModernAuth') | Out-Null $currentParameters.Remove('ProfileName') | Out-Null $currentParameters.Remove('Verbose') | Out-Null $globalParameters = @{} $workloadProfile = $Global:MSCloudLoginConnectionProfile if ($null -eq $workloadProfile) { # No Workload profile yet, so we need to connect # This should not happen, but just in case # We are not able to detect a change, so we return $false return $false } else { $workload = $currentParameters['Workload'] if ($Workload -eq 'MicrosoftTeams') { $workloadInternalName = 'Teams' } elseif ($Workload -eq 'PowerPlatforms') { $workloadInternalName = 'PowerPlatform' } else { $workloadInternalName = $workload } $workloadProfile = $Global:MSCloudLoginConnectionProfile.$workloadInternalName } # Clean the global Params if (-not [System.String]::IsNullOrEmpty($workloadProfile.TenantId)) { $globalParameters.Add('TenantId', $workloadProfile.TenantId) } if (-not [System.String]::IsNullOrEmpty($workloadProfile.Credentials.UserName)) { $globalParameters.Add('UserName', $workloadProfile.Credentials.UserName) # If the tenant id is part of the username, we need to remove it from the global parameters if ($workloadInternalName -eq 'MicrosoftGraph' ` -and $globalParameters.ContainsKey('TenantId') ` -and $globalParameters.TenantId -eq $workloadProfile.Credentials.UserName.Split('@')[1]) { $globalParameters.Remove('TenantId') | Out-Null } } if ($workloadInternalName -eq 'PNP' -and $currentParameters.ContainsKey("Url") -and ` -not [System.String]::IsNullOrEmpty($currentParameters.Url)) { $globalParameters.Add('Url', $workloadProfile.ConnectionUrl) } # This is the global graph application id. If it is something different, it means that we should compare the parameters if (-not [System.String]::IsNullOrEmpty($workloadProfile.ApplicationId) ` -and -not($workloadInternalName -eq 'MicrosoftGraph' -and $workloadProfile.ApplicationId -eq '14d82eec-204b-4c2f-b7e8-296a70dab67e')) { $globalParameters.Add('ApplicationId', $workloadProfile.ApplicationId) } if (-not [System.String]::IsNullOrEmpty($workloadProfile.ApplicationSecret)) { $globalParameters.Add('ApplicationSecret', $workloadProfile.ApplicationSecret) } if (-not [System.String]::IsNullOrEmpty($workloadProfile.CertificateThumbprint)) { $globalParameters.Add('CertificateThumbprint', $workloadProfile.CertificateThumbprint) } if (-not [System.String]::IsNullOrEmpty($workloadProfile.CertificatePassword)) { $globalParameters.Add('CertificatePassword', $workloadProfile.CertificatePassword) } if (-not [System.String]::IsNullOrEmpty($workloadProfile.CertificatePath)) { $globalParameters.Add('CertificatePath', $workloadProfile.CertificatePath) } if ($workloadProfile.Identity) { $globalParameters.Add('Identity', $workloadProfile.Identity) } if ($workloadProfile.AccessTokens) { $globalParameters.Add('AccessTokens', $workloadProfile.AccessTokens) } # Clean the current parameters # Remove the workload, as we don't need to compare that $currentParameters.Remove('Workload') | Out-Null if ([System.String]::IsNullOrEmpty($currentParameters.ApplicationId)) { $currentParameters.Remove('ApplicationId') | Out-Null } if ([System.String]::IsNullOrEmpty($currentParameters.TenantId)) { $currentParameters.Remove('TenantId') | Out-Null } if ([System.String]::IsNullOrEmpty($currentParameters.ApplicationSecret)) { $currentParameters.Remove('ApplicationSecret') | Out-Null } if ([System.String]::IsNullOrEmpty($currentParameters.CertificateThumbprint)) { $currentParameters.Remove('CertificateThumbprint') | Out-Null } if ([System.String]::IsNullOrEmpty($currentParameters.CertificatePassword)) { $currentParameters.Remove('CertificatePassword') | Out-Null } if ([System.String]::IsNullOrEmpty($currentParameters.CertificatePath)) { $currentParameters.Remove('CertificatePath') | Out-Null } if ($currentParameters.ContainsKey('Identity') -and -not ($currentParameters.Identity)) { $currentParameters.Remove('Identity') | Out-Null } if ($null -ne $globalParameters) { $diffKeys = Compare-Object -ReferenceObject @($currentParameters.Keys) -DifferenceObject @($globalParameters.Keys) -PassThru $referenceObj = $currentParameters.Values $diffValues = Compare-Object -ReferenceObject $referenceObj -DifferenceObject @($globalParameters.Values) -PassThru } if ($null -eq $diffKeys -and $null -eq $diffValues) { # no differences were found return $false } # We found differences, so we need to connect return $true } function Get-SPOAdminUrl { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter()] [System.Management.Automation.PSCredential] $Credential ) Write-Verbose -Message 'Connection to Microsoft Graph is required to automatically determine SharePoint Online admin URL...' try { $result = Invoke-MgGraphRequest -Uri /v1.0/sites/root -ErrorAction SilentlyContinue $weburl = $result.webUrl if (-not $weburl) { Connect-M365Tenant -Workload 'MicrosoftGraph' -Credential $Credential $weburl = (Invoke-MgGraphRequest -Uri /v1.0/sites/root).webUrl } } catch { Connect-M365Tenant -Workload 'MicrosoftGraph' -Credential $Credential try { $weburl = (Invoke-MgGraphRequest -Uri /v1.0/sites/root).webUrl } catch { if (Assert-IsNonInteractiveShell -eq $false) { # Only run interactive command when Exporting Write-Verbose -Message 'Requesting access to read information about the domain' Connect-MgGraph -Scopes Sites.Read.All -ErrorAction 'Stop' $weburl = (Invoke-MgGraphRequest -Uri /v1.0/sites/root).webUrl } else { if ($_.Exception.Message -eq 'Insufficient privileges to complete the operation.') { throw "The Graph application does not have the correct permissions to access Domains. Make sure you run 'Connect-MgGraph -Scopes Sites.Read.All' first!" } } } } if ($null -eq $weburl) { throw 'Unable to retrieve SPO Admin URL. Please check connectivity and if you have the Sites.Read.All permission.' } $spoAdminUrl = $webUrl -replace '^https:\/\/(\w*)\.', 'https://$1-admin.' Write-Verbose -Message "SharePoint Online admin URL is $spoAdminUrl" return $spoAdminUrl } function Get-AzureADDLL { [CmdletBinding()] [OutputType([System.String])] param( ) [array]$AzureADModules = Get-Module -ListAvailable | Where-Object { $_.name -eq 'AzureADPreview' } if ($AzureADModules.count -eq 0) { Throw "Can't find Azure AD DLL. Install the module manually 'Install-Module AzureADPreview'" } else { $AzureDLL = Join-Path (($AzureADModules | Sort-Object version -Descending | Select-Object -First 1).Path | Split-Path) Microsoft.IdentityModel.Clients.ActiveDirectory.dll return $AzureDLL } } function Get-TenantLoginEndPoint { [CmdletBinding()] [OutputType([System.String])] Param( [Parameter(Mandatory = $True)] [System.String] $TenantName, [Parameter(Mandatory = $false)] [System.String] [ValidateSet('MicrosoftOnline', 'EvoSTS')] $LoginSource = 'EvoSTS' ) $TenantInfo = @{ } if ($LoginSource -eq 'EvoSTS') { $webrequest = Invoke-WebRequest -Uri https://login.windows.net/$($TenantName)/.well-known/openid-configuration -UseBasicParsing } else { $webrequest = Invoke-WebRequest -Uri https://login.microsoftonline.com/$($TenantName)/.well-known/openid-configuration -UseBasicParsing } if ($webrequest.StatusCode -eq 200) { $TenantInfo = $webrequest.Content | ConvertFrom-Json } return $TenantInfo } function New-ADALServiceInfo { [CmdletBinding()] [OutputType([System.Collections.HashTable])] Param( [Parameter(Mandatory = $True)] [System.String] $TenantName, [Parameter(Mandatory = $True)] [System.String] $UserPrincipalName, [Parameter(Mandatory = $false)] [System.String] [ValidateSet('MicrosoftOnline', 'EvoSTS')] $LoginSource = 'EvoSTS' ) $AzureADDLL = Get-AzureADDLL if ([string]::IsNullOrEmpty($AzureADDLL)) { Throw "Can't find Azure AD DLL" Exit } else { Write-Verbose -Message "AzureADDLL: $AzureADDLL" $tMod = [System.Reflection.Assembly]::LoadFrom($AzureADDLL) } $TenantInfo = Get-TenantLoginEndPoint -TenantName $TenantName if ([string]::IsNullOrEmpty($TenantInfo)) { Throw "Can't find Tenant Login Endpoint" Exit } else { [string] $authority = $TenantInfo.authorization_endpoint } $PromptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto $Service = @{ } $Service['authContext'] = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($authority, $false) $Service['platformParam'] = New-Object 'Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters' -ArgumentList $PromptBehavior $Service['userId'] = New-Object 'Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier' -ArgumentList $UserPrincipalName, 'OptionalDisplayableId' Write-Verbose -Message "Current Assembly for AD AuthenticationContext: $([Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext].Assembly | Out-String)" return $Service } function Get-AuthHeader { [CmdletBinding()] Param( [Parameter(Mandatory = $True)] [System.String] $UserPrincipalName, [Parameter(Mandatory = $True)] [Alias('RessourceURI')] # For backward compat with anything using the misspelled parameter $ResourceURI, [Parameter(Mandatory = $True)] $clientId, [Parameter(Mandatory = $True)] [System.String] $RedirectURI ) if ($null -eq $Global:ADALServicePoint) { $TenantName = $UserPrincipalName.split('@')[1] $Global:ADALServicePoint = New-ADALServiceInfo -TenantName $TenantName -UserPrincipalName $UserPrincipalName } try { Write-Debug 'Looking for a refresh token' $authResult = $Global:ADALServicePoint.authContext.AcquireTokenSilentAsync($ResourceURI, $clientId) if ($null -eq $authResult.result) { $RedirectURI = [System.Uri]::new($RedirectURI) $authResult = $Global:ADALServicePoint.authContext.AcquireTokenAsync($ResourceURI, $clientId, $RedirectURI, $Global:ADALServicePoint.platformParam, $Global:ADALServicePoint.userId, '', '') } $AuthHeader = $authResult.result.CreateAuthorizationHeader() } catch { Throw "Can't create Authorization header: $_" } Return $AuthHeader } function Get-MSCloudLoginAccessToken { [CmdletBinding()] [OutputType([System.String])] Param( [Parameter(Mandatory = $True)] [System.String] $ConnectionUri, [Parameter(Mandatory = $True)] [System.String] $AzureADAuthorizationEndpointUri, [Parameter(Mandatory = $True)] [System.String] $ApplicationId, [Parameter(Mandatory = $True)] [System.String] $TenantId, [Parameter(Mandatory = $True)] [System.String] $CertificateThumbprint ) try { Write-Verbose -Message "Connecting by endpoints URI" $Certificate = Get-Item "Cert:\CurrentUser\My\$($CertificateThumbprint)" -ErrorAction SilentlyContinue if ($null -eq $Certificate) { Write-Verbose 'Certificate not found in CurrentUser\My' $Certificate = Get-ChildItem "Cert:\LocalMachine\My\$($CertificateThumbprint)" -ErrorAction SilentlyContinue if ($null -eq $Certificate) { throw 'Certificate not found in LocalMachine\My' } } # Create base64 hash of certificate $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash()) # Create JWT timestamp for expiration $StartDate = (Get-Date '1970-01-01T00:00:00Z' ).ToUniversalTime() $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds $JWTExpiration = [math]::Round($JWTExpirationTimeSpan, 0) # Create JWT validity start timestamp $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan, 0) # Create JWT header $JWTHeader = @{ alg = 'RS256' typ = 'JWT' # Use the CertificateBase64Hash and replace/strip to match web encoding of base64 x5t = $CertificateBase64Hash -replace '\+', '-' -replace '/', '_' -replace '=' } # Create JWT payload $JWTPayLoad = @{ # What endpoint is allowed to use this JWT aud = "$($AzureADAuthorizationEndpointUri)/$($TenantId)/oauth2/token" # Expiration timestamp exp = $JWTExpiration # Issuer = your application iss = $ApplicationId # JWT ID: random guid jti = [guid]::NewGuid() # Not to be used before nbf = $NotBefore # JWT Subject sub = $ApplicationId } # Convert header and payload to base64 $JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json)) $EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte) $JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json)) $EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte) # Join header and Payload with "." to create a valid (unsigned) JWT $JWT = $EncodedHeader + '.' + $EncodedPayload # Get the private key object of your certificate $PrivateKey = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)) # Define RSA signature and hashing algorithm $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1 $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256 # Create a signature of the JWT $Signature = [Convert]::ToBase64String( $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT), $HashAlgorithm, $RSAPadding) ) -replace '\+', '-' -replace '/', '_' -replace '=' # Join the signature to the JWT with "." $JWT = $JWT + '.' + $Signature # Create a hash with body parameters $Body = @{ client_id = $appId client_assertion = $JWT client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' scope = $ConnectionUri grant_type = 'client_credentials' } $Url = "$($AzureADAuthorizationEndpointUri)/$($TenantId)/oauth2/v2.0/token" # Use the self-generated JWT as Authorization $Header = @{ Authorization = "Bearer $JWT" } # Splat the parameters for Invoke-Restmethod for cleaner code $PostSplat = @{ ContentType = 'application/x-www-form-urlencoded' Method = 'POST' Body = $Body Uri = $Url Headers = $Header } $Request = Invoke-RestMethod @PostSplat return $Request.access_token } catch { Write-Verbose $_ throw $_ } } function Get-PowerPlatformTokenInfo { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.String] $Audience, [Parameter()] [System.Management.Automation.PSCredential] $Credentials ) $jobName = 'AcquireTokenAsync' + (New-Guid).ToString() Start-Job -Name $jobName -ScriptBlock { Param( [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $O365Credentials, [Parameter(Mandatory = $true)] [System.String] $Audience ) try { $WarningPreference = 'SilentlyContinue' Import-Module -Name 'Microsoft.PowerApps.Administration.PowerShell' -Force $authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext('https://login.windows.net/common') $credential = [Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential]::new($O365Credentials.Username, $O365Credentials.Password) $authResult = $authContext.AcquireToken($Audience, '1950a258-227b-4e31-a9cf-717495945fc2', $credential) $JwtToken = $authResult.IdToken $tokenSplit = $JwtToken.Split('.') $claimsSegment = $tokenSplit[1].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)) $claims = ConvertFrom-Json $decodedClaimsSegment } catch { $_ | Out-File "$env:temp\MSCloudLoginAssistant_Error.txt" } return @{ JwtToken = $JwtToken Claims = $claims RefreshToken = $authResult.RefreshToken AccessToken = $authResult.AccessToken ExpiresOn = $authResult.ExpiresOn } } -ArgumentList @($Credentials, $Audience) | Out-Null $job = Get-Job | Where-Object -FilterScript { $_.Name -eq $jobName } do { Start-Sleep -Seconds 1 } while ($job.JobStateInfo.State -ne 'Completed') $TokenInfo = Receive-Job -Name $jobName return $TokenInfo } function Test-MSCloudLoginCommand { [CmdletBinding()] [OutputType([System.Boolean])] Param( [Parameter(Mandatory = $true)] [System.String] $Command ) try { $testResult = Invoke-Command $Command return $true } catch { return $false } } function Get-CloudEnvironmentInfo { [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [System.Management.Automation.PSCredential] $Credentials, [Parameter()] [System.String] $ApplicationId, [Parameter()] [System.String] $TenantId, [Parameter()] [System.String] $ApplicationSecret, [Parameter()] [System.String] $CertificateThumbprint, [Parameter()] [switch] $Identity ) $VerbosePreference = 'SilentlyContinue' Write-Verbose -Message "Retrieving Environment Details" try { if ($null -ne $Credentials) { $tenantName = $Credentials.UserName.Split('@')[1] } elseif (-not [string]::IsNullOrEmpty($TenantId)) { $tenantName = $TenantId } elseif ($Identity.IsPresent) { return } else { throw 'TenantId or Credentials must be provided' } ## endpoint will work with TenantId or tenantName $response = Invoke-WebRequest -Uri "https://login.microsoftonline.com/$tenantName/v2.0/.well-known/openid-configuration" -Method Get -UseBasicParsing $content = $response.Content $result = ConvertFrom-Json $content return $result } catch { throw $_ } } function Get-MSCloudLoginTenantDomain { param( [Parameter()] [System.String] $ApplicationId, [Parameter()] [System.String] $TenantId, [Parameter()] [System.String] $CertificateThumbprint, [Parameter()] [switch] $Identity, [Parameter()] [System.String[]] $AccessTokens ) if (-not [string]::IsNullOrEmpty($ApplicationId)) { Connect-M365Tenant -Workload MicrosoftGraph ` -ApplicationId $ApplicationId ` -TenantId $TenantId ` -CertificateThumbprint $CertificateThumbprint } elseif ($Identity.IsPresent) { Connect-M365Tenant -Workload MicrosoftGraph ` -Identity ` -TenantId $TenantId } elseif ($null -ne $AccessTokens) { Connect-M365Tenant -Workload MicrosoftGraph ` -AccessTokens $AccessTokens } try { $domain = Get-MgDomain | Where-Object { $_.IsInitial -eq $True } } catch { $domain = Get-MgBetaDomain | Where-Object { $_.IsInitial -eq $True } } if ($null -ne $domain) { return $domain.Id.split('.')[0] } } function Get-MSCloudLoginOrganizationName { param( [Parameter()] [System.String] $ApplicationId, [Parameter()] [System.String] $TenantId, [Parameter()] [System.String] $CertificateThumbprint, [Parameter()] [System.String] $ApplicationSecret, [Parameter()] [switch] $Identity, [Parameter()] [System.String[]] $AccessTokens ) try { if (-not [string]::IsNullOrEmpty($ApplicationId) -and -not [System.String]::IsNullOrEmpty($CertificateThumbprint)) { Connect-M365Tenant -Workload MicrosoftGraph -ApplicationId $ApplicationId -TenantId $TenantId -CertificateThumbprint $CertificateThumbprint } elseif (-not [string]::IsNullOrEmpty($ApplicationId) -and -not [System.String]::IsNullOrEmpty($ApplicationSecret)) { Connect-M365Tenant -Workload MicrosoftGraph -ApplicationId $ApplicationId -TenantId $TenantId -ApplicationSecret $ApplicationSecret } elseif ($Identity.IsPresent) { Connect-M365Tenant -Workload MicrosoftGraph -Identity -TenantId $TenantId } elseif ($null -ne $AccessTokens) { Connect-M365Tenant -Workload MicrosoftGraph -AccessTokens $AccessTokens } $domain = Get-MgDomain -ErrorAction Stop | Where-Object { $_.IsInitial -eq $True } if ($null -ne $domain) { return $domain.Id } } catch { Write-Verbose -Message "Couldn't get domain. Using TenantId instead" return $TenantId } } function Assert-IsNonInteractiveShell { # Test each Arg for match of abbreviated '-NonInteractive' command. $NonInteractive = [Environment]::GetCommandLineArgs() | Where-Object { $_ -like '-NonI*' } if ([Environment]::UserInteractive -and -not $NonInteractive) { # We are in an interactive shell. return $false } return $true } |