Public/Connect-O365Exchange.ps1

function Connect-O365Exchange {
    <#
    .SYNOPSIS
        Establishes a connection to Exchange Online with proper error handling and verification.
     
    .DESCRIPTION
        This function provides a streamlined way to connect to Exchange Online PowerShell.
        It checks for existing connections, handles authentication, and verifies the connection
        is working properly. Includes support for different authentication methods and
        automatic module installation if needed.
     
    .PARAMETER UserPrincipalName
        The User Principal Name (UPN) to use for authentication.
        If not specified, will prompt for credentials or use modern authentication.
     
    .PARAMETER UseModernAuth
        Forces the use of modern authentication (OAuth2) instead of basic authentication.
        This is the default and recommended authentication method.
     
    .PARAMETER Organization
        The organization name (tenant) to connect to.
        Example: "contoso" for contoso.onmicrosoft.com
     
    .PARAMETER ShowProgress
        Displays detailed progress information during connection process.
     
    .PARAMETER Force
        Forces a new connection even if already connected to Exchange Online.
        Useful for switching between different tenants or accounts.
     
    .EXAMPLE
        Connect-O365Exchange
        Connects to Exchange Online using modern authentication with interactive login.
     
    .EXAMPLE
        Connect-O365Exchange -UserPrincipalName "admin@contoso.com"
        Connects using a specific user account.
     
    .EXAMPLE
        Connect-O365Exchange -Organization "contoso" -ShowProgress
        Connects to a specific organization with detailed progress information.
     
    .EXAMPLE
        Connect-O365Exchange -Force
        Forces a new connection even if already connected.
     
    .OUTPUTS
        System.Boolean
        Returns $true if connection was successful, $false otherwise.
     
    .NOTES
        Requires ExchangeOnlineManagement module version 2.0.5 or later.
        Uses modern authentication by default for enhanced security.
        Automatically installs the required module if not present (with user consent).
         
    .LINK
        https://docs.microsoft.com/en-us/powershell/exchange/connect-to-exchange-online-powershell
    #>

    
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [Parameter(
            HelpMessage = "User Principal Name for authentication"
        )]
        [ValidatePattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")]
        [string]$UserPrincipalName,
        
        [Parameter(
            HelpMessage = "Use modern authentication (OAuth2)"
        )]
        [switch]$UseModernAuth,
        
        [Parameter(
            HelpMessage = "Organization/tenant name to connect to"
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Organization,
        
        [Parameter(
            HelpMessage = "Show detailed progress during connection"
        )]
        [switch]$ShowProgress,
        
        [Parameter(
            HelpMessage = "Force new connection even if already connected"
        )]
        [switch]$Force
    )
    
    begin {
        Write-Verbose "Starting Exchange Online connection process"
        
        # Check if already connected (unless Force is specified)
        if (-not $Force) {
            try {
                $existingConnection = Get-ConnectionInformation -ErrorAction SilentlyContinue
                if ($existingConnection) {
                    Write-Host "Already connected to Exchange Online:" -ForegroundColor Green
                    Write-Host " Organization: $($existingConnection.Name)" -ForegroundColor Cyan
                    Write-Host " User: $($existingConnection.UserPrincipalName)" -ForegroundColor Cyan
                    Write-Host " Connection State: $($existingConnection.State)" -ForegroundColor Cyan
                    
                    # Test the connection is working
                    try {
                        $null = Get-OrganizationConfig -ErrorAction Stop
                        Write-Host " Connection Status: Active and functional" -ForegroundColor Green
                        return $true
                    }
                    catch {
                        Write-Warning "Existing connection appears to be inactive. Attempting to reconnect..."
                    }
                }
            }
            catch {
                Write-Verbose "No existing connection found or connection check failed"
            }
        }
        
        # Function to check and install required module
        function EnsureExchangeOnlineModule {
            if ($ShowProgress) {
                Write-Host "Checking for ExchangeOnlineManagement module..." -ForegroundColor Yellow
            }
            
            $module = Get-Module -Name ExchangeOnlineManagement -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1
            
            if (-not $module) {
                Write-Host "ExchangeOnlineManagement module not found." -ForegroundColor Yellow
                $installChoice = Read-Host "Would you like to install it now? (Y/N)"
                
                if ($installChoice -match "^[Yy]") {
                    try {
                        Write-Host "Installing ExchangeOnlineManagement module..." -ForegroundColor Yellow
                        Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -Force -AllowClobber
                        Write-Host "Module installed successfully." -ForegroundColor Green
                    }
                    catch {
                        Write-Error "Failed to install ExchangeOnlineManagement module: $($_.Exception.Message)"
                        return $false
                    }
                } else {
                    Write-Error "ExchangeOnlineManagement module is required. Please install it manually: Install-Module -Name ExchangeOnlineManagement"
                    return $false
                }
            } else {
                if ($ShowProgress) {
                    Write-Host "Found ExchangeOnlineManagement module version: $($module.Version)" -ForegroundColor Green
                }
                
                # Check if version is recent enough
                if ($module.Version -lt [Version]"2.0.5") {
                    Write-Warning "ExchangeOnlineManagement module version $($module.Version) is outdated. Consider updating: Update-Module -Name ExchangeOnlineManagement"
                }
            }
            
            # Import the module
            try {
                Import-Module ExchangeOnlineManagement -Force
                if ($ShowProgress) {
                    Write-Host "ExchangeOnlineManagement module imported successfully." -ForegroundColor Green
                }
                return $true
            }
            catch {
                Write-Error "Failed to import ExchangeOnlineManagement module: $($_.Exception.Message)"
                return $false
            }
        }
        
        # Ensure module is available
        if (-not (EnsureExchangeOnlineModule)) {
            return $false
        }
    }
    
    process {
        try {
            if ($ShowProgress) {
                Write-Host "Initiating Exchange Online connection..." -ForegroundColor Yellow
            }
            
            # Build connection parameters
            $connectionParams = @{
                ShowBanner = $false
                WarningAction = 'SilentlyContinue'
            }
            
            # Add UPN if specified
            if ($UserPrincipalName) {
                $connectionParams['UserPrincipalName'] = $UserPrincipalName
                if ($ShowProgress) {
                    Write-Host "Using specified user: $UserPrincipalName" -ForegroundColor Cyan
                }
            }
            
            # Add organization if specified
            if ($Organization) {
                $connectionParams['Organization'] = $Organization
                if ($ShowProgress) {
                    Write-Host "Connecting to organization: $Organization" -ForegroundColor Cyan
                }
            }
            
            # Disconnect existing connections if Force is specified
            if ($Force) {
                try {
                    Disconnect-ExchangeOnline -Confirm:$false -WarningAction SilentlyContinue
                    if ($ShowProgress) {
                        Write-Host "Disconnected from existing Exchange Online sessions." -ForegroundColor Yellow
                    }
                }
                catch {
                    Write-Verbose "No existing connections to disconnect or disconnect failed"
                }
            }
            
            # Attempt connection
            if ($ShowProgress) {
                Write-Host "Authenticating..." -ForegroundColor Yellow
            }
            
            Connect-ExchangeOnline @connectionParams
            
            # Verify connection
            if ($ShowProgress) {
                Write-Host "Verifying connection..." -ForegroundColor Yellow
            }
            
            $connectionInfo = Get-ConnectionInformation -ErrorAction Stop
            
            if ($connectionInfo) {
                # Test that we can actually run Exchange Online commands
                $null = Get-OrganizationConfig -ErrorAction Stop
                
                Write-Host "Successfully connected to Exchange Online!" -ForegroundColor Green
                Write-Host " Organization: $($connectionInfo.Name)" -ForegroundColor Cyan
                Write-Host " User: $($connectionInfo.UserPrincipalName)" -ForegroundColor Cyan
                Write-Host " Token Expiry: $($connectionInfo.TokenExpiryTimeUTC) UTC" -ForegroundColor Cyan
                
                return $true
            } else {
                throw "Connection established but verification failed"
            }
        }
        catch {
            Write-Error "Failed to connect to Exchange Online: $($_.Exception.Message)"
            
            # Provide helpful troubleshooting information
            Write-Host "`nTroubleshooting tips:" -ForegroundColor Yellow
            Write-Host "1. Ensure you have the correct permissions for Exchange Online" -ForegroundColor White
            Write-Host "2. Check your internet connection" -ForegroundColor White
            Write-Host "3. Verify your credentials are correct" -ForegroundColor White
            Write-Host "4. If using MFA, ensure you can receive authentication prompts" -ForegroundColor White
            Write-Host "5. Try running: Update-Module ExchangeOnlineManagement" -ForegroundColor White
            
            return $false
        }
    }
}

function Test-O365ExchangeConnection {
    <#
    .SYNOPSIS
        Tests the current Exchange Online connection status and functionality.
     
    .DESCRIPTION
        This function verifies that there is an active Exchange Online connection
        and that it is functioning properly by attempting to run a basic command.
        Provides detailed connection information and troubleshooting guidance.
     
    .PARAMETER Detailed
        Returns detailed connection information including token expiry and permissions.
     
    .PARAMETER Quiet
        Suppresses output and only returns true/false result.
     
    .EXAMPLE
        Test-O365ExchangeConnection
        Tests the connection and displays basic status information.
     
    .EXAMPLE
        Test-O365ExchangeConnection -Detailed
        Tests the connection and displays detailed information.
     
    .EXAMPLE
        if (Test-O365ExchangeConnection -Quiet) { "Connected" } else { "Not Connected" }
        Uses the function in conditional logic.
     
    .OUTPUTS
        System.Boolean
        Returns $true if connected and functional, $false otherwise.
     
    .NOTES
        This function is called internally by other functions in the module
        but can also be used standalone for connection verification.
    #>

    
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [Parameter(
            HelpMessage = "Return detailed connection information"
        )]
        [switch]$Detailed,
        
        [Parameter(
            HelpMessage = "Suppress output, return only boolean result"
        )]
        [switch]$Quiet
    )
    
    try {
        # Check if we have any connection information
        $connectionInfo = Get-ConnectionInformation -ErrorAction Stop
        
        if (-not $connectionInfo) {
            if (-not $Quiet) {
                Write-Host "No Exchange Online connection found." -ForegroundColor Red
                Write-Host "Run Connect-O365Exchange to establish a connection." -ForegroundColor Yellow
            }
            return $false
        }
        
        # Test that the connection actually works
        $null = Get-OrganizationConfig -ErrorAction Stop
        
        if (-not $Quiet) {
            Write-Host "Exchange Online connection is active and functional." -ForegroundColor Green
            
            if ($Detailed) {
                Write-Host "`nConnection Details:" -ForegroundColor Cyan
                Write-Host " Organization: $($connectionInfo.Name)" -ForegroundColor White
                Write-Host " User: $($connectionInfo.UserPrincipalName)" -ForegroundColor White
                Write-Host " Connection ID: $($connectionInfo.ConnectionId)" -ForegroundColor White
                Write-Host " State: $($connectionInfo.State)" -ForegroundColor White
                Write-Host " Token Expiry: $($connectionInfo.TokenExpiryTimeUTC) UTC" -ForegroundColor White
                Write-Host " App ID: $($connectionInfo.AppId)" -ForegroundColor White
                Write-Host " Tenant ID: $($connectionInfo.TenantId)" -ForegroundColor White
                
                # Check token expiry
                if ($connectionInfo.TokenExpiryTimeUTC) {
                    $timeUntilExpiry = $connectionInfo.TokenExpiryTimeUTC - (Get-Date).ToUniversalTime()
                    if ($timeUntilExpiry.TotalMinutes -lt 30) {
                        Write-Host " Warning: Token expires in $([math]::Round($timeUntilExpiry.TotalMinutes, 1)) minutes" -ForegroundColor Yellow
                    }
                }
            }
        }
        
        return $true
    }
    catch {
        if (-not $Quiet) {
            Write-Host "Exchange Online connection test failed: $($_.Exception.Message)" -ForegroundColor Red
            Write-Host "Run Connect-O365Exchange to establish a connection." -ForegroundColor Yellow
        }
        return $false
    }
}

function Disconnect-O365Exchange {
    <#
    .SYNOPSIS
        Safely disconnects from Exchange Online and cleans up sessions.
     
    .DESCRIPTION
        This function properly disconnects from Exchange Online, cleaning up
        any active sessions and providing confirmation of disconnection.
        Includes error handling for cases where no connection exists.
     
    .PARAMETER Force
        Disconnects without confirmation prompt.
     
    .PARAMETER Quiet
        Suppresses output messages.
     
    .EXAMPLE
        Disconnect-O365Exchange
        Disconnects from Exchange Online with confirmation.
     
    .EXAMPLE
        Disconnect-O365Exchange -Force
        Disconnects immediately without confirmation.
     
    .OUTPUTS
        System.Boolean
        Returns $true if disconnection was successful, $false otherwise.
     
    .NOTES
        Always run this function when finished with Exchange Online operations
        to properly clean up sessions and free resources.
    #>

    
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param(
        [Parameter(
            HelpMessage = "Disconnect without confirmation"
        )]
        [switch]$Force,
        
        [Parameter(
            HelpMessage = "Suppress output messages"
        )]
        [switch]$Quiet
    )
    
    try {
        # Check if we have an active connection
        $connectionInfo = Get-ConnectionInformation -ErrorAction SilentlyContinue
        
        if (-not $connectionInfo) {
            if (-not $Quiet) {
                Write-Host "No active Exchange Online connection found." -ForegroundColor Yellow
            }
            return $true
        }
        
        if (-not $Quiet) {
            Write-Host "Disconnecting from Exchange Online..." -ForegroundColor Yellow
            if (-not $Force) {
                Write-Host "Organization: $($connectionInfo.Name)" -ForegroundColor Cyan
                Write-Host "User: $($connectionInfo.UserPrincipalName)" -ForegroundColor Cyan
            }
        }
        
        # Disconnect with appropriate parameters
        $disconnectParams = @{
            WarningAction = 'SilentlyContinue'
        }
        
        if ($Force) {
            $disconnectParams['Confirm'] = $false
        }
        
        Disconnect-ExchangeOnline @disconnectParams
        
        if (-not $Quiet) {
            Write-Host "Successfully disconnected from Exchange Online." -ForegroundColor Green
        }
        
        return $true
    }
    catch {
        if (-not $Quiet) {
            Write-Error "Failed to disconnect from Exchange Online: $($_.Exception.Message)"
        }
        return $false
    }
}