diff --git a/Shared/M365/EXOConnection.ps1 b/Shared/M365/EXOConnection.ps1 new file mode 100644 index 000000000..3850e680e --- /dev/null +++ b/Shared/M365/EXOConnection.ps1 @@ -0,0 +1,150 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS +This script defines a function `Connect-EXOAdvanced` that establishes a connection to Exchange Online. +It ensures that the required ExchangeOnlineManagement module (version 3.0.0 or higher by default) is installed and loaded. +The function supports single and multiple session connections, with optional parameters to control the connection details display and session prefix. +If the required module is not found, the script attempts to install it. +The function returns the connection information or null if the connection fails. + +.PARAMETER DoNotShowConnectionDetails + Optional switch to hide connection details. +.PARAMETER AllowMultipleSessions + Optional switch to allow multiple sessions. +.PARAMETER Prefix + Optional string to specify a prefix for the session. +.PARAMETER MinModuleVersion + Optional parameter to specify the minimum version of the ExchangeOnlineManagement module (default and minimum supported version is 3.0.0). + +.OUTPUTS +Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionInformation. The connection information object for the Exchange Online session. + +.EXAMPLE +$exoConnection = Connect-EXOAdvanced +This example establishes a connection to Exchange Online using the default settings. + +.EXAMPLE +$exoConnection = Connect-EXOAdvanced -AllowMultipleSessions +This example establishes a connection to Exchange Online and allows multiple sessions. + +.EXAMPLE +$exoConnection = Connect-EXOAdvanced -AllowMultipleSessions -Prefix Con2 +This example establishes a connection to Exchange Online, allows multiple sessions, and specifies a prefix "Con2" for the session. + +.EXAMPLE +$exoConnection2 = Connect-EXOAdvanced -minModuleVersion 3.5.0 +This example establishes a connection to Exchange Online and specifies a minimum module version of 3.5.0. +#> + +. $PSScriptRoot\..\ModuleHandle.ps1 + +function Connect-EXOAdvanced { + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] + param ( + [Parameter(Mandatory = $false, ParameterSetName = 'SingleSession')] + [Parameter(Mandatory = $false, ParameterSetName = 'AllowMultipleSessions')] + [switch]$DoNotShowConnectionDetails, + [Parameter(Mandatory = $true, ParameterSetName = 'AllowMultipleSessions')] + [switch]$AllowMultipleSessions, + [Parameter(Mandatory = $false, ParameterSetName = 'AllowMultipleSessions')] + [string]$Prefix = $null, + [ValidateScript({ + if ($_ -lt [System.Version]'3.0.0.0') { + throw "Minimum supported version: 3.0.0.0" + } + $true + })] + [Parameter(Mandatory = $false, ParameterSetName = 'SingleSession')] + [Parameter(Mandatory = $false, ParameterSetName = 'AllowMultipleSessions')] + [System.Version]$MinModuleVersion = '3.0.0.0' + ) + + #Validate EXO 3.0 is installed and loaded + $requestModule = Request-Module -Modules "ExchangeOnlineManagement" -MinModuleVersion $MinModuleVersion + + if (-not $requestModule) { + Write-Host "We cannot continue without ExchangeOnlineManagement Powershell module" -ForegroundColor Red + return $null + } + + #Validate EXO is connected or try to connect + $connections = $null + $newConnection = $null + try { + $connections = Get-ConnectionInformation -ErrorAction Stop | Where-Object { $_.State -eq 'Connected' } + } catch { + Write-Host "We cannot check connections. Error:`n$_" -ForegroundColor Red + return $null + } + + if ($null -eq $connections -or $AllowMultipleSessions) { + if ($connections.ModulePrefix -contains $Prefix) { + Write-Host "You already have a session" -ForegroundColor Yellow -NoNewline + if ($Prefix) { + Write-Host " with the prefix $Prefix." -ForegroundColor Yellow + } else { + Write-Host " without prefix." -ForegroundColor Yellow + } + $newConnection = $connections | Where-Object { $_.ModulePrefix -eq $Prefix } + } else { + $prefixString = "." + if ($Prefix) { $prefixString = " with Prefix $Prefix." } + Write-Host "Not connected to Exchange Online$prefixString" -ForegroundColor Yellow + + if ($PSCmdlet.ShouldProcess("Do you want to add it?", "Adding an Exchange Online Session")) { + Write-Verbose "Connecting to Exchange Online session" + try { + Connect-ExchangeOnline -ShowBanner:$false -Prefix $Prefix -ErrorAction Stop + } catch { + Write-Host "We cannot connect to Exchange Online. Error:`n$_" -ForegroundColor Red + return $null + } + try { + $newConnections = Get-ConnectionInformation -ErrorAction Stop + } catch { + Write-Host "We cannot check connections. Error:`n$_" -ForegroundColor Red + return $null + } + foreach ($testConnection in $newConnections) { + if ($connections -notcontains $testConnection) { + $newConnection = $testConnection + } + } + } + } + if ($newConnection.count -gt 1) { + Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix." -ForegroundColor Red + return $null + } + } else { + Write-Verbose "You already have an Exchange Online session" + if ($connections.count -gt 1) { + Write-Host "You have more than one Exchange Online sessions please use just one session. You are not using AllowMultipleSessions" -ForegroundColor Red + return $null + } + $newConnection = $connections + } + + Write-Verbose "Connected session to Exchange Online" + $newConnection.PSObject.Properties | ForEach-Object { Write-Verbose "$($_.Name): $($_.Value)" } + if (-not $DoNotShowConnectionDetails) { + Show-EXOConnection -Connection $newConnection + } + return $newConnection +} + +function Show-EXOConnection { + param ( + [Parameter(Mandatory = $true)] + [Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionInformation]$Connection + ) + Write-Host "`nConnected to Exchange Online" + Write-Host "Session details" + Write-Host "Tenant Id: $($Connection.TenantId)" + Write-Host "User: $($Connection.UserPrincipalName)" + if ($($Connection.ModulePrefix)) { + Write-Host "Prefix: $($Connection.ModulePrefix)" + } +} diff --git a/Shared/M365/GraphConnection.ps1 b/Shared/M365/GraphConnection.ps1 new file mode 100644 index 000000000..3a5af1092 --- /dev/null +++ b/Shared/M365/GraphConnection.ps1 @@ -0,0 +1,180 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS +This script defines a function `Connect-GraphAdvanced` that establishes a connection to Microsoft Graph. +It ensures that the required modules are installed and loaded. +The function accepts a list of scopes and modules, with optional parameters for tenant ID and connection details display. +If the required modules are not found, the script attempts to install them. +The function returns the connection information or null if the connection fails. + +.PARAMETER Scopes + Mandatory array of strings specifying the scopes for the connection. +.PARAMETER Modules + Mandatory array of strings specifying the modules required for the connection. +.PARAMETER TenantId + Optional array of strings specifying the tenant ID(s) for the connection. +.PARAMETER DoNotShowConnectionDetails + Optional switch to hide connection details. +.PARAMETER MinModuleVersion + Optional parameter to specify the minimum version of the Graph modules (default and minimum supported version 2.0.0). + +.OUTPUTS +Microsoft.Graph.PowerShell.Authentication.AuthContext. The connection information object for the Microsoft Graph session. + +.EXAMPLE +$graphConnection = Connect-GraphAdvanced -Scopes User.Read, Mail.Read -Modules Microsoft.Graph +This example establishes a connection to Microsoft Graph with the scopes "User.Read" and "Mail.Read" using the "Microsoft.Graph" module. + +.EXAMPLE +$graphConnection = Connect-GraphAdvanced -Scopes Group.Read.All, User.Read.All -Modules Microsoft.Graph.Users, Microsoft.Graph.Groups +This example establishes a connection to Microsoft Graph with the scopes "Group.Read.All" and "User.Read.All" using the "Microsoft.Graph.Users" and "Microsoft.Graph.Groups" modules. + +.EXAMPLE +$graphConnection = Connect-GraphAdvanced -Scopes Group.Read.All, User.Read.All -Modules Microsoft.Graph.Users, Microsoft.Graph.Groups -minModuleVersion 2.25.0 +This example establishes a connection to Microsoft Graph with the scopes "Group.Read.All" and "User.Read.All" using the "Microsoft.Graph.Users" and "Microsoft.Graph.Groups" modules, and specifies a minimum module version of 2.25.0. +#> + +. $PSScriptRoot\..\ModuleHandle.ps1 + +function Connect-GraphAdvanced { + param ( + [Parameter(Mandatory = $true)] + [string[]]$Scopes, + [Parameter(Mandatory = $true)] + [string[]]$Modules, + [Parameter(Mandatory = $false)] + [string[]]$TenantId = $null, + [Parameter(Mandatory = $false)] + [switch]$DoNotShowConnectionDetails, + [ValidateScript({ + if ($_ -lt [System.Version]'2.0.0.0') { + throw "Minimum supported version: 2.0.0.0" + } + $true + })] + [Parameter(Mandatory = $false)] + [System.Version]$MinModuleVersion = '2.0.0.0' + ) + + #Validate Graph is installed and loaded + $requestModule = Request-Module -Modules $Modules -MinModuleVersion $MinModuleVersion + if (-not $requestModule) { + Write-Host "We cannot continue without $Modules Powershell module" -ForegroundColor Red + return $null + } + + #Validate Graph is connected or try to connect + $connection = $null + try { + $connection = Get-MgContext -ErrorAction Stop + } catch { + Write-Host "We cannot check context. Error:`n$_" -ForegroundColor Red + return $null + } + + if ($null -eq $connection) { + Write-Host "Not connected to Graph" -ForegroundColor Yellow + $connection = Add-GraphConnection -Scopes $Scopes + } else { + Write-Verbose "You have a Graph sessions" + Write-Verbose "Checking scopes" + if (-not (Test-GraphScopeContext -Scopes $connection.Scopes -ExpectedScopes $Scopes)) { + Write-Host "Not connected to Graph with expected scopes" -ForegroundColor Yellow + $connection = Add-GraphConnection -Scopes $Scopes + } else { + Write-Verbose "All scopes are present" + } + } + + if ($connection) { + Write-Verbose "Checking TenantId" + if ($TenantId) { + if ($connection.TenantId -ne $TenantId) { + Write-Host "Connected to $($connection.TenantId). Not expected tenant: $TenantId" -ForegroundColor Red + return $null + } else { + Write-Verbose "TenantId is correct" + } + } + + $connection.PSObject.Properties | ForEach-Object { Write-Verbose "$($_.Name): $($_.Value)" } + if (-not $DoNotShowConnectionDetails) { + Show-GraphContext -Context $connection + } + } + return $connection +} + +function Add-GraphConnection { + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] + param ( + [Parameter(Mandatory = $true)] + [string[]]$Scopes + ) + + if ($PSCmdlet.ShouldProcess("Do you want to connect?", "We need a Graph connection with scopes $Scopes")) { + Write-Verbose "Connecting to Microsoft Graph API using scopes $Scopes" + try { + Connect-MgGraph -Scopes $Scopes -NoWelcome -ErrorAction Stop + } catch { + Write-Host "We cannot connect to Graph. Error:`n$_" -ForegroundColor Red + return $null + } + $connection = $null + try { + $connection = Get-MgContext -ErrorAction Stop + } catch { + Write-Host "We cannot check context. Error:`n$_" -ForegroundColor Red + return $null + } + Write-Verbose "Checking scopes" + if (-not $connection) { + Write-Host "We cannot continue without Graph Powershell session" -ForegroundColor Red + return $null + } + if (-not (Test-GraphScopeContext -Scopes $connection.Scopes -ExpectedScopes $Scopes)) { + Write-Host "We cannot continue without Graph Powershell session without Expected Scopes" -ForegroundColor Red + return $null + } + return $connection + } +} + +function Test-GraphScopeContext { + [OutputType([bool])] + param ( + [Parameter(Mandatory = $true)] + [string[]]$ExpectedScopes, + [Parameter(Mandatory = $true)] + [string[]]$Scopes + ) + + $foundError = $false + foreach ($expectedScope in $ExpectedScopes) { + if ($Scopes -notcontains $expectedScope) { + Write-Host "The following scope is missing: $expectedScope" -ForegroundColor Red + $foundError = $true + } + } + + Write-Verbose "All expected scopes are $(if($foundError){ "NOT "})present." + return (-not $foundError) +} + +function Show-GraphContext { + param ( + [Parameter(Mandatory = $true)] + [Microsoft.Graph.PowerShell.Authentication.AuthContext]$Context + ) + Write-Host "`nConnected to Graph" + Write-Host "Session details" + Write-Host "Tenant Id: $($Context.TenantId)" + if ($graphConnection.AuthType) { + Write-Host "AuthType: $($graphConnection.AuthType)" + } + if ($Context.Account) { + Write-Host "Account: $($Context.Account)" + } +} diff --git a/Shared/ModuleHandle.ps1 b/Shared/ModuleHandle.ps1 new file mode 100644 index 000000000..5c04cb892 --- /dev/null +++ b/Shared/ModuleHandle.ps1 @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS +This script defines a function `Request-Module` that checks for the presence of specified PowerShell modules. +If a module is not found, it attempts to install it on current user scope. +The function accepts a list of module names and an optional minimum version for the modules. +It returns a boolean indicating whether all specified modules are installed successfully. + +.PARAMETER Modules + Mandatory array of strings specifying the names of the modules to check and install if necessary. +.PARAMETER MinModuleVersion + Optional parameter to specify the minimum version of the modules (default is null). + +.OUTPUTS +bool. A boolean indicating whether all specified modules are installed successfully. + +.EXAMPLE +$requestModule = Request-Module -Modules "ExchangeOnlineManagement" +This example checks if the "ExchangeOnlineManagement" module is installed. If it is not found, the script attempts to install it. + +.EXAMPLE +$requestModule = Request-Module -Modules "ExchangeOnlineManagement" -MinModuleVersion $MinModuleVersion +This example checks if the "ExchangeOnlineManagement" module with a specified minimum version is installed. If it is not found, the script attempts to install it. +#> + +function Request-Module { + [OutputType([bool])] + param ( + [Parameter(Mandatory = $true)] + [string[]]$Modules, + [Parameter(Mandatory = $false)] + [System.Version]$MinModuleVersion = $null + ) + + $noFoundError = $true + foreach ($module in $Modules) { + Write-Verbose "Checking $Modules PowerShell Module" + $getParams = @{ + Name = $module + ErrorAction = 'SilentlyContinue' + } + Write-Verbose "Checking $module" + if ($MinModuleVersion) { + $getParams["MinimumVersion"] = $MinModuleVersion + Write-Verbose "with minimum version $minModuleVersion" + } else { + Write-Verbose "without minimum version" + } + $installed = Get-InstalledModule @getParams + + if ($null -eq $installed -or $installed.Name -notcontains $module) { + Write-Host "The following module is missing: $module" -ForegroundColor Yellow + $confirmed = $null + try { + Write-Verbose "Installing $module" + $installParams = @{ + Name = $module + Scope = "CurrentUser" + ErrorAction = 'Stop' + } + if ($MinModuleVersion) { + $installParams["MinimumVersion"] = $MinModuleVersion + Write-Verbose "with minimum version $minModuleVersion" + } else { + Write-Verbose "without minimum version" + } + Install-Module @installParams -Force + + Write-Verbose "Checking $module" + $confirmed = Get-InstalledModule @getParams + if (-not $confirmed) { + Write-Host "We could not install module: $module" -ForegroundColor Red + $noFoundError = $false + } + } catch { + Write-Host "Installation process fails. Error: `n$_" -ForegroundColor Red + $noFoundError = $false + } + } + } + return $noFoundError +}