Skip to content

Conversation

@iserrano76
Copy link
Contributor

Issue:
Added two functions to Shared in case any script needs a connection to M365.
One function for Exchange Online another function for AzureAD

Reason:
Unify the way we connect to M365.

Fix:
Both functions will do the following:

  • Verify if the module is installed, if not, install it after request confirmation.
  • Verify if the module is loaded, if not, load it.
  • Lastly, verify if any connection is present (we verify only one connection is present); if none, we offer to connect.
    Important: we do not disconnect at anytime we just verify if we have an active session

@iserrano76 iserrano76 requested a review from a team as a code owner May 9, 2024 14:05
Copy link
Contributor

@lusassl-msft lusassl-msft left a comment

Choose a reason for hiding this comment

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

Requesting changes as there are a few things that should be changed (see comments).

Copy link
Member

@dpaulson45 dpaulson45 left a comment

Choose a reason for hiding this comment

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

Please address all open comments then let me know when this is ready for the next review.

Copy link
Member

@dpaulson45 dpaulson45 left a comment

Choose a reason for hiding this comment

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

There might be some more things that I find later on, but here is a good starting point to address.

@dpaulson45
Copy link
Member

We should also look into adding pester testing here as well.

@iserrano76
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@iserrano76
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dpaulson45
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dpaulson45
Copy link
Member

Pending review from Lukas as well.

Comment on lines 9 to 34
[Parameter(Mandatory = $false)]
[System.Version]$MinModuleVersion = $null
Copy link
Contributor

Choose a reason for hiding this comment

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

We could also think about removing the MinModuleVersion parameter and instead passing a key-value pair via Module parameter. This could be a dictionary (because they are strongly typed):

$modules = New-Object "System.Collections.Generic.Dictionary[String, Version]"
$modules.Add("ModuleToLoad", "3.0.0.0")

We could then progress them one by one

@dpaulson45
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dpaulson45
Copy link
Member

@lusassl-msft to review the final result, then we will squash this back down a commit and then merge.

Write-Verbose "Checking $m PowerShell Module"
$getParams = @{
Name = $m
ErrorAction = 'SilentlyContinue'
Copy link
Contributor

Choose a reason for hiding this comment

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

@iserrano76 should we change this to ErrorAction = Stop and put the Get-InstalledModule into a try/catch? If we fail to query the modules for whatever reason, we treat it the same as if the module was not found. I think we should change this behavior.

Copy link
Member

Choose a reason for hiding this comment

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

If we are handling it properly, does it matter if we do the try/catch version?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, it shouldn't matter then. If I understand the code correctly, we're trying to install the module if $installed is $null or does not contain $m (module x). Installed would be $null in case that Get-InstalledModule fail for whatever reason. This would mean we're trying installing a module which might be already there. That's my understanding here. Please correct me if I'm wrong.

Comment on lines 9 to 34
[Parameter(Mandatory = $false)]
[System.Version]$MinModuleVersion = $null
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm okay with both approaches. If you want to keep the current approach I'm okay with it.


$noFoundError = $true

foreach ($m in $Module) {
Copy link
Contributor

@lusassl-msft lusassl-msft Feb 11, 2025

Choose a reason for hiding this comment

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

@iserrano76 Sorry to be so petty. Wouldn't it be better to return a final statement for each module that was passed when calling the Request-Module function? We support processing multiple modules in here but return just one state. If one of the modules fail and the remaining ones are processed successfully, we just return $false because one of them has failed. I think it would be better to put the module and final state to a hashtable and finally return it. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @lusassl-msft do not worry, it is a healthy discussion looking for the best solution, no problem at all 😉
Maybe it is easier to simplify and just allow one module each time instead of an array and return true or false for each one.
I think we are not going to request several modules.
What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

That could make it more complicated for the caller to use. If that is the case, I would just adjust to only accept a single module and you have to test against each one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, let’s move to the one module approach then 👍

Copy link
Member

Choose a reason for hiding this comment

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

@iserrano76 are you able to make this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I am trying to find some time for testing.

@iserrano76
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dpaulson45
Copy link
Member

@lukak-msft please review as soon as you can.

@iserrano76 please squash the commits down to 1 so we can clean this up and make it ready to merge once we are done reviewing.

@iserrano76
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@dpaulson45
Copy link
Member

@iserrano76 please squash this down to a single commit, then we will review and merge.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds standardized Microsoft 365 connection functions to the Shared module, providing unified ways to connect to Exchange Online and Microsoft Graph. The implementation includes module validation, installation prompts, and connection management with proper error handling.

  • Adds Request-Module function for PowerShell module management (install/verify)
  • Implements Connect-EXOAdvanced for Exchange Online connections with multi-session support
  • Implements Connect-GraphAdvanced for Microsoft Graph connections with scope validation

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
Shared/ModuleHandle.ps1 Provides module installation and verification functionality
Shared/M365/EXOConnection.ps1 Exchange Online connection management with session handling
Shared/M365/GraphConnection.ps1 Microsoft Graph connection management with scope validation

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +177 to +178
if ($graphConnection.AuthType) {
Write-Host "AuthType: $($graphConnection.AuthType)"
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

Variable $graphConnection is undefined in this context. It should be $Context.AuthType to match the function parameter.

Suggested change
if ($graphConnection.AuthType) {
Write-Host "AuthType: $($graphConnection.AuthType)"
if ($Context.AuthType) {
Write-Host "AuthType: $($Context.AuthType)"

Copilot uses AI. Check for mistakes.
}

if ($null -eq $connections -or $AllowMultipleSessions) {
if ($connections.ModulePrefix -contains $Prefix) {
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

This line will fail when $connections contains multiple connection objects, as ModulePrefix property access on a collection doesn't work as expected. Consider using a proper collection filter or foreach loop to check individual connection prefixes.

Suggested change
if ($connections.ModulePrefix -contains $Prefix) {
if ($connections | Where-Object { $_.ModulePrefix -eq $Prefix }) {

Copilot uses AI. Check for mistakes.
} else {
Write-Host " without prefix." -ForegroundColor Yellow
}
$newConnection = $connections | Where-Object { $_.ModulePrefix -eq $Prefix }
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

This filter may return multiple connections with the same prefix, but later code expects a single connection object. Add -First 1 or handle multiple results appropriately.

Suggested change
$newConnection = $connections | Where-Object { $_.ModulePrefix -eq $Prefix }
$newConnection = $connections | Where-Object { $_.ModulePrefix -eq $Prefix } | Select-Object -First 1

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +123
if ($newConnection.count -gt 1) {
Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix.`nPlease use just one session with same Prefix." -ForegroundColor Red
return $null
}
} else {
Write-Verbose "You already have an Exchange Online session"
if ($connections.count -gt 1) {
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

Using .count property on potentially null objects will cause errors. Use @($newConnection).Count to safely get the count even when $newConnection is null or a single object.

Suggested change
if ($newConnection.count -gt 1) {
Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix.`nPlease use just one session with same Prefix." -ForegroundColor Red
return $null
}
} else {
Write-Verbose "You already have an Exchange Online session"
if ($connections.count -gt 1) {
if (@($newConnection).Count -gt 1) {
Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix.`nPlease use just one session with same Prefix." -ForegroundColor Red
return $null
}
} else {
Write-Verbose "You already have an Exchange Online session"
if (@($connections).Count -gt 1) {

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +123
if ($newConnection.count -gt 1) {
Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix.`nPlease use just one session with same Prefix." -ForegroundColor Red
return $null
}
} else {
Write-Verbose "You already have an Exchange Online session"
if ($connections.count -gt 1) {
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

Using .count property on potentially null objects will cause errors. Use @($connections).Count to safely get the count even when $connections is null or a single object.

Suggested change
if ($newConnection.count -gt 1) {
Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix.`nPlease use just one session with same Prefix." -ForegroundColor Red
return $null
}
} else {
Write-Verbose "You already have an Exchange Online session"
if ($connections.count -gt 1) {
if (@($newConnection).Count -gt 1) {
Write-Host "You have more than one Exchange Online sessions with Prefix $Prefix.`nPlease use just one session with same Prefix." -ForegroundColor Red
return $null
}
} else {
Write-Verbose "You already have an Exchange Online session"
if (@($connections).Count -gt 1) {

Copilot uses AI. Check for mistakes.
@dpaulson45
Copy link
Member

@iserrano76 please squash this down to a single commit, then we will review and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants