Workloads/ExchangeOnline.ps1
|
<#
.SYNOPSIS Returns the Windows principal of the current user. .DESCRIPTION Thin wrapper around the Windows identity APIs used to decide whether the current session is elevated. Extracted so that unit tests can substitute the principal without depending on the privileges of the test runner. #> function Get-MSCloudLoginWindowsPrincipal { [CmdletBinding()] [OutputType([System.Security.Principal.WindowsPrincipal])] param() return [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() } function Connect-MSCloudLoginExchangeOnline { [CmdletBinding()] param() $InformationPreference = 'SilentlyContinue' $ProgressPreference = 'SilentlyContinue' $source = 'Connect-MSCloudLoginExchangeOnline' Add-MSCloudLoginAssistantEvent -Message 'Trying to get the Get-AcceptedDomain command from within MSCloudLoginAssistant' -Source $source $loadAllCmdlets = $false if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad.Count -eq 0) { $loadAllCmdlets = $true } Add-MSCloudLoginAssistantEvent -Message "Current loaded module: $($Script:MSCloudLoginCurrentLoadedModule)" -Source $source if ($Script:MSCloudLoginCurrentLoadedModule -eq 'EXO') { try { $null = Get-Command -Name Get-AcceptedDomain -ErrorAction Stop if (-not $loadAllCmdlets) { Add-MSCloudLoginAssistantEvent -Message 'Checking for missing commands' -Source $source Add-MSCloudLoginAssistantEvent -Message "Cmdlets to load: $($Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad -join ',')" -Source $source Add-MSCloudLoginAssistantEvent -Message "Loaded Cmdlets: $($Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedCmdlets -join ',')" -Source $source $missingCommands = $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad | Where-Object -FilterScript { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedCmdlets -notcontains $_ } Add-MSCloudLoginAssistantEvent -Message "Missing commands: $($missingCommands -join ',')" -Source $source } # $missingCommands is null if no missing commands are found Add-MSCloudLoginAssistantEvent -Message "Loaded all cmdlets: $($Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedAllCmdlets)" -Source $source if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedAllCmdlets -or (-not $loadAllCmdlets -and $null -eq $missingCommands)) { Add-MSCloudLoginAssistantEvent -Message 'Succeeded' -Source $source $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection($Script:MSCloudLoginConnectionProfile.ExchangeOnline.MultiFactorAuthentication) return } } catch { Add-MSCloudLoginAssistantEvent -Message "Probe for existing Exchange Online session failed: $($_.Exception.Message)" -Source $source } } if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected) { Add-MSCloudLoginAssistantEvent -Message 'Exchange Online is already connected' -Source $source return } [array]$currentSessions = Get-ConnectionInformation | Where-Object -Property Name -Like 'ExchangeOnline_*' if ($null -ne $currentSessions -and $currentSessions.Count -gt 0) { Add-MSCloudLoginAssistantEvent -Message "Found {$($currentSessions.Count)} active Exchange Online session(s) but not connected" -Source $source if (-not [System.String]::IsNullOrEmpty($Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId) -and -not [System.String]::IsNullOrEmpty($Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId)) { $filteredSessions = $currentSessions | Where-Object { $_.AppId -eq $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId ` -and $_.Organization -eq $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId } if ($filteredSessions.Count -gt 0) { Add-MSCloudLoginAssistantEvent -Message "Found an active Exchange Online session for ApplicationId {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId)} and TenantId {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId)}" -Source $source Import-Module $filteredSessions.ModuleName -Force -Global -DisableNameChecking $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection($Script:MSCloudLoginConnectionProfile.ExchangeOnline.MultiFactorAuthentication) $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedAllCmdlets = $true $Script:MSCloudLoginCurrentLoadedModule = 'EXO' return } } if (-not [System.String]::IsNullOrEmpty($Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials.UserName)) { $filteredSessions = $currentSessions | Where-Object { $_.UserPrincipalName -eq $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials.UserName } if ($filteredSessions.Count -gt 0) { Add-MSCloudLoginAssistantEvent -Message "Found an active Exchange Online session for UserPrincipalName {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials.UserName)}" -Source $source Import-Module $filteredSessions.ModuleName -Force -Global -DisableNameChecking $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection($Script:MSCloudLoginConnectionProfile.ExchangeOnline.MultiFactorAuthentication) $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedAllCmdlets = $true $Script:MSCloudLoginCurrentLoadedModule = 'EXO' return } } } Add-MSCloudLoginAssistantEvent -Message 'No active Exchange Online session found.' -Source $source Add-MSCloudLoginAssistantEvent -Message "Loaded Modules: $(Get-Module | Select-Object -ExpandProperty Name)" -Source $source Remove-MSCloudLoginProxyModule -ProbeCommand 'Get-AcceptedDomain' -Source $source # Make sure we disconnect from any existing connections Disconnect-ExchangeOnline -Confirm:$false $CommandName = @{} if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad.Count -gt 0) { # Make sure we have the Get-AcceptedDomain command available if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad -notcontains 'Get-AcceptedDomain') { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad += 'Get-AcceptedDomain' } # Include the previously loaded commands, if available $combinedCmdlets = ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad + $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedCmdlets) | Select-Object -Unique $CommandName.Add('CommandName', $combinedCmdlets) Add-MSCloudLoginAssistantEvent -Message "Commands to load: $($CommandName.CommandName -join ',')" -Source $source } if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.AuthenticationType -eq 'ServicePrincipalWithThumbprint') { Add-MSCloudLoginAssistantEvent -Message "Attempting to connect to Exchange Online using AAD App {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId)}" -Source $source try { if ($PSVersionTable.PSVersion -gt [Version]'6.0' -and $PSVersionTable.Platform -ne 'Win32NT') { throw 'Certificate Thumbprint authentication is only supported on the Windows platform.' } if ($null -eq $Script:MSCloudLoginConnectionProfile.OrganizationName -or ` $Script:MSCloudLoginConnectionProfile.OrganizationName -ne $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId) { <# $Script:MSCloudLoginConnectionProfile.OrganizationName = Get-MSCloudLoginOrganizationName ` -ApplicationId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId ` -TenantId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId ` -CertificateThumbprint $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CertificateThumbprint #> $Script:MSCloudLoginConnectionProfile.OrganizationName = $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId } if (($Script:CustomEnvConfig.CustomEnvironment -or $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName -eq 'Custom') -and ` $null -ne $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ConnectionUri -and ` $null -ne $Script:MSCloudLoginConnectionProfile.ExchangeOnline.AzureADAuthorizationEndpointUri) { Add-MSCloudLoginAssistantEvent -Message 'Connecting by endpoints URI' -Source $source Connect-ExchangeOnline -AppId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId ` -Organization $Script:MSCloudLoginConnectionProfile.OrganizationName ` -CertificateThumbprint $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CertificateThumbprint ` -ShowBanner:$false ` -ShowProgress:$false ` -ConnectionUri $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ConnectionUri ` -AzureADAuthorizationEndpointUri $Script:MSCloudLoginConnectionProfile.ExchangeOnline.AzureADAuthorizationEndpointUri ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null } else { Add-MSCloudLoginAssistantEvent -Message 'Connecting by environment name' -Source $source Connect-ExchangeOnline -AppId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId ` -Organization $Script:MSCloudLoginConnectionProfile.OrganizationName ` -CertificateThumbprint $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CertificateThumbprint ` -ShowBanner:$false ` -ShowProgress:$false ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null } $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection() Add-MSCloudLoginAssistantEvent -Message "Successfully connected to Exchange Online using AAD App {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId)}" -Source $source } catch { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } elseif ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.AuthenticationType -eq 'ServicePrincipalWithPath') { Add-MSCloudLoginAssistantEvent -Message "Attempting to connect to Exchange Online using AAD App {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId)} with Certificate Path" -Source $source try { if ($null -eq $Script:MSCloudLoginConnectionProfile.OrganizationName -or ` $Script:MSCloudLoginConnectionProfile.OrganizationName -ne $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId) { # Because we are using Certificate Path, an authentication to Graph is not possible here if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId -notlike '*.onmicrosoft.com') { throw 'TenantId must be specified as the primary domain in the format <domain>.onmicrosoft.com when using Certificate Path authentication.' } $Script:MSCloudLoginConnectionProfile.OrganizationName = $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId } if (($IsWindows -or $PSVersionTable.PSVersion.Major -eq 5) -and ` -not (Get-MSCloudLoginWindowsPrincipal).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Certificate Path authentication on Windows requires the command to be run as Administrator.' } Connect-ExchangeOnline -AppId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId ` -Organization $Script:MSCloudLoginConnectionProfile.OrganizationName ` -CertificateFilePath $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CertificatePath ` -CertificatePassword $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CertificatePassword ` -ShowBanner:$false ` -ShowProgress:$false ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection() Add-MSCloudLoginAssistantEvent -Message "Successfully connected to Exchange Online using AAD App {$($Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId)} with Certificate Path" -Source $source } catch { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online with Certificate Path: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } elseif ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.AuthenticationType -eq 'Credentials') { try { Add-MSCloudLoginAssistantEvent -Message 'Attempting to connect to Exchange Online using Credentials without MFA' -Source $source Connect-ExchangeOnline -Credential $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials ` -ShowProgress:$false ` -ShowBanner:$false ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -ErrorAction Stop ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection() Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Exchange Online using Credentials without MFA' -Source $source } catch { if ((Test-MSCloudLoginMFARequiredError -ErrorRecord $_ -AdditionalPatterns @('*WAM Error*')) -and -not (Assert-IsNonInteractiveShell)) { Connect-MSCloudLoginExchangeOnlineMFA -Credentials $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials -CommandName $CommandName } else { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online with Credentials: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } } elseif ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.AuthenticationType -eq 'CredentialsWithTenantId') { try { Add-MSCloudLoginAssistantEvent -Message 'Attempting to connect to Exchange Online using Credentials without MFA' -Source $source Connect-ExchangeOnline -Credential $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials ` -ShowProgress:$false ` -ShowBanner:$false ` -DelegatedOrganization $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -ErrorAction Stop ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection() Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Exchange Online using Credentials & TenantId without MFA' -Source $source } catch { if ((Test-MSCloudLoginMFARequiredError -ErrorRecord $_ -AdditionalPatterns @('*WAM Error*')) -and -not (Assert-IsNonInteractiveShell)) { Connect-MSCloudLoginExchangeOnlineMFA -Credentials $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials ` -TenantId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId ` -CommandName $CommandName } else { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online with Credentials and TenantId: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } } elseif ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.AuthenticationType -eq 'Identity') { Add-MSCloudLoginAssistantEvent -Message 'Attempting to connect to Exchange Online using Managed Identity' -Source $source try { if ($null -eq $Script:MSCloudLoginConnectionProfile.OrganizationName) { #$Script:MSCloudLoginConnectionProfile.OrganizationName = Get-MSCloudLoginOrganizationName -Identity $Script:MSCloudLoginConnectionProfile.OrganizationName = $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId } Connect-ExchangeOnline -AppId $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ApplicationId ` -Organization $Script:MSCloudLoginConnectionProfile.OrganizationName ` -ManagedIdentity ` -ShowBanner:$false ` -ShowProgress:$false ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection($true) Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Exchange Online using Managed Identity' -Source $source } catch { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online with Managed Identity: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } elseif ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.AuthenticationType -eq 'AccessTokens') { Add-MSCloudLoginAssistantEvent -Message 'Connecting to EXO with AccessTokens' -Source $source try { $AccessTokenValue = Get-MSCloudLoginAccessTokenValue -Token $Script:MSCloudLoginConnectionProfile.ExchangeOnline.AccessTokens[0] Connect-ExchangeOnline -AccessToken $AccessTokenValue ` -Organization $Script:MSCloudLoginConnectionProfile.ExchangeOnline.TenantId ` -ShowBanner:$false ` -ShowProgress:$false ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection() Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Exchange Online using Access Token' -Source $source } catch { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online with Access Token: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } else { Add-MSCloudLoginAssistantEvent -Message 'No valid authentication type found' -Source $source throw 'No valid authentication type found' } $Script:MSCloudLoginCurrentLoadedModule = 'EXO' # Usually the tmpEXO* modules, but it might also be from another PSSession $loadedEXOProxyModule = Get-Module | Where-Object -FilterScript { $_.ExportedCommands.Keys.Contains('Get-AcceptedDomain') } $loadedEXOModule = Get-Module -Name 'ExchangeOnlineManagement' $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedCmdlets = $loadedEXOProxyModule.ExportedCommands.Keys + $loadedEXOModule.ExportedCommands.Keys if ($loadAllCmdlets) { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedAllCmdlets = $true } } function Connect-MSCloudLoginExchangeOnlineMFA { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $Credentials, [Parameter()] [System.String] $TenantId, [Parameter()] [System.Collections.Hashtable] $CommandName ) $ProgressPreference = 'SilentlyContinue' $source = 'Connect-MSCloudLoginExchangeOnlineMFA' try { if ([System.String]::IsNullOrEmpty($TenantId)) { Add-MSCloudLoginAssistantEvent -Message 'Creating a new ExchangeOnline Session using MFA' -Source $source Connect-ExchangeOnline -UserPrincipalName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials.UserName ` -ShowBanner:$false ` -ShowProgress:$false ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Exchange Online using credentials with MFA' -Source $source } else { Add-MSCloudLoginAssistantEvent -Message 'Creating a new ExchangeOnline Session using MFA with Credentials and TenantId' -Source $source Connect-ExchangeOnline -UserPrincipalName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Credentials.UserName ` -ShowBanner:$false ` -ShowProgress:$false ` -DelegatedOrganization $TenantId ` -ExchangeEnvironmentName $Script:MSCloudLoginConnectionProfile.ExchangeOnline.ExchangeEnvironmentName ` -Verbose:$false ` -SkipLoadingCmdletHelp ` @CommandName | Out-Null Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Exchange Online using credentials and tenantId with MFA' -Source $source } $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CompleteConnection($true) } catch { $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Exchange Online using MFA: $($_.Exception.Message)" -Source $source -EntryType 'Error' throw } } function Disconnect-MSCloudLoginExchangeOnline { [CmdletBinding()] param() $source = 'Disconnect-MSCloudLoginExchangeOnline' if ($Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected) { Add-MSCloudLoginAssistantEvent -Message 'Attempting to disconnect from Exchange Online' -Source $source Disconnect-ExchangeOnline -Confirm:$false $Script:MSCloudLoginConnectionProfile.ExchangeOnline.Connected = $false $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedAllCmdlets = $false $Script:MSCloudLoginConnectionProfile.ExchangeOnline.LoadedCmdlets = @() $Script:MSCloudLoginConnectionProfile.ExchangeOnline.CmdletsToLoad = @() Add-MSCloudLoginAssistantEvent -Message 'Successfully disconnected from Exchange Online' -Source $source } else { Add-MSCloudLoginAssistantEvent -Message 'No connections to Exchange Online were found' -Source $source } } |