Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active September 20, 2022 19:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jborean93/d50041c2a0fed20e87aa46ba32381754 to your computer and use it in GitHub Desktop.
Save jborean93/d50041c2a0fed20e87aa46ba32381754 to your computer and use it in GitHub Desktop.
Cross platform function that can connect to Exchange Online using modern auth
# 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
}
@jborean93
Copy link
Author

For non-Windows hosts, this script will only work if you change your libmi library with this forked version.

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