EWS scripts in PowerShell after the EWS retirement

Many administration scripts load Microsoft.Exchange.WebServices.dll with Add-Type and call EWS through it. In Exchange Online they stop working when EWS is switched off. With Sunsetless EWS the same script calls Microsoft Graph, after three changes at its top.

Which PowerShell

PowerShellWorksNote
Windows PowerShell 5.1YesNeeds the dependency loader from step 3
PowerShell 7.6Yes
PowerShell 7.4NoIt cannot load the newer System.Text.Json the package needs. Microsoft ends its support on November 10, 2026

The script has to load the EWS Managed API itself, as a DLL. Exchange Online PowerShell cmdlets such as Get-Mailbox do not use EWS and are not affected.

1. Put the package and its dependencies in one folder

The package comes from NuGet.org, like any other .NET library. nuget.exe downloads it with its dependencies; the loop then copies the DLLs for your PowerShell into one folder. Run it once, in the PowerShell that will run the script.

$lib = "C:\Scripts\Sunsetless"
Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile "$env:TEMP\nuget.exe"

New-Item -ItemType Directory -Force $lib | Out-Null
$framework = if ($PSVersionTable.PSEdition -eq "Core") { "net8.0" } else { "net48" }
& "$env:TEMP\nuget.exe" install Sunsetless.Ews -Framework $framework -OutputDirectory "$lib\packages" -NonInteractive
$order = if ($framework -eq "net48") { "net48", "net472", "net471", "net47", "net462", "net461", "netstandard2.0" } else { "net8.0", "netstandard2.1", "netstandard2.0" }
foreach ($package in Get-ChildItem "$lib\packages" -Directory) {
    $dir = $order | ForEach-Object { Join-Path $package.FullName "lib\$_" } | Where-Object { Test-Path $_ } | Select-Object -First 1
    if ($dir) { Copy-Item "$dir\*.dll" $lib }
}

2. Give it an app registration

The library calls Microsoft Graph as an app registration in your tenant, with application permissions for what the script does. Getting started lists them. The script reads the app and the license key from four environment variables, which have to be set before the first ExchangeService is created. Use a certificate instead of the secret in production: SUNSETLESS_CERT_THUMBPRINT in place of SUNSETLESS_CLIENT_SECRET.

$env:SUNSETLESS_TENANT_ID = "<tenant ID>"
$env:SUNSETLESS_CLIENT_ID = "<application (client) ID>"
$env:SUNSETLESS_CLIENT_SECRET = "<client secret>"
$env:SUNSETLESS_LICENSE_KEY = "<license or trial key>"

A script that opens one mailbox without ImpersonatedUserId also needs SUNSETLESS_DEFAULT_MAILBOX with that mailbox’s address.

3. Load the library from that folder

Point Add-Type at the folder from step 1. Windows PowerShell 5.1 has no binding redirects, so it also needs a small loader that finds the dependencies in the same folder by name. Everything after Add-Type stays as it is, including the code that gets the OAuth token for EWS.

$lib = "C:\Scripts\Sunsetless"

if ($PSVersionTable.PSEdition -ne "Core") {
    Add-Type -TypeDefinition @"
using System; using System.IO; using System.Reflection;
public static class LibResolver {
    public static void Register(string dir) {
        AppDomain.CurrentDomain.AssemblyResolve += (s, e) => {
            string path = Path.Combine(dir, new AssemblyName(e.Name).Name + ".dll");
            return File.Exists(path) ? Assembly.LoadFrom(path) : null;
        };
    }
}
"@
    [LibResolver]::Register($lib)
}

Add-Type -Path "$lib\Microsoft.Exchange.WebServices.dll"

# The rest of the script is unchanged.
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)

The loader is C#, not a PowerShell script block: the library also loads dependencies on background threads, where a script block cannot run.

[Sunsetless.Ews.SunsetlessEws]::IsEnabled returns True when the calls go to Microsoft Graph. With SUNSETLESS_DISABLED=1 the same script talks to EWS again, as long as your tenant still allows it, which is a quick way to compare the two.

What the script can do

The same as an application on the package: the compatibility table lists every EWS operation and how it maps to Microsoft Graph. Mailboxes on Exchange Server keep using EWS, so a script that works with both keeps working with both.

Sources

More guides