Private/O365MFASupport/Connect-MDSEXOPSSession.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
function Connect-MDSEXOPSSession {
    <#
        .SYNOPSIS
            To connect in other Office 365 offerings, use the following settings:
             - Office 365 operated by 21Vianet: -ConnectionURI https://partner.outlook.cn/PowerShell-LiveID -AzureADAuthorizationEndpointUri https://login.chinacloudapi.cn/common
             - Office 365 Germany: -ConnectionURI https://outlook.office.de/PowerShell-LiveID -AzureADAuthorizationEndpointUri https://login.microsoftonline.de/common
         
            - PSSessionOption accept object created using New-PSSessionOption
 
            - EnableEXOTelemetry To collect telemetry on Exchange cmdlets. Default value is False.
 
            - TelemetryFilePath Telemetry records will be written to this file. Default value is %TMP%\EXOCmdletTelemetry\EXOCmdletTelemetry-yyyymmdd-hhmmss.csv
 
            - DoLogErrorMessage Switch to enable/disable error message logging in telemetry file. Default value is True.
 
        .DESCRIPTION
            This PowerShell module allows you to connect to Exchange Online service
        .LINK
            https://go.microsoft.com/fwlink/p/?linkid=837645
    #>

    [CmdletBinding()]
    param(
        # Connection Uri for the Remote PowerShell endpoint
        [string] $ConnectionUri = 'https://outlook.office365.com/PowerShell-LiveId',

        # Azure AD Authorization endpoint Uri that can issue the OAuth2 access tokens
        [string] $AzureADAuthorizationEndpointUri = 'https://login.windows.net/common',

        # 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
    )
    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
            
            # Switch to collect telemetry on command execution.
            $EnableEXOTelemetry = New-Object System.Management.Automation.RuntimeDefinedParameter('EnableEXOTelemetry', [switch], $attributeCollection)
            $EnableEXOTelemetry.Value = $false
            
            # Where to store EXO command telemetry data. By default telemetry is stored in
            # %TMP%/EXOTelemetry/EXOCmdletTelemetry-yyyymmdd-hhmmss.csv.
            $TelemetryFilePath = New-Object System.Management.Automation.RuntimeDefinedParameter('TelemetryFilePath', [string], $attributeCollection)
            $TelemetryFilePath.Value = ''
            
            # Switch to Disable error message logging in telemetry file.
            $DoLogErrorMessage = New-Object System.Management.Automation.RuntimeDefinedParameter('DoLogErrorMessage', [switch], $attributeCollection)
            $DoLogErrorMessage.Value = $true
            
            $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add('UserPrincipalName', $UserPrincipalName)
            $paramDictionary.Add('Credential', $Credential)
            $paramDictionary.Add('EnableEXOTelemetry', $EnableEXOTelemetry)
            $paramDictionary.Add('TelemetryFilePath', $TelemetryFilePath)
            $paramDictionary.Add('DoLogErrorMessage', $DoLogErrorMessage)
            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
        }
    }
    begin {
        $MFAExchangeModulePath = Get-MFAExchangeModulePath -ErrorAction Stop
        . "$MFAExchangeModulePath\CreateExoPSSession.ps1"
    }
    process {
        # Validate parameters
        if (-not (Test-Uri $ConnectionUri)) {
            throw "Invalid ConnectionUri parameter '$ConnectionUri'"
        }
        if (-not (Test-Uri $AzureADAuthorizationEndpointUri)) {
            throw "Invalid AzureADAuthorizationEndpointUri parameter '$AzureADAuthorizationEndpointUri'"
        }

        # Keep track of error count at beginning.
        $errorCountAtStart = $global:Error.Count
        
        try {
            # Cleanup old ps sessions
            $ComputerName = 'outlook.office365.com'
            If ((Get-PSSession).Where{$_.ComputerName -match $ComputerName}) {
                $WarningMsg = 'A previous connection to {0} has been removed.' -f $ComputerName
                Write-Warning $WarningMsg
            }

            $MFAExchangeModulePath = Get-MFAExchangeModulePath -ErrorAction Stop
            $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellModule.dll"
            $ModulePath = [System.IO.Path]::Combine($MFAExchangeModulePath, $ExoPowershellModule)

            $global:ConnectionUri = $ConnectionUri
            $global:AzureADAuthorizationEndpointUri = $AzureADAuthorizationEndpointUri
            $global:PSSessionOption = $PSSessionOption
            $global:BypassMailboxAnchoring = $BypassMailboxAnchoring

            if ($isCloudShell -eq $false) {
                $global:UserPrincipalName = $UserPrincipalName.Value
                $global:Credential = $Credential.Value
            }
            else {
                $global:Device = $Device.Value
            }

            Import-Module $ModulePath
            
            $newExoPSSessionSplat = @{
                BypassMailboxAnchoring          = $BypassMailboxAnchoring
                PSSessionOption                 = $PSSessionOption
                ConnectionUri                   = $ConnectionUri
                AzureADAuthorizationEndpointUri = $AzureADAuthorizationEndpointUri
            }

            if ($isCloudShell -eq $false) {
                $newExoPSSessionSplat.Add('UserPrincipalName', $UserPrincipalName.Value)
                $newExoPSSessionSplat.Add('Credential', $Credential.Value)
                $PSSession = New-ExoPSSession @newExoPSSessionSplat
            }
            else {
                $newExoPSSessionSplat.Add('Device', $Device.Value)
                $PSSession = New-ExoPSSession @newExoPSSessionSplat
            }

            if ($null -ne $PSSession) {
                $PSSessionModuleInfo = Import-PSSession $PSSession -AllowClobber
                UpdateImplicitRemotingHandler

                # If we are configured to collect telemetry, add telemetry wrappers.
                if ($EnableEXOTelemetry.Value -eq $true) {
                    $addEXOClientTelemetryWrapperSplat = @{
                        TelemetryFilePath   = $TelemetryFilePath.Value
                        DoLogErrorMessage   = $DoLogErrorMessage.Value
                        PSSessionModuleName = $PSSessionModuleInfo.Name
                        Organization        = (Get-OrgNameFromUPN -UPN $UserPrincipalName.Value)
                    }
                    $TelemetryFilePath.Value = Add-EXOClientTelemetryWrapper @addEXOClientTelemetryWrapperSplat
                }
            }
        }
        catch {
            throw $_
        }
        Finally {
            # If telemetry is enabled, log errors generated from this cmdlet also.
            if ($EnableEXOTelemetry.Value -eq $true) {
                $errorCountAtProcessEnd = $global:Error.Count 

                # If we have any errors during this cmdlet execution, log it.
                if ($errorCountAtProcessEnd -gt $errorCountAtStart) {
                    if (!$TelemetryFilePath.Value) {
                        $TelemetryFilePath.Value = New-EXOClientTelemetryFilePath
                    }

                    # Log errors which are encountered during Connect-EXOPSSession execution.
                    Write-Warning("Writing Connect-EXOPSSession errors to " + $TelemetryFilePath.Value)
                    
                    $pushEXOTelemetryRecordSplat = @{
                        CommandName            = 'Connect-EXOPSSession'
                        ScriptName             = $global:ExPSTelemetryScriptName
                        OrganizationName       = $global:ExPSTelemetryOrganization
                        ScriptExecutionGuid    = $global:ExPSTelemetryScriptExecutionGuid
                        TelemetryFilePath      = $TelemetryFilePath.Value
                        ErrorObject            = $global:Error
                        ErrorRecordsToConsider = ($errorCountAtProcessEnd - $errorCountAtStart)
                    }
                    Push-EXOTelemetryRecord @pushEXOTelemetryRecordSplat 
                }
            }
        }
    }
    end {}
}