Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added M365 connections to shared #2090

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions Shared/M365/EXOConnection.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# 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 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

iserrano76 marked this conversation as resolved.
Show resolved Hide resolved
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,
[Parameter(Mandatory = $false, ParameterSetName = 'SingleSession')]
[Parameter(Mandatory = $false, ParameterSetName = 'AllowMultipleSessions')]
[System.Version]$MinModuleVersion = 3.0.0.0
iserrano76 marked this conversation as resolved.
Show resolved Hide resolved
)

#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
}
dpaulson45 marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we returning a connection here? Trying to understand this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big reason why is trying to understand this section of code:

    if ($null -eq $connections -or $AllowMultipleSessions) {
        if ($connections.ModulePrefix -contains $Prefix) {
            Write-Host "You already have a session with the prefix $Prefix" -ForegroundColor Red
            return $null
        } else {

Copy link
Contributor Author

@iserrano76 iserrano76 Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to return the connection because you could use different connections and you can control which one is under your responsibility, which one you created and which one is not your connection.

On that part of the code, I want to create a connection only if you do not have a connection and you do not use allow multiplesessions.
In case that you use allowmultiplesession, we will verify that we do not have a duplicated prefix.

It could be useful in future in case that we want to close a created connection or the one that you are going to use.

For example, in MDO we get information from EXO connection and compare with Graph connection to confirm that we are connected to the same tenant.

}

function Show-EXOConnection {
param (
[Parameter(Mandatory = $true)]
[Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionInformation]$Connection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this correctly work when you have multiple sessions used with $AllowMultipleSessions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, you are sending by parameter the session that you created, just one, and that is the one that we are showing.
If you create a second one or a third one, is going to have a new call to the function that shows that information.
I think I test it when I created, but I will test it and paste some screenshots.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, $newConnection should only have 1 object in the array by the time we get here, so it should work as expected.

)
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)"
}
}
178 changes: 178 additions & 0 deletions Shared/M365/GraphConnection.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# 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 is 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

iserrano76 marked this conversation as resolved.
Show resolved Hide resolved
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,
[Parameter(Mandatory = $false)]
[System.Version]$MinModuleVersion = 2.0.0.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a script validation here for the min version supported.

Let's also add some version information to get the module that we are currently using, just in case things change with Graph.

)

#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"
}
}

if (-not $DoNotShowConnectionDetails) {
$connection.PSObject.Properties | ForEach-Object { Write-Verbose "$($_.Name): $($_.Value)" }
iserrano76 marked this conversation as resolved.
Show resolved Hide resolved
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
}
}

if ($foundError) {
return $false
} else {
Write-Verbose "All expected scopes are present."
return $true
}
iserrano76 marked this conversation as resolved.
Show resolved Hide resolved
}

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)"
}
}
Loading