ZIM.Office365.psm1

#### FUNCTIONS ####

Function Start-ADSync{
<#
.Synopsis
    Starts an AD sync to Azure AD on the remote server
.DESCRIPTION
    When a sync to Azure AD is required in a script, then this function can be used to remotely start the sync
    to Azure AD. Specify the policy type (delta or intial) and the module path (if the directory is not default)
.EXAMPLE
    PS C:\Scripts> Start-ADSync -ComputerName Server01
 
    PSComputerName RunspaceId Result
    -------------- ---------- ------
    Server01 d5490783-7f1f-46bb-aa97-fa8d4cabdd32 Success
 
    Successfully started the AD Sync Cycle on target Server01
 
#>

    [CmdletBinding(SupportsShouldProcess=$true,
                    ConfirmImpact='Medium'
                    )]
    Param
    (
        #Specify the AAD Connect Server
        [Parameter(Mandatory=$true,
                    ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true
                    )]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [Alias("ADConnectServer","DirSyncServer","ADSyncServer")]
        [string]
        $ComputerName,

        #Specifiy the AAD Connect Module Path on the AAD Connect Server
        [Parameter(ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true
                    )]
        [Alias("Module","Path")]
        [string]
        $modulePath = "C:\Program Files\Microsoft Azure AD Sync\Bin\ADSync\ADSync.psd1",

        #Specifiy the policy type that needs to sync
        [Parameter()]
        [Alias("Type")]
        [ValidateSet('Delta','Initial')]
        [string]
        $policyType = "Delta",

        #Switch to turn on Error logging
        [Switch]$ErrorLog,
        [String]$LogFile = '.\Force-ADSync.log'
    )

    Process
    {
        #starts transcript to errorlog when error log is specified
        if($Errorlog){
            Start-Transcript -Path $LogFile -Append
        }

        if ($pscmdlet.ShouldProcess($ComputerName, "Making a connection to the AD Connect Server"))
        {
            #trying to make a connection to the AD Connect Server
            Try{
                $s = New-PSSession -ComputerName $ComputerName -EA Stop
                Write-Verbose "Successfully connected to the AD Connect server $Computername"
            }
            Catch{
                Write-Warning "Unable to Connect to the AD Connect Server!"
                Write-Verbose $error[0]
                If ($ErrorLog){
                        Stop-Transcript
                    }

                break
            }
        }


        if ($pscmdlet.ShouldProcess($ComputerName, "Importing the AD Connect Module"))
        {
            #Setting the script block in a variable
            $scriptBlock = {
                param( [String] $modulePath )
                Import-Module $modulePath
            }

            #Importing the AD Connect Module
            Try{
                Invoke-Command -Session $s -ScriptBlock $scriptBlock -ArgumentList $(,$modulePath) -ErrorAction inquire
                Write-Verbose "Successfully imported AD Connect Module from location $modulePath"
            }
            Catch{
                Write-Warning "Unable to import AD Connect Module!"
                Write-Verbose $error[0]
                Remove-PSSession $s -ErrorAction SilentlyContinue
                If ($ErrorLog){
                        Stop-Transcript
                }
                break
            }
        }

        if ($pscmdlet.ShouldProcess($ComputerName, "Performing an AD Sync Cycle"))
        {
            #Setting the script block in a variable
            $scriptBlock = {
                param( [String] $policyType )
                Start-ADSyncSyncCycle -PolicyType $policyType
            }

            #starting the AD Sync Cycle on the remote server
            try{
                Invoke-Command -Session $s -ScriptBlock $scriptBlock -ArgumentList $(,$policyType) -ErrorAction Stop
                Write-Host "Successfully started the AD Sync Cycle on target $ComputerName" -ForegroundColor Green
            }
            catch{
                Write-Warning "Unable to start the AD Sync Cylce!"
                Write-Verbose $error[0]
            }
            Finally{
                #removing the session and stopping the transcript
                Remove-PSSession $s -ErrorAction SilentlyContinue
                If ($ErrorLog){
                        Stop-Transcript
                }
            }
        }
    }
}


Function Connect-O365{
<#
.Synopsis
    Connects to Office 365
.DESCRIPTION
    Connects to all instances of Office 365 for the tenant
.EXAMPLE
    PS C:\Users> Connect-O365
 
.EXAMPLE
    PS C:\Users> Connect-O365 -Proxy:$true
#>


    [CmdletBinding(SupportsShouldProcess=$true,
                    ConfirmImpact='Medium'
                    )]
    Param
    (
        #Specifiy if a proxy is required. Default the IE Proxy settings are used
        [Parameter()]
        [switch]
        $Proxy = $false
    )

    process
    {
        #Imports the installed Azure Active Directory module.
        Import-Module MSOnline

        #Capture administrative credential for future connections.
        $credential = get-credential

        #Set organization variable
        $org = $credential.UserName.Split("@")[1]
        $spoAdminURI = "https://" + $org.Split(".")[0] +"-admin.sharepoint.com"

        #Set proxy settings
        if($proxy -eq $true){
            $proxySettings = New-PSSessionOption -ProxyAccessType IEConfig

            #Establishes Online Services connection to Office 365 Management Layer.
            Connect-MsolService -Credential $credential

            #login to Azure
            Login-AzureRmAccount -Credential $credential

            #Imports SharePoint Online session commands into your local Windows PowerShell session.
            Import-Module Microsoft.Online.Sharepoint.PowerShell
            Connect-SPOService -url $spoAdminURI -Credential $credential

            #Creates an Exchange Online session using defined credential.
            $ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $credential -Authentication "Basic" -AllowRedirection -SessionOption $proxySettings
            Import-Module (Import-PSSession $ExchangeSession -AllowClobber) -Global

            #Connection to Skype Online
            Import-Module SkypeOnlineConnector
            $sfboSession = New-CsOnlineSession -Credential $credential -SessionOption $proxySettings
            Import-Module (Import-PSSession $sfboSession -AllowClobber) -Global

            #Connect to Azure AD
            Connect-AzureAD -Credential $credential
        }
        else{
            #Establishes Online Services connection to Office 365 Management Layer.
            Connect-MsolService -Credential $credential

            #login to Azure
            Login-AzureRmAccount -Credential $credential


            #Imports SharePoint Online session commands into your local Windows PowerShell session.
            Import-Module Microsoft.Online.Sharepoint.PowerShell
            Connect-SPOService -url $spoAdminURI -Credential $credential

            #Creates an Exchange Online session using defined credential.
            $ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $credential -Authentication "Basic" -AllowRedirection
            Import-Module (Import-PSSession $ExchangeSession -AllowClobber) -Global

            #Connection to Skype Online
            Import-Module SkypeOnlineConnector
            $sfboSession = New-CsOnlineSession -Credential $credential
            Import-Module (Import-PSSession $sfboSession -AllowClobber) -Global

            #Connect to Azure AD
            Connect-AzureAD -Credential $credential
        }
    }
}


Function Set-O365License{
<#
.Synopsis
    Checks if the users location is configured and if not configures the location for NL and sets the required licences.
.DESCRIPTION
    Checks if the users location is configured and if not configures the location for NL and sets the required licences.
.EXAMPLE
    PS C:\WINDOWS\system32> Set-O365License -userPrincipalName test@contoso.com -licenseType EMS
 
    User test@contoso.com is licensed for Enterprise Mobility and Security!
 
.EXAMPLE
    PS C:\WINDOWS\system32> $user = import-csv c:\temp\test.csv
 
    PS C:\WINDOWS\system32> $user | foreach{Set-O365License -userPrincipalName $_.userPrincipalName -location NL -licenseType EMS,O365E3}
#>

    [CmdletBinding(SupportsShouldProcess=$true,
                    ConfirmImpact='Low')]
    Param
    (
        #Provide the UPN of the user
        [Parameter(Mandatory=$true,
                    ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNull()]
        [ValidateNotNullOrEmpty()]
        [Alias("UPN","User","emailAddress")]
        $userPrincipalName,

        #Provide the required location
        [Parameter(ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true)]
        [ValidateSet('NL','DE')]
        $location = "NL",

        #Provide the required licenses
        [Parameter(Mandatory=$true,
                    ValueFromPipeline=$true,
                    ValueFromPipelineByPropertyName=$true)]
        [ValidateSet('O365E3','EMS')]
        [Alias("license")]
        [string[]]
        $licenseType,

        #Switch to turn on Error logging
        [Switch]$ErrorLog,
        [String]$LogFile = '.\Set-Office365License.log'
    )

    Process
    {
        #start error logging if -errorlog parameter was set
        if($ErrorLog){
            Start-Transcript -Path $LogFile -Append
        }

        if ($pscmdlet.ShouldProcess("Office365", "Checking if user $userPrincipalName is present in Office365"))
        {
            try
            {
                $User = Get-MsolUser -UserPrincipalName $userPrincipalName -ErrorAction stop
                $UsageLocation = $user.UsageLocation
                $License = $user.Licenses
                Write-Verbose "User found in Office 365."
            }
            catch
            {
                Write-Warning "Unable to find the user in Office 365!"
                Write-Verbose $error[0]
                If ($ErrorLog){
                        Stop-Transcript
                }
                break
            }

        }

        if ($pscmdlet.ShouldProcess("Office 365", "Checking if the location is set for user $userPrincipalName"))
        {
            if(!($UsageLocation)){

                Write-Verbose "Usage location is not set for user $userPrincipalName. Setting location to $location."

                try
                {
                    #set usage location to $location
                    $user | Set-MsolUser -UsageLocation $location -ErrorAction stop
                    write-verbose "Usage location set for $userPrincipalName to $location successfully."
                }
                catch
                {
                    Write-Warning "Unable set the usage location for $userPrincipalName to $location!"
                    Write-Verbose $error[0]

                }
            }
            elseif($location -ne $UsageLocation)
            {
                Write-Verbose "Current usage location for the user $userPrincipalName is $UsageLocation. Changing the usage location to $location."

                try
                {
                    #set usage location to $location
                    $user | Set-MsolUser -UsageLocation $location -ErrorAction stop
                    write-verbose "Usage location set for $userPrincipalName to $location successfully."
                }
                catch
                {
                    Write-Warning "Unable set the usage location for $userPrincipalName to $location!"
                    Write-Verbose $error[0]
                }
            }
            else{
                write-verbose "Current usage location for the user is $usageLocation. No action required."
            }


        }

        if ($pscmdlet.ShouldProcess("Office 365", "Checking if the required licenses are available for the user $userPrincipalName"))
        {
            $accountSKUs = Get-MsolAccountSku

            #checking for the licenses and setting these when not assigned
            switch($licenseType){
                    O365E3
                    {
                        if($License.accountsku.skupartnumber -match "ENTERPRISEPACK"){
                            #User already has an Enterprise License
                            write-host "$userPrincipalName already has an O365 E3 License" -ForegroundColor Green
                        }
                        else
                        {
                            #Enabling O365 E3 License
                            write-verbose "Enabling O365 Enterprise license for User $userPrincipalName"

                            try{
                                $user | Set-MsolUserLicense -AddLicenses ($accountSKUs|where{$_.AccountSkuId -match "ENTERPRISEPACK"}).accountSkuID -ea stop
                                write-host "User $userPrincipalName is licensed for O365 Enterprise E3!" -ForegroundColor Green
                            }
                            catch{
                                Write-Warning "Unable to set the Office 365 E3 License for the user $userPrincipalName!"
                                Write-Verbose $error[0]
                            }
                        }

                    }

                    EMS
                    {
                        #Checking and setting EMS License
                        if($License.accountsku.skupartnumber -match "EMS"){
                            #User already has an Enterprise License
                            write-host "$userPrincipalName already has an EMS License" -ForegroundColor Green
                        }
                        else
                        {
                            #Enabling O365 E3 License
                            write-verbose "Enabling EMS license for user $userPrincipalName"

                            try{
                                $user | Set-MsolUserLicense -AddLicenses ($accountSKUs|where{$_.AccountSkuId -match "EMS"}).accountSkuID -ea stop
                                write-host "User $userPrincipalName is licensed for Enterprise Mobility and Security!" -ForegroundColor Green
                            }
                            catch{
                                Write-Warning "Unable to set the EMS License for the user $userPrincipalName!"
                                Write-Verbose $error[0]
                            }
                        }
                    }
            }

        }

    }

    End
    {
        if($ErrorLog){
            Stop-Transcript
        }

    }
}

Function Set-MailboxDefaultPermission{
<#
.Synopsis
    Set the rights on the agenda or calendar of the user Default to Reviewer and a secretary group to Editor
.DESCRIPTION
    This CMDLET sets the mailbox permissions of the agenda (NL) or the calendar (NL) of the mailbox of the user to the default permissions.
    The user Default will be set to Reviewer
    The provided group will be set to Editor
.EXAMPLE
    Set-MailboxDefaultPermission -UserName test@contoso.com -GroupName Secretariaat
.EXAMPLE
    $users | %{Set-MailboxDefaultPermission -UserName $_.UserPrincipalName -GroupName $groupname} | out-file $report -Append
.NOTES
    Author: Michael Zimmerman
    Date: 19/02/2018
#>

    [CmdletBinding(SupportsShouldProcess=$true,
                    ConfirmImpact='Medium')]
    Param
    (
        # Provide the name of the user which mailbox permissions will be altered
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName=$true,
                    Position=0)]
        [ValidateNotNullOrEmpty()]
        [Alias("user")]
        $UserName,

        # Provide the group name which will be set to editor
        [Parameter(Mandatory=$true,
                    ValueFromPipelineByPropertyName=$true,
                    Position=1)]
        [ValidateNotNullOrEmpty()]
        [Alias("group")]
        $GroupName
    )


    Process
    {
        #get the mailbox
        try
        {
            Write-Verbose "Getting the mailbox of the user $username"
            $mailbox = get-mailbox $UserName -EA Stop
        }
        catch
        {
            Write-Error $Error[0]
            return
        }

        #check if the dutch or english calendar is used
        if($AgendaPermissions = get-MailboxFolderPermission ($mailbox.Alias + ":\Calendar") -ErrorAction silentlycontinue){
            $AgendaFolder = ($mailbox.Alias + ":\Calendar")
            Write-Verbose "The user $username has an English calendar. Continuing..."
        }
        elseif($AgendaPermissions = Get-MailboxFolderPermission ($mailbox.Alias + ":\agenda") -ErrorAction silentlycontinue){
            $AgendaFolder = ($mailbox.Alias + ":\agenda")
            Write-Verbose "The user $username has a Dutch calendar. Continuing..."
        }
        else{
            Write-Warning "Could not get the calendar information of the user $UserName."
            return
        }

        #check if the correct permissions are already set for the default user and if not set the correct permissions
        if($AgendaPermissions | where{$_.user -match "Default" -and $_.AccessRights -match "Reviewer"})
        {
            Write-verbose "The calendar permissions for mailbox $username to user Default are already set to reviewer."
        }
        else
        {
            if ($pscmdlet.ShouldProcess("mailbox $UserName", "Setting the mailbox permission of the user Default"))
            {
                try
                {
                    set-MailboxFolderPermission $AgendaFolder -user Default -AccessRights Reviewer -EA stop | out-null
                    Write-verbose "successfully added the reviewer access rights to the user Default on the mailbox $username."
                }
                catch
                {
                    Write-error "Unable to add the reviewer access rights to the user Default on the mailbox $username."
                }
            }
        }

        #check if the correct permissions are already set for the default user and if not set the correct permissions
        if($AgendaPermissions | where{$_.user -Match $GroupName -and $_.AccessRights -match "Editor"})
        {
            Write-verbose "The calendar permissions for mailbox $username to the group $groupname are already set to reviewer."
        }
        else
        {
            if ($pscmdlet.ShouldProcess("mailbox $UserName", "Setting the mailbox permission of the group $GroupName"))
            {
                try
                {
                    add-MailboxFolderPermission $AgendaFolder -user $GroupName -AccessRights Editor | Out-Null
                    Write-verbose "successfully added the Editor access rights to the group $GroupName on the mailboxcalendar of $username."
                }
                catch
                {
                    Write-error "Unable to add the Editor access rights to the group $groupname on mailboxcalendar of $username."
                }
            }
        }

        $CurrentPermissionsDefault = Get-MailboxFolderPermission $AgendaFolder |  where{$_.user -match "Default"} | select user,accessrights
        $CurrentPermissionsGroup = Get-MailboxFolderPermission $AgendaFolder |  where{$_.user -match $GroupName} | select user,accessrights

        $hash = @{

            UserName = $UserName
            "User Default Access Rights" = $CurrentPermissionsDefault.AccessRights
            "Group $GroupName Access Rights" = $CurrentPermissionsGroup.AccessRights

        }

        $Object = New-Object PSObject -Property $hash

        return $Object
    }
}

function Test-Uri
{
<#
.Synopsis Validates a given 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'
}

function Get-OrgNameFromUPN
{
<#
.SYNOPSIS Extract organization name from UserPrincipalName
#>

    param([string] $UPN)
    $fields = $UPN -split '@'
    return $fields[-1]
}


function global:UpdateImplicitRemotingHandler()
{
<#
.Synopsis Override Get-PSImplicitRemotingSession function for reconnection
#>

    $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
                )

                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) -and ($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)
                }
                if (($script:PSSession -eq $null) -or ($script:PSSession.Runspace.RunspaceStateInfo.State -ne 'Opened'))
                {
                    Write-PSImplicitRemotingMessage ('Creating a new Remote PowerShell session using MFA for implicit remoting of "{0}" command ...' -f $commandName)

                    $session = New-ExoPSSession -UserPrincipalName $global:UserPrincipalName -ConnectionUri $global:ConnectionUri -AzureADAuthorizationEndpointUri $global:AzureADAuthorizationEndpointUri -PSSessionOption $global:PSSessionOption -Credential $global:Credential -BypassMailboxAnchoring:$global:BypassMailboxAnchoring

                    if ($session -ne $null)
                    {
                        Set-PSImplicitRemotingSession -CreatedByModule $true -PSSession $session
                    }

                    RemoveBrokenOrClosedPSSession
                }
                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
            }}
        }
    }
}

function global:RemoveBrokenOrClosedPSSession()
{
<#
.Synopsis Remove broken and closed sessions
#>

    $psBroken = Get-PSSession | where-object {$_.State -like "*Broken*"}
    $psClosed = Get-PSSession | where-object {$_.State -like "*Closed*"}

    if ($psBroken.count -gt 0)
    {
        for ($index = 0; $index -lt $psBroken.count; $index++)
        {
            Remove-PSSession -session $psBroken[$index]
        }
    }

    if ($psClosed.count -gt 0)
    {
        for ($index = 0; $index -lt $psClosed.count; $index++)
        {
            Remove-PSSession -session $psClosed[$index]
        }
    }
}

function Connect-EXO
{
<#
.SYNOPSIS
    Connects to Exchange Online using MFA
.DESCRIPTION
    This function let's the user connect to Exchange Online with MFA.
 
    This function requires the installation of the Exchange Online Remote PowerShell Module (see link below)
 
    - 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.
.EXAMPLE
    C:\PS>Connect-EXO
 
    This command provides a popup and let's the user pass in credentials for Exchange Online.
.EXAMPLE
    C:\PS>Connect-EXO -UserPrincipalName test@contoso.com
 
    This command connects to Exchange Online for the user with UPN test@contoso.com.
.NOTES
    This function is based on the CreateExoPSSession script provided by Microsoft when installing the
    Microsoft Exchange Online Powershell module.
.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
    {
        $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
    }
    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
            Get-PSSession | Remove-PSSession

            $global:ConnectionUri = $ConnectionUri;
            $global:AzureADAuthorizationEndpointUri = $AzureADAuthorizationEndpointUri;
            $global:PSSessionOption = $PSSessionOption;
            $global:BypassMailboxAnchoring = $BypassMailboxAnchoring;
            $global:UserPrincipalName = $UserPrincipalName.Value;
            $global:Credential = $Credential.Value;

            #Import Exchange EXO Module
            $modules = @(Get-ChildItem -Path "$($env:LOCALAPPDATA)\Apps\2.0" -Filter "Microsoft.Exchange.Management.ExoPowershellModule.manifest" -Recurse )
            $moduleName =  Join-Path $modules[0].Directory.FullName "Microsoft.Exchange.Management.ExoPowershellModule.dll"
            Import-Module -FullyQualifiedName $moduleName -Force

            #Create the new session
            $PSSession = New-ExoPSSession -UserPrincipalName $UserPrincipalName.Value -ConnectionUri $ConnectionUri -AzureADAuthorizationEndpointUri $AzureADAuthorizationEndpointUri -PSSessionOption $PSSessionOption -Credential $Credential.Value -BypassMailboxAnchoring:$BypassMailboxAnchoring

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

                # If we are configured to collect telemetry, add telemetry wrappers.
                if ($EnableEXOTelemetry.Value -eq $true)
                {
                    $TelemetryFilePath.Value = Add-EXOClientTelemetryWrapper -Organization (Get-OrgNameFromUPN -UPN $UserPrincipalName.Value) -PSSessionModuleName $PSSessionModuleInfo.Name -TelemetryFilePath $TelemetryFilePath.Value -DoLogErrorMessage:$DoLogErrorMessage.Value
                }
            }
        }
        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)

                    Push-EXOTelemetryRecord -TelemetryFilePath $TelemetryFilePath.Value -CommandName Connect-EXOPSSession -OrganizationName  $global:ExPSTelemetryOrganization -ScriptName $global:ExPSTelemetryScriptName  -ScriptExecutionGuid $global:ExPSTelemetryScriptExecutionGuid -ErrorObject $global:Error -ErrorRecordsToConsider ($errorCountAtProcessEnd - $errorCountAtStart)
                }
            }
        }
    }
}