Microsoft Exchange module
The Exchange module deploys a certificate to an on-premises Microsoft Exchange Server (2013 / 2016 / 2019 / SE) over WinRM using the Exchange Management Shell (EMS). It imports the PKCS#12, enables it for the selected Exchange services, optionally restarts them, and optionally removes the previous certificate.
Exchange identifies certificates by thumbprint (SHA-1), which is new on every renewal, so each deploy is import-new → enable-new → optional remove-old — there is no in-place update.
Exchange Online (Microsoft 365) is not supported. Microsoft owns and rotates the service TLS
certificates for *.outlook.com / *.protection.outlook.com; the *-ExchangeCertificate cmdlets
exist only in on-premises Exchange. An Exchange Online target is not a thing this module can serve.
Prerequisites
- WinRM reachable on the Exchange server (5986 HTTPS by default, or 5985 HTTP).
- The target is a domain-joined Exchange server (it needs its own Kerberos/KDC access — always true in a real deployment; this is how the module reaches AD, see below).
- A service account enabled for remote PowerShell (
Set-User -RemotePowerShellEnabled $true) with an Exchange RBAC role that grantsImport-/Enable-/Remove-ExchangeCertificate(e.g. an Organization Management or a scoped role-group member). - For the
all_org/ remote-server restart paths, the account needs rights on each server.
Running the Exchange cmdlets through the local snap-in over a WinRM shell fails on any real
domain box — every AD-touching cmdlet returns Active Directory operation failed … The supplied credential … is invalid (ADInvalidCredentialException), even for a Domain Admin. The WinRM session
is a network logon with no credential it can present onward to a domain controller (the classic
Kerberos double-hop), and constrained delegation does not fix it (the cmdlet process
impersonates the user, not the machine).
CertAutoPilot handles this automatically: on the Exchange box it opens a fresh Kerberos
implicit-remoting session to Exchange's own RBAC endpoint —
New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://<fqdn>/PowerShell/ -Credential …
— where the Exchange Trusted Subsystem does the AD work under its own identity. You do not need to
configure CredSSP or Kerberos delegation. Because that RBAC endpoint only accepts Kerberos, the
Exchange target is Kerberos-only (auth_type is locked to kerberos, and the domain realm is
required); address the server by its AD FQDN so the KDC can resolve its HTTP/<host> SPN.
Native flow (implicit remoting to the Exchange RBAC endpoint)
The module connects over WinRM, then on the box opens the Exchange remoting session and imports the management cmdlets; per resolved server it runs:
# on the Exchange box — a fresh Kerberos session to the RBAC endpoint (no double-hop):
$cred = New-Object PSCredential('CORP\svc-certdeploy', $securePw)
$s = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri ('http://' + ([Net.Dns]::GetHostByName($env:COMPUTERNAME).HostName) + '/PowerShell/') `
-Authentication Kerberos -Credential $cred
Import-PSSession -Session $s -CommandName Import-ExchangeCertificate,Enable-ExchangeCertificate,`
Remove-ExchangeCertificate,Get-ExchangeCertificate,Get-ExchangeServer -AllowClobber | Out-Null
Import-ExchangeCertificate -Server $srv -FileData ([IO.File]::ReadAllBytes($pfx)) -Password $pw # → new Thumbprint
Enable-ExchangeCertificate -Server $srv -Thumbprint <new> -Services IIS,SMTP -Force
# optional: iisreset $srv / Restart-Service MSExchangeTransport …
# optional: Remove-ExchangeCertificate -Server $srv -Thumbprint <old> -Confirm:$false
Remove-PSSession $s
-Force suppresses the SMTP default-cert replacement prompt; -Confirm:$false suppresses the
destructive remove prompt. -FileData (byte array) is used on all versions — the -FileName/UNC
parameter was removed in Exchange 2016 CU23 / 2019 CU12. The inner session is always Kerberos —
the Exchange /PowerShell RBAC endpoint does not accept NTLM.
Authentication & Kerberos setup
Exchange is Kerberos-only — auth_type is locked to kerberos (the RBAC endpoint accepts
nothing else, and the realm-based principal must be consistent across the outer and inner legs).
Set it up as follows:
- Address the server by its AD FQDN (never an IP — the Kerberos SPN
HTTP/<fqdn>has no KDC entry for an IP). domain= the uppercase Kerberos realm (e.g.CORP.LOCAL), required.- credential username = the bare account name (e.g.
svc-certdeploy) — the module builds the principal as<username>@<realm>. Do not prefixDOMAIN\. - The CertAutoPilot host needs a
krb5.conf, FQDN resolution, and KDC reachability — follow the full WinRM Kerberos setup (krb5.conf example,/etc/hosts, container mounts).
The account must also have Exchange RBAC for the certificate cmdlets; a missing role surfaces as
EXCHANGE_EMS_UNAVAILABLE (the /PowerShell session opens but the cmdlets are not imported).
Create the credential
Settings → Distribution → Credentials → New, type Exchange WinRM:
{ "username": "svc-certdeploy", "password": "<password>" }
Use the bare username (no DOMAIN\ prefix) — Exchange is Kerberos-only, so the module builds
<username>@<realm> from the target's domain (realm) field. One credential is reusable across
targets.
Create the target
Settings → Distribution → Targets → New, type Microsoft Exchange:
| Field | Notes |
|---|---|
hostname | Exchange server to connect to over WinRM (required). |
port | WinRM port. Default 5986 (HTTPS) / 5985 (HTTP). |
use_tls | Connect over WinRM HTTPS (5986). Default on; off uses HTTP (5985). |
tls_skip_verify | Skip WinRM HTTPS listener-cert verification (self-signed listeners). Insecure — trusted networks only. |
auth_type | kerberos only (locked) — the Exchange RBAC endpoint accepts nothing else. |
domain | Kerberos realm (uppercase, e.g. CORP.LOCAL). Required. |
services | Any of IIS,SMTP,POP,IMAP. Default IIS,SMTP. (UM, UMCallRouter, Federation are also accepted via API for Exchange 2013/2016 — UM was removed in 2019/SE.) |
apply_mode | single (this server, default), explicit_list (a servers list), or all_org (every Get-ExchangeServer, for a DAG). |
servers | Server names — required for explicit_list. |
restart_services | Restart the bound services after enabling (iisreset / Restart-Service). Default off. |
delete_old_cert | Remove the previous certificate after enabling. Default off. Does not affect rollback availability — rollback re-deploys from CertAutoPilot's artifact history. |
do_not_require_ssl | Pass -DoNotRequireSsl when enabling IIS. |
Which services?
- IIS — OWA, ECP/EAC, EWS, ActiveSync, OAB, MAPI/HTTP, Autodiscover. The common case. Microsoft
recommends an
iisresetafterwards, or OWA may keep serving the old certificate. - SMTP — external/edge SMTP TLS. Enabling replaces the default self-signed SMTP cert (handled
with
-Force); usually only needed for edge/external transport. - POP / IMAP — client TLS for those protocols. Don't assign a wildcard cert here — set the FQDN
via
Set-PopSettings/Set-ImapSettings.
Multi-server / DAG
Certificates are per-server (each server's local store). apply_mode: all_org enumerates every
Get-ExchangeServer (Edge Transport servers are excluded — they aren't domain-joined and can't be
reached via -Server) and applies the cert to each server in its own bounded operation, so one
unreachable/offline server is reported (the target result is downgraded to partial with a
warning) rather than aborting the others, and a large DAG can't blow a single command timeout.
command_timeout_seconds is therefore per server, not for the whole batch. The thumbprint is
identical across servers. Use explicit_list to target a subset, or one target per server composed
via a target group. all_org re-resolves the org on every run, so a server added since the last
deploy is picked up automatically.
Service restarts run against each server via Get-Service -ComputerName $s | Restart-Service
(the Windows PowerShell 5.1 remote pattern; IIS = the W3SVC/WAS services), so restarting
remote servers needs the RPC/SCM path reachable, not server-to-server WinRM.
Rollback
Supported via previous-version re-deploy: a rollback re-imports a previous retained certificate
version and re-enables it on the bound services — the module's normal deploy path, fed older
material. It no longer depends on delete_old_cert having been off: the previous version comes
from CertAutoPilot's own artifact history, not from a certificate kept on the server. During a
rollback run the delete_old_cert behavior is suppressed, so the newer certificate stays on
the server and rolling forward again is instant. Eligibility, the version picker, and
auto-rollback: Rollback.
If restart_services is on and a restart fails on some server, the module deliberately does
not record the deployed fingerprint. The stored fingerprint is blanked, so the next run — a job
retry, a repeat rollback, or the next deploy — re-executes instead of skipping as already-current,
and re-attempts the restart. Import and enable are idempotent, so the repeat run is safe.
Troubleshooting
| Error | Meaning |
|---|---|
EXCHANGE_CONNECT | WinRM unreachable — host/port/network (retried). |
EXCHANGE_AUTH | WinRM authentication rejected — username/password/auth-type, or a TLS-verify / Kerberos misconfiguration (terminal, not retried). |
EXCHANGE_EMS_UNAVAILABLE | The Exchange /PowerShell RBAC session did not open or its cmdlets did not import — not a real Exchange server (Mailbox/CAS), remote PowerShell disabled for the account, or the account lacks an Exchange RBAC role for the certificate cmdlets. |
EXCHANGE_CREDENTIAL_MISSING | No or malformed credential — the exchange_winrm username/password is missing. |
EXCHANGE_PFX_FAILED | Could not read the certificate/key or build the PKCS#12 to import. |
EXCHANGE_IMPORT | Import-ExchangeCertificate failed — cert/key or permissions. |
EXCHANGE_ENABLE | Enable-ExchangeCertificate failed — services/thumbprint. |
A failed removal of the previous certificate does not fail the deploy (the new certificate is already enabled) — it surfaces as a warning on the target result so you can remove the stale certificate manually.
Concurrent deploys serialize on the connected server's WinRM lock (shared
with the IIS and WinRM modules). all_org fans out to every Exchange server
from that one connection — so two exchange targets pointed at different
servers of the same organization do not serialize against each other.
Don't schedule two such targets into the same maintenance window.