netCore/ExchangeOnlineManagementBeta.psm1
# Import the REST module so that the EXO* cmdlets are present before Connect-ExchangeOnline in the powershell instance. $RestModule = "Microsoft.Exchange.Management.RestApiClient.dll" $RestModulePath = [System.IO.Path]::Combine($PSScriptRoot, $RestModule) Import-Module $RestModulePath $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellGalleryModule.dll" $ExoPowershellModulePath = [System.IO.Path]::Combine($PSScriptRoot, $ExoPowershellModule) Import-Module $ExoPowershellModulePath ############# Helper Functions Begin ############# <# Details to be printed on the console when the Connect-ExchangeOnline function is run #> function Print-Details { Write-Host -ForegroundColor Yellow "" Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------" Write-Host -ForegroundColor Yellow "The module allows access to all existing remote PowerShell (V1) cmdlets in addition to the 9 new, faster, and more reliable cmdlets." Write-Host -ForegroundColor Yellow "" Write-Host -ForegroundColor Yellow "|--------------------------------------------------------------------------|" Write-Host -ForegroundColor Yellow "| Old Cmdlets | New/Reliable/Faster Cmdlets |" Write-Host -ForegroundColor Yellow "|--------------------------------------------------------------------------|" Write-Host -ForegroundColor Yellow "| Get-CASMailbox | Get-EXOCASMailbox |" Write-Host -ForegroundColor Yellow "| Get-Mailbox | Get-EXOMailbox |" Write-Host -ForegroundColor Yellow "| Get-MailboxFolderPermission | Get-EXOMailboxFolderPermission |" Write-Host -ForegroundColor Yellow "| Get-MailboxFolderStatistics | Get-EXOMailboxFolderStatistics |" Write-Host -ForegroundColor Yellow "| Get-MailboxPermission | Get-EXOMailboxPermission |" Write-Host -ForegroundColor Yellow "| Get-MailboxStatistics | Get-EXOMailboxStatistics |" Write-Host -ForegroundColor Yellow "| Get-MobileDeviceStatistics | Get-EXOMobileDeviceStatistics |" Write-Host -ForegroundColor Yellow "| Get-Recipient | Get-EXORecipient |" Write-Host -ForegroundColor Yellow "| Get-RecipientPermission | Get-EXORecipientPermission |" Write-Host -ForegroundColor Yellow "|--------------------------------------------------------------------------|" Write-Host -ForegroundColor Yellow "" Write-Host -ForegroundColor Yellow "To get additional information, run: Get-Help Connect-ExchangeOnline or check https://aka.ms/exops-docs" Write-Host -ForegroundColor Yellow "" Write-Host -ForegroundColor Yellow "Send your product improvement suggestions and feedback to exocmdletpreview@service.microsoft.com. For issues related to the module, contact Microsoft support. Don't use the feedback alias for problems or support issues." Write-Host -ForegroundColor Yellow "----------------------------------------------------------------------------" Write-Host -ForegroundColor Yellow "" } <# .Synopsis Validates a given Uri #> function Test-Uri { [CmdletBinding()] [OutputType([bool])] Param ( # Uri to be validated [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [string] $UriString ) [Uri]$uri = $UriString -as [Uri] $uri.AbsoluteUri -ne $null -and $uri.Scheme -eq 'https' } <# .Synopsis Is Cloud Shell Environment #> function global:IsCloudShellEnvironment() { return [Microsoft.Exchange.Management.AdminApiProvider.Utility]::IsCloudShellEnvironment(); } <# .Synopsis Override Get-PSImplicitRemotingSession function for reconnection #> function global:UpdateImplicitRemotingHandler() { $modules = Get-Module tmp_* foreach ($module in $modules) { [bool]$moduleProcessed = $false [string] $moduleUrl = $module.Description [int] $queryStringIndex = $moduleUrl.IndexOf("?") if ($queryStringIndex -gt 0) { $moduleUrl = $moduleUrl.SubString(0,$queryStringIndex) } if ($moduleUrl.EndsWith("/PowerShell-LiveId", [StringComparison]::OrdinalIgnoreCase) -or $moduleUrl.EndsWith("/PowerShell", [StringComparison]::OrdinalIgnoreCase)) { & $module { ${function:Get-PSImplicitRemotingSession} = ` { param( [Parameter(Mandatory = $true, Position = 0)] [string] $commandName ) $shouldRemoveCurrentSession = $false; # Clear any left over PS tmp modules if (($script:PSSession -ne $null) -and ($script:PSSession.PreviousModuleName -ne $null) -and ($script:PSSession.PreviousModuleName -ne $script:MyModule.Name)) { Remove-Module -Name $script:PSSession.PreviousModuleName -ErrorAction SilentlyContinue $script:PSSession.PreviousModuleName = $null } if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened')) { Set-PSImplicitRemotingSession ` (& $script:GetPSSession ` -InstanceId $script:PSSession.InstanceId.Guid ` -ErrorAction SilentlyContinue ) } if ($script:PSSession -ne $null) { if ($script:PSSession.Runspace.RunspaceStateInfo.State -eq 'Disconnected') { # If we are handed a disconnected session, try re-connecting it before creating a new session. Set-PSImplicitRemotingSession ` (& $script:ConnectPSSession ` -Session $script:PSSession ` -ErrorAction SilentlyContinue) } else { # Import the module once more to ensure that Test-ActiveToken is present Import-Module $global:_EXO_ModulePath -Cmdlet Test-ActiveToken; # If there is no active token run the new session flow $hasActiveToken = Test-ActiveToken -TokenExpiryTime $script:PSSession.TokenExpiryTime $sessionIsOpened = $script:PSSession.Runspace.RunspaceStateInfo.State -eq 'Opened' if (($hasActiveToken -eq $false) -or ($sessionIsOpened -ne $true)) { #If there is no active user token or opened session then ensure that we remove the old session $shouldRemoveCurrentSession = $true; } } } if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened') -or ($shouldRemoveCurrentSession -eq $true)) { # Import the module once more to ensure that New-ExoPSSession is present Import-Module $global:_EXO_ModulePath -Cmdlet New-ExoPSSession; Write-PSImplicitRemotingMessage ('Creating a new Remote PowerShell session using Modern Authentication for implicit remoting of "{0}" command ...' -f $commandName) $session = New-ExoPSSession -PreviousSession $script:PSSession if ($session -ne $null) { if ($shouldRemoveCurrentSession -eq $true) { Remove-PSSession $script:PSSession } # Import the latest session to ensure that the next cmdlet call would occur on the new PSSession instance. $PSSessionModuleInfo = Import-PSSession $session -AllowClobber -DisableNameChecking -CommandName $script:MyModule.CommandName -FormatTypeName $script:MyModule.FormatTypeName # Add the name of the module to clean up in case of removing the broken session $session | Add-Member -NotePropertyName "CurrentModuleName" -NotePropertyValue $PSSessionModuleInfo.Name $CurrentModule = Import-Module $PSSessionModuleInfo.Path -Global -DisableNameChecking -Prefix $script:MyModule.ModulePrefix -PassThru $CurrentModule | Add-Member -NotePropertyName "ModulePrefix" -NotePropertyValue $script:MyModule.Prefix $CurrentModule | Add-Member -NotePropertyName "CommandName" -NotePropertyValue $script:MyModule.CommandName $CurrentModule | Add-Member -NotePropertyName "FormatTypeName" -NotePropertyValue $script:MyModule.$FormatTypeName $session | Add-Member -NotePropertyName "PreviousModuleName" -NotePropertyValue $script:MyModule.Name UpdateImplicitRemotingHandler $script:PSSession = $session } } if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened')) { throw 'No session has been associated with this implicit remoting module' } return [Management.Automation.Runspaces.PSSession]$script:PSSession }} } } } <# .SYNOPSIS Extract organization name from UserPrincipalName #> function Get-OrgNameFromUPN { param([string] $UPN) $fields = $UPN -split '@' return $fields[-1] } <# .SYNOPSIS Get the command from the given module #> function global:Get-WrappedCommand { param( [string] $CommandName, [string] $ModuleName, [string] $CommandType) $cmd = Get-Command -Name $CommandName -Module $ModuleName -CommandType $CommandType -All return $cmd } ############# Helper Functions End ############# ###### Begin Main ###### $EOPConnectionInProgress = $false function Connect-ExchangeOnline { [CmdletBinding()] param( # Connection Uri for the Remote PowerShell endpoint [string] $ConnectionUri = '', # Azure AD Authorization endpoint Uri that can issue the OAuth2 access tokens [string] $AzureADAuthorizationEndpointUri = '', # Exchange Environment name [Microsoft.Exchange.Management.RestApiClient.ExchangeEnvironment] $ExchangeEnvironmentName = 'O365Default', # PowerShell session options to be used when opening the Remote PowerShell session [System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption = $null, # Switch to bypass use of mailbox anchoring hint. [switch] $BypassMailboxAnchoring = $false, # Delegated Organization Name [string] $DelegatedOrganization = '', # Prefix [string] $Prefix = '', # Show Banner of Exchange cmdlets Mapping and recent updates [switch] $ShowBanner = $true, # Skip session import [switch] $SkipImportSession = $false, #Cmdlets to Import for rps cmdlets , by default it would bring all [string[]] $CommandName = @("*"), #The way the output objects would be printed on the console [string[]] $FormatTypeName = @("*") ) DynamicParam { if (($isCloudShell = IsCloudShellEnvironment) -eq $false) { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # User Principal Name or email address of the user $UserPrincipalName = New-Object System.Management.Automation.RuntimeDefinedParameter('UserPrincipalName', [string], $attributeCollection) $UserPrincipalName.Value = '' # User Credential to Logon $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection) $Credential.Value = $null # Certificate $Certificate = New-Object System.Management.Automation.RuntimeDefinedParameter('Certificate', [System.Security.Cryptography.X509Certificates.X509Certificate2], $attributeCollection) $Certificate.Value = $null # Certificate Path $CertificateFilePath = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificateFilePath', [string], $attributeCollection) $CertificateFilePath.Value = '' # Certificate Password $CertificatePassword = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificatePassword', [System.Security.SecureString], $attributeCollection) $CertificatePassword.Value = $null # Certificate Thumbprint $CertificateThumbprint = New-Object System.Management.Automation.RuntimeDefinedParameter('CertificateThumbprint', [string], $attributeCollection) $CertificateThumbprint.Value = '' # Application Id $AppId = New-Object System.Management.Automation.RuntimeDefinedParameter('AppId', [string], $attributeCollection) $AppId.Value = '' # Organization $Organization = New-Object System.Management.Automation.RuntimeDefinedParameter('Organization', [string], $attributeCollection) $Organization.Value = '' # Switch to collect telemetry on command execution. $EnableErrorReporting = New-Object System.Management.Automation.RuntimeDefinedParameter('EnableErrorReporting', [switch], $attributeCollection) $EnableErrorReporting.Value = $false # Where to store EXO command telemetry data. By default telemetry is stored in the directory "%TEMP%/EXOTelemetry" in the file : EXOCmdletTelemetry-yyyymmdd-hhmmss.csv. $LogDirectoryPath = New-Object System.Management.Automation.RuntimeDefinedParameter('LogDirectoryPath', [string], $attributeCollection) $LogDirectoryPath.Value = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "EXOCmdletTelemetry") # Create a new attribute and valiate set against the LogLevel $LogLevelAttribute = New-Object System.Management.Automation.ParameterAttribute $LogLevelAttribute.Mandatory = $false $LogLevelAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $LogLevelAttributeCollection.Add($LogLevelAttribute) $LogLevelList = @([Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::Default, [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::All) $ValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogLevelList) $LogLevel = New-Object System.Management.Automation.RuntimeDefinedParameter('LogLevel', [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel], $LogLevelAttributeCollection) $LogLevel.Attributes.Add($ValidateSet) # EXO params start # Switch to track perfomance $TrackPerformance = New-Object System.Management.Automation.RuntimeDefinedParameter('TrackPerformance', [bool], $attributeCollection) $TrackPerformance.Value = $false # Flag to enable or disable showing the number of objects written $ShowProgress = New-Object System.Management.Automation.RuntimeDefinedParameter('ShowProgress', [bool], $attributeCollection) $ShowProgress.Value = $false # Switch to enable/disable Multi-threading in the EXO cmdlets $UseMultithreading = New-Object System.Management.Automation.RuntimeDefinedParameter('UseMultithreading', [bool], $attributeCollection) $UseMultithreading.Value = $true # Pagesize Param $PageSize = New-Object System.Management.Automation.RuntimeDefinedParameter('PageSize', [uint32], $attributeCollection) $PageSize.Value = 1000 # Switch to MSI auth $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection) $Device.Value = $false # Switch to CmdInline parameters $InlineCredential = New-Object System.Management.Automation.RuntimeDefinedParameter('InlineCredential', [switch], $attributeCollection) $InlineCredential.Value = $false # EXO params end $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('UserPrincipalName', $UserPrincipalName) $paramDictionary.Add('Credential', $Credential) $paramDictionary.Add('Certificate', $Certificate) $paramDictionary.Add('CertificateFilePath', $CertificateFilePath) $paramDictionary.Add('CertificatePassword', $CertificatePassword) $paramDictionary.Add('AppId', $AppId) $paramDictionary.Add('Organization', $Organization) $paramDictionary.Add('EnableErrorReporting', $EnableErrorReporting) $paramDictionary.Add('LogDirectoryPath', $LogDirectoryPath) $paramDictionary.Add('LogLevel', $LogLevel) $paramDictionary.Add('TrackPerformance', $TrackPerformance) $paramDictionary.Add('ShowProgress', $ShowProgress) $paramDictionary.Add('UseMultithreading', $UseMultithreading) $paramDictionary.Add('PageSize', $PageSize) if($PSEdition -eq 'Core') { $paramDictionary.Add('Device', $Device) $paramDictionary.Add('InlineCredential', $InlineCredential); # We do not want to expose certificate thumprint in Linux as it is not feasible there. if($IsWindows) { $paramDictionary.Add('CertificateThumbprint', $CertificateThumbprint); } } else { $paramDictionary.Add('CertificateThumbprint', $CertificateThumbprint); } return $paramDictionary } else { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # Switch to MSI auth $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection) $Device.Value = $false # Switch to collect telemetry on command execution. $EnableErrorReporting = New-Object System.Management.Automation.RuntimeDefinedParameter('EnableErrorReporting', [switch], $attributeCollection) $EnableErrorReporting.Value = $false # Where to store EXO command telemetry data. By default telemetry is stored in the directory "%TEMP%/EXOTelemetry" in the file : EXOCmdletTelemetry-yyyymmdd-hhmmss.csv. $LogDirectoryPath = New-Object System.Management.Automation.RuntimeDefinedParameter('LogDirectoryPath', [string], $attributeCollection) $LogDirectoryPath.Value = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "EXOCmdletTelemetry") # Create a new attribute and valiate set against the LogLevel $LogLevelAttribute = New-Object System.Management.Automation.ParameterAttribute $LogLevelAttribute.Mandatory = $false $LogLevelAttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $LogLevelAttributeCollection.Add($LogLevelAttribute) $LogLevelList = @([Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::Default, [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel]::All) $ValidateSet = New-Object System.Management.Automation.ValidateSetAttribute($LogLevelList) $LogLevel = New-Object System.Management.Automation.RuntimeDefinedParameter('LogLevel', [Microsoft.Online.CSE.RestApiPowerShellModule.Instrumentation.LogLevel], $LogLevelAttributeCollection) $LogLevel.Attributes.Add($ValidateSet) # Switch to CmdInline parameters $InlineCredential = New-Object System.Management.Automation.RuntimeDefinedParameter('InlineCredential', [switch], $attributeCollection) $InlineCredential.Value = $false # User Credential to Logon $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection) $Credential.Value = $null $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('Device', $Device) $paramDictionary.Add('EnableErrorReporting', $EnableErrorReporting) $paramDictionary.Add('LogDirectoryPath', $LogDirectoryPath) $paramDictionary.Add('LogLevel', $LogLevel) $paramDictionary.Add('Credential', $Credential) $paramDictionary.Add('InlineCredential', $InlineCredential); return $paramDictionary } } process { # Validate parameters if (($ConnectionUri -ne '') -and (-not (Test-Uri $ConnectionUri))) { throw "Invalid ConnectionUri parameter '$ConnectionUri'" } if (($AzureADAuthorizationEndpointUri -ne '') -and (-not (Test-Uri $AzureADAuthorizationEndpointUri))) { throw "Invalid AzureADAuthorizationEndpointUri parameter '$AzureADAuthorizationEndpointUri'" } if (($Prefix -ne '')) { if ($Prefix -notmatch '^[a-z0-9]+$') { throw "Use of any special characters in the Prefix string is not supported." } if ($Prefix -eq 'EXO') { throw "Prefix 'EXO' is a reserved Prefix, please use a different prefix." } } if ($ShowBanner -eq $true) { Print-Details; } if (($ConnectionUri -ne '') -and ($AzureADAuthorizationEndpointUri -eq '')) { Write-Host -ForegroundColor Green "Using ConnectionUri:'$ConnectionUri', in the environment:'$ExchangeEnvironmentName'." } if (($AzureADAuthorizationEndpointUri -ne '') -and ($ConnectionUri -eq '')) { Write-Host -ForegroundColor Green "Using AzureADAuthorizationEndpointUri:'$AzureADAuthorizationEndpointUri', in the environment:'$ExchangeEnvironmentName'." } # Keep track of error count at beginning. $errorCountAtStart = $global:Error.Count; try { $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellGalleryModule.dll"; $ModulePath = [System.IO.Path]::Combine($PSScriptRoot, $ExoPowershellModule); Write-Verbose "ExchangeEnvironment : $ExchangeEnvironmentName" Write-Verbose "ConnectionUri : $ConnectionUri" Write-Verbose "AzureADAuthorizationEndpointUri : $AzureADAuthorizationEndpointUri" Write-Verbose "DelegatedOrganization : $DelegatedOrganization" Write-Verbose "Prefix : $Prefix" Write-Verbose ("FormatTypeName :" + [String]::Join(' ,', $FormatTypeName)) Write-Verbose ("CommandName :" + [String]::Join(' ,', $CommandName)) if ($isCloudShell -eq $false) { $global:_EXO_EnableErrorReporting = $EnableErrorReporting.Value; } Import-Module $ModulePath; $global:_EXO_ModulePath = $ModulePath; if ($isCloudShell -eq $false) { $PSSession = New-ExoPSSession -ExchangeEnvironmentName $ExchangeEnvironmentName -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -UserPrincipalName $UserPrincipalName.Value -PSSessionOption $PSSessionOption -Credential $Credential.Value -BypassMailboxAnchoring:$BypassMailboxAnchoring -DelegatedOrg $DelegatedOrganization -Certificate $Certificate.Value -CertificateFilePath $CertificateFilePath.Value -CertificatePassword $CertificatePassword.Value -CertificateThumbprint $CertificateThumbprint.Value -AppId $AppId.Value -Organization $Organization.Value -Device:$Device.Value -InlineCredential:$InlineCredential.Value } else { $PSSession = New-ExoPSSession -ExchangeEnvironmentName $ExchangeEnvironmentName -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -Credential $Credential.Value -PSSessionOption $PSSessionOption -BypassMailboxAnchoring:$BypassMailboxAnchoring -Device:$Device.Value -InlineCredential:$InlineCredential.Value -DelegatedOrg $DelegatedOrganization } if ($PSSession -ne $null) { if ($SkipImportSession -eq $false) { $PSSessionModuleInfo = Import-PSSession $PSSession -AllowClobber -DisableNameChecking -CommandName $CommandName -FormatTypeName $FormatTypeName # Add the name of the module to clean up in case of removing the broken session $PSSession | Add-Member -NotePropertyName "CurrentModuleName" -NotePropertyValue $PSSessionModuleInfo.Name # Import the above module globally. This is needed as with using psm1 files, # any module which is dynamically loaded in the nested module does not reflect globally. $CurrentModule = Import-Module $PSSessionModuleInfo.Path -Global -DisableNameChecking -Prefix $Prefix -PassThru $CurrentModule | Add-Member -NotePropertyName "ModulePrefix" -NotePropertyValue $Prefix $CurrentModule | Add-Member -NotePropertyName "CommandName" -NotePropertyValue $CommandName $CurrentModule | Add-Member -NotePropertyName "FormatTypeName" -NotePropertyValue $FormatTypeName UpdateImplicitRemotingHandler } # Import the REST module $RestPowershellModule = "Microsoft.Exchange.Management.RestApiClient.dll"; $RestModulePath = [System.IO.Path]::Combine($PSScriptRoot, $RestPowershellModule); Import-Module $RestModulePath -Cmdlet Set-ExoAppSettings; # If we are configured to collect telemetry, add telemetry wrappers. if ($EnableErrorReporting.Value -eq $true) { $FilePath = Add-EXOClientTelemetryWrapper -Organization (Get-OrgNameFromUPN -UPN $UserPrincipalName.Value) -PSSessionModuleName $PSSessionModuleInfo.Name -LogDirectoryPath $LogDirectoryPath.Value $global:_EXO_TelemetryFilePath = $FilePath[0] Import-Module $FilePath[1] -DisableNameChecking Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Connect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid if ($EOPConnectionInProgress -eq $false) { # Set the AppSettings Set-ExoAppSettings -ShowProgress $ShowProgress.Value -PageSize $PageSize.Value -UseMultithreading $UseMultithreading.Value -TrackPerformance $TrackPerformance.Value -EnableErrorReporting $true -LogDirectoryPath $LogDirectoryPath.Value -LogLevel $LogLevel.Value } } else { if ($EOPConnectionInProgress -eq $false) { # Set the AppSettings disabling the logging Set-ExoAppSettings -ShowProgress $ShowProgress.Value -PageSize $PageSize.Value -UseMultithreading $UseMultithreading.Value -TrackPerformance $TrackPerformance.Value -EnableErrorReporting $false } } } } catch { # If telemetry is enabled, log errors generated from this cmdlet also. if ($EnableErrorReporting.Value -eq $true) { $errorCountAtProcessEnd = $global:Error.Count if ($global:_EXO_TelemetryFilePath -eq $null) { $global:_EXO_TelemetryFilePath = New-EXOClientTelemetryFilePath -LogDirectoryPath $LogDirectoryPath.Value # Import the REST module $RestPowershellModule = "Microsoft.Exchange.Management.RestApiClient.dll"; $RestModulePath = [System.IO.Path]::Combine($PSScriptRoot, $RestPowershellModule); Import-Module $RestModulePath -Cmdlet Set-ExoAppSettings; # Set the AppSettings Set-ExoAppSettings -ShowProgress $ShowProgress.Value -PageSize $PageSize.Value -UseMultithreading $UseMultithreading.Value -TrackPerformance $TrackPerformance.Value -ExchangeEnvironmentName $ExchangeEnvironmentName -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -EnableErrorReporting $true -LogDirectoryPath $LogDirectoryPath.Value -LogLevel $LogLevel.Value } # Log errors which are encountered during Connect-ExchangeOnline execution. Write-Warning("Writing Connect-ExchangeOnline error log to " + $global:_EXO_TelemetryFilePath) Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Connect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid -ErrorObject $global:Error -ErrorRecordsToConsider ($errorCountAtProcessEnd - $errorCountAtStart) } throw $_ } } } function Connect-IPPSSession { [CmdletBinding()] param( # Connection Uri for the Remote PowerShell endpoint [string] $ConnectionUri = 'https://ps.compliance.protection.outlook.com/PowerShell-LiveId', # Azure AD Authorization endpoint Uri that can issue the OAuth2 access tokens [string] $AzureADAuthorizationEndpointUri = 'https://login.windows.net/common', # Delegated Organization Name [string] $DelegatedOrganization = '', # PowerShell session options to be used when opening the Remote PowerShell session [System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption = $null, # Switch to bypass use of mailbox anchoring hint. [switch] $BypassMailboxAnchoring = $false, # Prefix [string] $Prefix = '', #Cmdlets to Import, by default it would bring all [string[]] $CommandName = @("*"), #The way the output objects would be printed on the console [string[]] $FormatTypeName = @("*") ) DynamicParam { if (($isCloudShell = IsCloudShellEnvironment) -eq $false) { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # User Principal Name or email address of the user $UserPrincipalName = New-Object System.Management.Automation.RuntimeDefinedParameter('UserPrincipalName', [string], $attributeCollection) $UserPrincipalName.Value = '' # User Credential to Logon $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection) $Credential.Value = $null $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('UserPrincipalName', $UserPrincipalName) $paramDictionary.Add('Credential', $Credential) return $paramDictionary } else { $attributes = New-Object System.Management.Automation.ParameterAttribute $attributes.Mandatory = $false $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] $attributeCollection.Add($attributes) # Switch to MSI auth $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection) $Device.Value = $false $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary $paramDictionary.Add('Device', $Device) return $paramDictionary } } process { if ( $PSEdition -eq 'Core' -and $AzureADAuthorizationEndpointUri -eq "https://login.windows.net/common") { $AzureADAuthorizationEndpointUri = "https://login.windows.net/organizations"; } try { $EOPConnectionInProgress = $true if ($isCloudShell -eq $false) { Connect-ExchangeOnline -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -UserPrincipalName $UserPrincipalName.Value -PSSessionOption $PSSessionOption -Credential $Credential.Value -BypassMailboxAnchoring:$BypassMailboxAnchoring -ShowBanner:$false -DelegatedOrganization $DelegatedOrganization -Prefix $Prefix -CommandName $CommandName -FormatTypeName $FormatTypeName } else { Connect-ExchangeOnline -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -PSSessionOption $PSSessionOption -BypassMailboxAnchoring:$BypassMailboxAnchoring -Device:$Device.Value -ShowBanner:$false -DelegatedOrganization $DelegatedOrganization -Prefix $Prefix -CommandName $CommandName -FormatTypeName $FormatTypeName } } finally { $EOPConnectionInProgress = $false } } } function Disconnect-ExchangeOnline { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')] param() process { if ($PSCmdlet.ShouldProcess( "Running this cmdlet clears all active sessions created using Connect-ExchangeOnline or Connect-IPPSSession.", "Press(Y/y/A/a) if you want to continue.", "Running this cmdlet clears all active sessions created using Connect-ExchangeOnline or Connect-IPPSSession. ")) { # Keep track of error count at beginning. $errorCountAtStart = $global:Error.Count; try { # Import the module once more to ensure that Test-ActiveToken is present Import-Module $global:_EXO_ModulePath -Cmdlet Clear-ActiveToken; $existingPSSession = Get-PSSession | Where-Object {$_.ConfigurationName -like "Microsoft.Exchange" -and $_.Name -like "ExchangeOnlineInternalSession*"} if ($existingPSSession.count -gt 0) { for ($index = 0; $index -lt $existingPSSession.count; $index++) { $session = $existingPSSession[$index] Remove-PSSession -session $session Write-Host "Removed the PSSession $($session.Name) connected to $($session.ComputerName)" # Remove any active access token from the cache Clear-ActiveToken -TokenProvider $session.TokenProvider # Remove any previous modules loaded because of the current PSSession if ($session.PreviousModuleName -ne $null) { if ((Get-Module $session.PreviousModuleName).Count -ne 0) { Remove-Module -Name $session.PreviousModuleName -ErrorAction SilentlyContinue } $session.PreviousModuleName = $null } # Remove any leaked module in case of removal of broken session object if ($session.CurrentModuleName -ne $null) { if ((Get-Module $session.CurrentModuleName).Count -ne 0) { Remove-Module -Name $session.CurrentModuleName -ErrorAction SilentlyContinue } } } } Write-Host "Disconnected successfully !" if ($global:_EXO_EnableErrorReporting -eq $true) { if ($global:_EXO_TelemetryFilePath -eq $null) { $global:_EXO_TelemetryFilePath = New-EXOClientTelemetryFilePath } Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Disconnect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid } } catch { # If telemetry is enabled, log errors generated from this cmdlet also. if ($global:_EXO_EnableErrorReporting -eq $true) { $errorCountAtProcessEnd = $global:Error.Count if ($global:_EXO_TelemetryFilePath -eq $null) { $global:_EXO_TelemetryFilePath = New-EXOClientTelemetryFilePath } # Log errors which are encountered during Disconnect-ExchangeOnline execution. Write-Warning("Writing Disconnect-ExchangeOnline errors to " + $global:_EXO_TelemetryFilePath) Push-EXOTelemetryRecord -TelemetryFilePath $global:_EXO_TelemetryFilePath -CommandName Disconnect-ExchangeOnline -CommandParams $PSCmdlet.MyInvocation.BoundParameters -OrganizationName $global:_EXO_ExPSTelemetryOrganization -ScriptName $global:_EXO_ExPSTelemetryScriptName -ScriptExecutionGuid $global:_EXO_ExPSTelemetryScriptExecutionGuid -ErrorObject $global:Error -ErrorRecordsToConsider ($errorCountAtProcessEnd - $errorCountAtStart) } throw $_ } } } } |