conex.psm1

function Connect-Exchange {
<#
 
    .Synopsis
 
    Connect-Exchange (a.k.a. conex)
 
    .Description
 
    Connect to Exchange or Exchange Online PowerShell (requires EXO PS module
    for EXO). Run with no parameters to see helpful usage tips.
 
    .Parameter UserPrincipalName
 
    [Optional/Recommended] Helps to keep sessions alive by allowing the refresh
    and access tokens to update automatically.
 
    .Parameter Credential
 
    [Optional] Credentials for on-premises Exchange / EXO.
 
    .Parameter ExchangeFqdn
 
    [Optional] FQDN for on-premises Exchange (server).
 
    .Parameter CommandName
 
    [Optional] Pass-through parameter for Import-PSSession. Refer to:
    https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/import-pssession
 
    .Example
 
    Connect-Exchange [Enter]
 
    .Example
 
    New-Alias -Name conex -Value Connect-Exchange
    PS C:\>conex [Enter]
 
    .Example
 
    $creds = Get-Credential
    PS C:\>$server = exsrv25.jb365.ca
    PS C:\>conex -Credential $creds -ExchangeFqdn $server -CommandName *mailbox*
 
    .Example
 
    conex -CommandName Set-M*, Get-Transp*, *database*
 
    .Example
 
    conex -UserPrincipalName exoadmin@jb365.ca
    PS C:\>conex exoadmin@jb365.ca # <--: -UserPrincipalName is in position 0
 
    .Link
 
    https://github.com/JeremyTBradshaw/PowerShell/blob/master/.functions/Connect-Exchange.ps1
 
#>


#Requires -Version 3

[CmdletBinding(DefaultParameterSetName = 'EXO')]

param (

    [Parameter(ParameterSetName = 'EXO', ValueFromRemainingArguments = $true)]
    [string]$UserPrincipalName = '',

    [Parameter(ParameterSetName = 'ExOnP')]
    [string]$ExchangeFqdn,

    [Parameter(ParameterSetName = 'EXO')]
    [Parameter(ParameterSetName = 'ExOnP')]
    [System.Management.Automation.PSCredential]$Credential,

    [Parameter(ParameterSetName = 'EXO')]
    [Parameter(ParameterSetName = 'ExOnP')]
    [ValidatePattern('(^[a-zA-Z*]{0,12})([*-]{0,1})([a-zA-Z*]{1,50}$)')]
    [string[]]$CommandName = "*"

)

$DetectedSession =  Get-PSSession |
                    Where-Object {$_.ConfigurationName -eq 'Microsoft.Exchange'}

if ($DetectedSession) {

    $SessionDetectedMenu = [System.Management.Automation.Host.ChoiceDescription[]] @(

        '&Abort',
        '&End current session',
        '&New PowerShell window'
    )

    $SessionDetectedChoice = $host.UI.PromptForChoice(

        "Existing Exchange PowerShell session detected.`n",
        $null,
        $SessionDetectedMenu, 0
    )

    switch ($SessionDetectedChoice) {

        0 {$break = $true}
        1 {
            $DetectedSession | Remove-PSSession
            Get-Module tmp_* | Remove-Module
            Remove-Module Microsoft.Exchange.Management.ExoPowershellModule -ErrorAction:SilentlyContinue
            Remove-Module CreateExoPSSession -ErrorAction:SilentlyContinue
        }
        2 {
            Invoke-Item $PSHOME\powershell.exe
            $break = $true
        }
    }
    if ($break) {break}
}

if (-not ($PSBoundParameters.ContainsKey('UserPrincipalName')) -and
    -not ($PSBoundParameters.ContainsKey('ExchangeFqdn'))
) {

    $ConexBanner =      " _________________________________`n`n" +
                        " Connect-Exchange (a.k.a. conex) `n" +
                        " _________________________________`n"

    $ConexTipCaption =  " Random Tip:`n"
    $ConexTips = @()
    $ConexTips +=       " - Tired of MFA/credential re-prompts? Specify -UserPrincipalName to enable seamless and silent AAD token refreshes.`n`n" +
                        " e.g. PS C:\> conex -UserPrincipalName ExAdmin@contoso.com`n" +
                        " PS C:\> conex ExAdmin@contoso.com`n`n" +
                        " * Implies connecting to Exchange Online.`n" +
                        " * Requires EXO PS module to be installed (aka.ms/exopspreview).`n" +
                        " * Recommended when logging in with the current user.`n" +
                        " * Recommended for logging in with a different user when:`n" +
                        " - AAD Connect Seamless SSO is not enabled, or...`n" +
                        ' - Intranet automatic-logon is not enabled (i.e. Internet Options).'

    $ConexTips +=       " - Use -Credential for Exchange on-premises or EXO (if not MFA-enabled).`n`n" +
                        " e.g. PS C:\> Connect-Exchange -Credential $myAdminCreds`n" +
                        ' e.g. PS C:\> Connect-Exchange $myAdminCreds -ExchangeFqdn mail.contoso.com'

    $ConexTips +=       " - Use -ExchangeFqdn for Exchange on-premises.`n`n" +
                        ' e.g. PS C:\> Connect-Exchange -ExchangeFqdn mail.contoso.com -Credential $myAdminCreds'

    $ConexTips +=       " - Try -CommandName to download only the commands you plan on using.`n`n" +
                        ' e.g. PS C:\> Connect-Exchange -CommandName *et-*Mailbox, Get-MessageTrackingLog'

    $ConexTips +=       " - Call Connect-Exchange by its preferred name:`n`n" +
                        " New-Alias -Name conex -Value Connect-Exchange`n`n" +
                        ' * This is already done and ready for you if you''re using the conex module.'

    $ConexTips +=       " - Let this menu and its prompt sequences do some of the heavy lifting for you:`n`n" +
                        " PS C:\> conex [Enter]`n" +
                        ' PS C:\> Connect-Exchange [Enter]'

    $ConexTips +=       " - Need assistance? Try:`n`n" +
                        " Get-Help Connect-Exchange`n" +
                        " Get-Help Connect-Exchange -Examples | -Detailed | -Full | -Online`n" +
                        " Connect-Exchange -?`n" +
                        ' conex -?'

    $ExchangeConnectionMenuOptions = [System.Management.Automation.Host.ChoiceDescription[]] @(

        '&Abort',
        '&Exchange Online',
        'Enter Exchange server &FQDN'
    )

    Write-Host -Object $ConexBanner -ForegroundColor Green
    Write-Host -Object $ConexTipCaption -ForegroundColor DarkGray
    Write-Host -Object (Get-Random $ConexTips) -ForegroundColor DarkGray
    $ExchangeConnectionChoice = $host.UI.PromptForChoice(

        " Where to connect?`n`n",
        $null,
        $ExchangeConnectionMenuOptions, 0
    )
}

elseif ($PSBoundParameters.ContainsKey('UserPrincipalName')) {$ExchangeConnectionChoice = 1}

else {$ExchangeConnectionChoice = 2}

$ImportPSSessionProps = @{

    AllowClobber        = $true
    DisableNameChecking = $true
    CommandName         = $CommandName
    FormatTypeName      = @($CommandName -replace '.*-','*')
    ErrorAction         = 'Stop'
}

switch ($ExchangeConnectionChoice) {

    1 {
        try {
            $ExoPSModuleSearchProperties = @{

                Path        = "$($env:LOCALAPPDATA)\Apps\2.0\"
                Filter      = 'Microsoft.Exchange.Management.ExoPowerShellModule.dll'
                Recurse     = $true
                ErrorAction = 'Stop'
            }

            $ExoPSModule =  Get-ChildItem @ExoPSModuleSearchProperties |
                            Where-Object {$_.FullName -notmatch '_none_'} |
                            Sort-Object LastWriteTime |
                            Select-Object -Last 1

            Import-Module $ExoPSModule.FullName -ErrorAction:Stop

            $ExoPSModuleManifest = $ExoPSModule.FullName -replace '\.dll','.psd1'

            $NewExoPSModuleManifestProps = @{

                    Path            = $ExoPSModuleManifest
                    RootModule      = $ExoPSModule.Name
                    ModuleVersion   = "$((Get-Module $ExoPSModule.FullName -ListAvailable).Version.ToString())"
                    Author          = 'Jeremy Bradshaw (https://github.com/JeremyTBradshaw)'
                    CompanyName     = 'jb365'
            }

            New-ModuleManifest @NewExoPSModuleManifestProps

            Import-Module $ExoPSModule.FullName -Global -ErrorAction:Stop

            $CreateExoPSSessionPs1 = Get-ChildItem -Path $ExoPSModule.PSParentPath -Filter 'CreateExoPSSession.ps1'

            $CreateExoPSSessionManifest = $CreateExoPSSessionPs1.FullName -replace '\.ps1','.psd1'

            $CreateExoPSSessionPs1 =    $CreateExoPSSessionPs1 |
                                        Get-Content |
                                        Where-Object {-not ($_ -like 'Write-Host*')}

            $CreateExoPSSessionPs1 -join "`n" |
            Set-Content -Path "$($CreateExoPSSessionManifest -replace '\.psd1','.psm1')"

            $NewCreateExoPSSessionManifest = @{

                    Path            = $CreateExoPSSessionManifest
                    RootModule      = Split-Path -Path ($CreateExoPSSessionManifest -replace '\.psd1','.psm1') -Leaf
                    ModuleVersion   = '1.0'
                    Author          = 'Jeremy Bradshaw (https://github.com/JeremyTBradshaw)'
                    CompanyName     = 'jb365'
            }

            New-ModuleManifest @NewCreateExoPSSessionManifest

            Import-Module "$($ExoPSModule.PSParentPath)\CreateExoPSSession.psm1" -Global -ErrorAction:Stop
        }
        catch {
            Write-Warning -Message "Tried but failed to import the EXO PS module.`n`nError message:"
            throw $_
        }
        try {
            $global:UserPrincipalName = $UserPrincipalName
            $global:ConnectionUri = 'https://outlook.office365.com/PowerShell-LiveId'
            $global:AzureADAuthorizationEndpointUri = 'https://login.windows.net/common'
            $global:PSSessionOption = New-PSSessionOption -CancelTimeout 5000 -IdleTimeout 43200000
            $global:BypassMailboxAnchoring = $false

            $ExoPSSession = @{
                UserPrincipalName               = $global:UserPrincipalName
                ConnectionUri                   = $global:ConnectionUri
                AzureADAuthorizationEndpointUri = $global:AzureADAuthorizationEndpointUri
                PSSessionOption                 = $global:PSSessionOption
                BypassMailboxAnchoring          = $global:BypassMailboxAnchoring
            }

            if ($PSBoundParameters.Credential) {$ExoPSSession['Credential'] = $Credential}

            $ExoPSSession = New-ExoPSSession @ExoPSSession -ErrorAction:Stop

            Import-Module (Import-PSSession $ExoPSSession @ImportPSSessionProps) -Global -DisableNameChecking -ErrorAction:Stop

            UpdateImplicitRemotingHandler
        }
        catch {
            Write-Warning -Message "Failed to connect to EXO via the imported EXO PS module.`n`nError message:"
            throw $_
        }
    }

    2 {
        if (-not $ExchangeFqdn) {$ExchangeFqdn = Read-Host -Prompt "`nExchange PowerShell FQDN (e.g. mail.contoso.com)"}

        $AuthenticationMenuOptions = [System.Management.Automation.Host.ChoiceDescription[]] @(

            '&Basic (HTTPS)',
            '&Default (HTTP/S)'
        )

        $AuthenticationChoice = $host.UI.PromptForChoice(

            'Authentication method?',
            "`n",
            $AuthenticationMenuOptions, 0
        )

        switch ($AuthenticationChoice) {

            0 {
                $Authentication = 'Basic'
                $Protocol       = 'https'
            }
            1 {
                $Authentication = 'Default'

                $ProtocolMenu = [System.Management.Automation.Host.ChoiceDescription[]] @(

                    'HTT&P',
                    'HTTP&S'
                )

                $ProtocolChoice = $host.UI.PromptForChoice(

                    'Protocol?',
                    'Options:',
                    $ProtocolMenu, 0
                )

                switch ($ProtocolChoice) {

                    0 {$Protocol = 'http'}
                    1 {$Protocol = 'https'}
                }
            }
        }

        if (-not $PSBoundParameters.Credential) {$Credential = Get-Credential -Message 'Exchange On-Premises Credentials'}

        $ExchPSSessionProps = @{

            Credential        = $Credential
            ConfigurationName = 'Microsoft.Exchange'
            AllowRedirection  = $true
            ConnectionUri     = "$($Protocol)://$($ExchangeFqdn)/powershell"
            Authentication    = $Authentication
        }

        try {
            $ExchPSSession = New-PSSession @ExchPSSessionProps -ErrorAction:Stop

            Import-Module (Import-PSSession $ExchPSSession @ImportPSSessionProps) -Global -DisableNameChecking -ErrorAction:Stop
        }
        catch {
            Write-Warning -Message "Failed to connect to $($Protocol)://$($ExchangeFqdn)/powershell with credentials for username $($Credential.UserName).`n`nError message:"
            throw $_
        }
    }
} # end switch ($ExchangeConnectionChoice)

} # end function Connect-Exchange
New-Alias -Name conex -Value Connect-Exchange

function Disconnect-Exchange {

    Get-PSSession |
    Where-Object {$_.ConfigurationName -eq 'Microsoft.Exchange'} |
    Remove-PSSession

    Get-Module tmp_* |
    Remove-Module

    Remove-Module Microsoft.Exchange.Management.ExoPowershellModule -ErrorAction:SilentlyContinue
    Remove-Module CreateExoPSSession -ErrorAction:SilentlyContinue
}
New-Alias -Name noconex -Value Disconnect-Exchange

function Set-ConexTitle {

    [CmdletBinding()]
    param()

    $ConexPath = $PSCmdlet.MyInvocation.MyCommand.Module.ModuleBase

    $ConexTitleIsSet = $false

    if ($host.UI.RawUI.WindowTitle -match '^.*\[ c o n e x \] : : T o \(.*\)$') {

        $ConexTitleIsSet = $true
    }

    else {
        $host.UI.RawUI.WindowTitle |
        Export-Clixml -Path "$($ConexPath)\ConexTitleBackup.xml"
    }

    $ConexTitleBackup = $null
    $ConexTitleBackup = Get-ChildItem -Path $ConexPath -Filter 'ConexTitleBackup.xml'

    if ($null -ne $ConexTitleBackup) {

        $LastSavedTitle =   $null
        $LastSavedTitle =   Import-Clixml $ConexTitleBackup.FullName |
                            Where-Object {$_ -notmatch '^.*\[ c o n e x \] : : T o \(.*\)$'}
    }

    $ExPSSession =  @()
    $ExPSSession += Get-PSSession |
                    Where-Object {$_.ConfigurationName -eq 'Microsoft.Exchange'}

    if ($ExPSSession.Count -eq 1) {

        $host.UI.RawUI.WindowTitle = " [conex]::To( $($ExPSSession.ComputerName),$($ExPSSession.State) )".ToCharArray() -join ' '
    }

    elseif ($ExPSSession.Count -gt 1) {

        $host.UI.RawUI.WindowTitle = " [ c o n e x ] : : To( Multiple Sessions, Clear all with: noconex [Enter] )"
    }

    else {
        if ($ConexTitleIsSet -and ($null -ne $LastSavedTitle)) {

            $ConexTitleRestoreOptions = @()
            $ConexTitleRestoreOptions += [System.Management.Automation.Host.ChoiceDescription[]]('&Yes','&No')
            $ConexTitleRestoreChoice = $host.UI.PromptForChoice(

                " No Exchange PS sessions found. Restore PS window title to '$($LastSavedTitle)'?`n`n",
                $null,
                $ConexTitleRestoreOptions, 0
            )

            switch ($ConexTitleRestoreChoice) {

                0 {$host.UI.RawUI.WindowTitle = $LastSavedTitle}
            }
        }
    }
} # end function Set-ConexTitle
New-Alias -Name tex -Value Set-ConexTitle