Last active
September 20, 2022 19:05
-
-
Save jborean93/d50041c2a0fed20e87aa46ba32381754 to your computer and use it in GitHub Desktop.
Cross platform function that can connect to Exchange Online using modern auth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright: (c) 2020, Jordan Borean (@jborean93) <jborean93@gmail.com> | |
# MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
#Requires -Module MSAL.PS | |
Function New-EXOPSSession { | |
<# | |
.SYNOPSIS | |
Will open a PSSession to Exchange Online. | |
.DESCRIPTION | |
Acts in a similar way to Connect-ExchangeOnline but supports both cert and app secret authentication that is cross | |
platform compatible. It is designed to authenticate with an app registration in a non-interactive fashion. | |
.PARAMETER TenantID | |
The tenant ID as a GUID or the name, e.g. 'tenant.onmicrosoft.com'. | |
.PARAMETER ClientID | |
The client ID of the app registration to use | |
.PARAMETER ClientSecret | |
The client secret of the app registration. This is mutually exclusive from the certificate based authentication | |
parameters. | |
.PARAMETER Certificate | |
The X509 certificate to use for certificate based authentication. This is mutually exclusive with *ClientSecret | |
and the other certificate parameters. | |
.PARAMETER CertificateThumbprint | |
The thumbprint for the certifcate that is stored in Cert:\CurrentUser\My to use for certificate based | |
authentication. This is mutually exclusive with -ClientSecret and the other certificate parameters. This parameter | |
only works on Windows as there is no Cert: provider on other platforms. | |
.PARAMETER CertificatePath | |
The path to the .pfx certificate to use for certificate based authentication. This can be combined with | |
-CertificatePassword if the .pfx is protected with a password. This is mutually exclusive with -ClientSecret and | |
the other certificate parameters (excluding -CertificatePassword). | |
.PARAMETER CertificatePassword | |
The password for the cert specified by -CertificatePath. | |
.EXAMPLE Using app secret | |
$sessionParams = @{ | |
TenantID = '027a19fe-b025-48dc-8f46-fb09e27e17af' | |
ClientID = '05e97e34-e8f0-46eb-821b-37841d043352' | |
ClientSecret = (Read-Host -Prompt "Please enter the client secret" -AsSecureString) | |
} | |
$session = New-EXOPSSession @sessionParams | |
Import-PSSession -Session $session | |
.EXAMPLE Using certificate object | |
$cert = [System.Security.Cryptography.X509Certificate.X509Certificate2]::new($certBytes) | |
$sessionParams = @{ | |
TenantID = '027a19fe-b025-48dc-8f46-fb09e27e17af' | |
ClientID = '05e97e34-e8f0-46eb-821b-37841d043352' | |
Certificate = $cert | |
} | |
$session = New-EXOPSSession @sessionParams | |
Import-PSSession -Session $session | |
.EXAMPLE Using certificate thumbprint | |
$sessionParams = @{ | |
TenantID = '027a19fe-b025-48dc-8f46-fb09e27e17af' | |
ClientID = '05e97e34-e8f0-46eb-821b-37841d043352' | |
CertificateThumbprint = '035F413A0E1900DD7889CF76CA165C52355418C4' | |
} | |
$session = New-EXOPSSession @sessionParams | |
Import-PSSession -Session $session | |
.EXAMPLE Using certificate path | |
$sessionParams = @{ | |
TenantID = '027a19fe-b025-48dc-8f46-fb09e27e17af' | |
ClientID = '05e97e34-e8f0-46eb-821b-37841d043352' | |
CertificatePath = 'MyCertificate.pfx' | |
} | |
$session = New-EXOPSSession @sessionParams | |
Import-PSSession -Session $session | |
.EXAMPLE Using certificate path that is password protected | |
$sessionParams = @{ | |
TenantID = '027a19fe-b025-48dc-8f46-fb09e27e17af' | |
ClientID = '05e97e34-e8f0-46eb-821b-37841d043352' | |
CertificatePath = 'MyCertificate.pfx' | |
CertificatePassword = (Read-Host -Prompt "Certificate password" -AsSecureString) | |
} | |
$session = New-EXOPSSession @sessionParams | |
Import-PSSession -Session $session | |
.OUTPUTS | |
[System.Management.Automation.Runspaces.PSSession] - The opened PSSession to Exchange Online. | |
.NOTES | |
This requires the MSAL.PS PowerShell module to be installed so that we can get the MSAL token. | |
While this works out of the box on Windows, on Linux it will most likely fail due to an unreliable WSMan library | |
that ships with PowerShell. The fork https://github.com/jborean93/omi will allow you to build your own libmi | |
library that allows you to use WSMan from Linux. | |
Once finished with the session you should close it by running '$session | Remove-PSSession'. This ensures any | |
server side resources are released. | |
#> | |
[CmdletBinding(DefaultParameterSetName='ClientSecret')] | |
param ( | |
[Parameter(Mandatory=$true)] | |
[String] | |
$TenantID, | |
[Parameter(Mandatory=$true)] | |
[String] | |
$ClientID, | |
[Parameter(Mandatory=$true, ParameterSetName='ClientSecret')] | |
[Alias('Credential')] | |
[SecureString] | |
$ClientSecret, | |
[Parameter(Mandatory=$true, ParameterSetName='Certificate')] | |
[System.Security.Cryptography.X509Certificates.X509Certificate2] | |
$Certificate, | |
[Parameter(Mandatory=$true, ParameterSetName='CertificateThumbprint')] | |
[String] | |
$CertificateThumbprint, | |
[Parameter(Mandatory=$true, ParameterSetName='CertificatePath')] | |
[String] | |
$CertificatePath, | |
[Parameter(ParameterSetName='CertificatePath')] | |
[SecureString] | |
$CertificatePassword | |
) | |
# If one of the certificate parameters are selected, get the actual X509 certificate object. | |
if ($PSCmdlet.ParameterSetName -eq 'CertificatePath') { | |
$certType = [System.Security.Cryptography.X509Certificates.X509Certificate2] | |
# Resolve the Path so it become the absolute path based on the current PS Path. | |
$CertificatePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($CertificatePath) | |
try { | |
if ($CertificatePassword) { | |
$Certificate = $certType::new($CertificatePath, $CertificatePassword) | |
} else { | |
$Certificate = $certType::new($CertificatePath) | |
} | |
} catch { | |
$msg = "Failed to load certificate from '$CertificatePath': $($_.Exception.Message)" | |
Write-Error -Message $msg -Exception $_.Exception | |
return | |
} | |
} elseif ($PSCmdlet.ParameterSetName -eq 'CertificateThumbprint') { | |
$Certificate = Get-Item -Path Cert:\CurrentUser\My\$CertificateThumbprint -ErrorAction Stop | |
} | |
$msalParams = @{ | |
TenantID = $TenantID | |
ClientID = $ClientID | |
Scopes = 'https://outlook.office365.com/.default' | |
} | |
# Build the client credential based on the auth type chosen. | |
if ($Certificate) { | |
$msalParams.ClientCertificate = $Certificate | |
} else { | |
$msalParams.ClientSecret = $ClientSecret | |
} | |
$msalResult = Get-MsalToken @msalParams | |
# EXO uses Basic auth that wraps the actual MSAL token. It is in the form | |
# Base64("OAuthUser@$TenantID:Bearer $MSALToken") | |
$bearerToken = ConvertTo-SecureString -AsPlainText -Force -String "Bearer $($msalResult.AccessToken)" | |
$exoCredential = [PSCredential]::new( | |
"OAuthUser@$TenantID", $bearerToken) | |
# Create the PSSession to EXO. | |
$sessionParams = @{ | |
Authentication = 'Basic' | |
ConfigurationName = 'Microsoft.Exchange' | |
ConnectionUri = 'https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true' | |
Credential = $exoCredential | |
AllowRedirection = $true | |
} | |
New-PSSession @sessionparams | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For non-Windows hosts, this script will only work if you change your libmi library with this forked version.