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 |