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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
For non-Windows hosts, this script will only work if you change your libmi library with this forked version.