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 accepts RSA certificates only — it cannot use ECDSA. CertAutoPilot issues ECDSA P-256 by default, so a certificate destined for an Exchange target must be created with an RSA key (2048 bits or larger).
This is worth stating plainly because the failure is silent on the Exchange side: an ECDSA certificate imports into the Windows certificate store, private key and all, and the import command reports success — but Exchange never lists it and can never bind it to a service. CertAutoPilot therefore checks the key type before deploying and stops with unsupported key type, and the plan preview warns about it before you run anything.
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
- A certificate with an RSA key (see the note above).
- 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). - If you turn service restarts on for
all_orgorexplicit_list: CertAutoPilot restarts services by connecting to each Exchange server itself, so WinRM must be reachable from CertAutoPilot to every server in the organization, not only to the one the target names. Each server is dialled by the Active Directory FQDN the organization reports for it (Kerberos builds anHTTP/<fqdn>service principal and cannot use a short name or an IP — you may still list a server by its short name; the run looks the FQDN up), on the same port and scheme as the target. The service account needs permission to restart services on each server. No server-to-server access is required. - Edge Transport servers are not supported. They are not domain-joined and have no RBAC
/PowerShellendpoint, so no management session can be opened on them.all_org, the health check's server list and the plan preview's "servers left out" warning all ignore them.
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).
# The principal is <username>@<realm> built from the credential and the target's domain:
$cred = New-Object PSCredential('svc-certdeploy@CORP.LOCAL', $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 Get-ExchangeServer,Get-ExchangeCertificate,`
Import-ExchangeCertificate,Enable-ExchangeCertificate,Remove-ExchangeCertificate -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, BEFORE any restart (restarting W3SVC tears down this session):
# Remove-ExchangeCertificate -Server $srv -Thumbprint <old> -Confirm:$false
# optional restart — on the connected server inside this script; for any other
# server over CertAutoPilot's own connection to the FQDN Get-ExchangeServer reports:
# Restart-Service W3SVC, WAS / MSExchangeTransport …
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.
Where the passwords go
None of the passwords involved is ever written on a Windows command line. Every Exchange operation —
a deployment, a dry run, a health check, and the server enumeration behind all_org — first uploads
the service account's password to a temporary file on the target that only administrators can read,
and the script reads it from there. A deployment adds a second such file for the certificate
bundle's own password. Both are deleted when the operation ends, including when it is cancelled or
times out.
This is why even a health check writes to the target: it is a probe, but not a read-only one. If the account cannot write to the target's temporary directory, operations fail with "uploading session credential" rather than an authentication error.
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). An IP address or a single-label short name is rejected when the target is saved. 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\. - On plain HTTP (5985): the WinRM service on the Exchange server needs
AllowUnencrypted=true— the client does not encrypt the WSMan body (applies to every auth type, Kerberos included). Not needed over the default HTTPS 5986. - Nothing to install on the CertAutoPilot host — no
krb5.conf, no mounts. The Kerberos configuration is built in memory and the domain controller is discovered automatically via the AD DNS SRV record. If this host's DNS cannot resolve AD records, fill the optional KDC Address field on the target instead. Details: WinRM Kerberos.
The account must also have Exchange RBAC for the certificate cmdlets. An account with no Exchange
role at all cannot open the /PowerShell session, which surfaces as EXCHANGE_EMS_UNAVAILABLE. An
account whose role opens the session but does not expose Import-/Enable-ExchangeCertificate gets
no proxy for those cmdlets: the deployment script fails on the missing command and the run reports
EXCHANGE_IMPORT / EXCHANGE_ENABLE with the per-server message — and the plan preview warns up
front that the cmdlet is not available in this session.
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), by its AD FQDN. An IP address or a single-label short name is rejected when the target is saved — Exchange targets authenticate with Kerberos, which needs the HTTP/<fqdn> service principal. A URL or path is refused too; a :port typed here moves into port, and TLS must match the port (5985 plain / 5986 TLS). An existing target is re-checked only when its hostname, port or TLS setting changes. |
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. |
kdc_host | Optional KDC pin (host or host:port). Empty = the KDC is discovered via the AD DNS SRV record. Set only when the CertAutoPilot host's DNS cannot resolve AD records. |
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. A short name (EXCH02) or an FQDN (exch02.corp.local) both work; the picker stores the FQDN. With restart_services on, a server other than the connected one is restarted over a separate Kerberos-authenticated connection to the FQDN the organization reports for it (looked up with Get-ExchangeServer during the run); a typed FQDN is used only when the organization reports none. An IP address is never dialled — if no FQDN is known for a server, its restart is skipped and reported as a warning naming that server. |
restart_services | Restart the bound services after enabling (Restart-Service; iisreset is never used). Default off. |
delete_old_cert | Remove the previous certificate after enabling. Default off. Only the certificate this target deployed for this certificate last time is removed, never anything else on the server. 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. A wildcard certificate does not work here.
Exchange refuses to bind a certificate whose subject is not a fully qualified domain name to POP
or IMAP — and it reports that refusal as a warning while still returning success, so the
service quietly keeps serving the previous certificate. Either leave POP/IMAP out of the target's
services, or name the service FQDN on the server with
Set-PopSettings -X509CertificateName/Set-ImapSettings -X509CertificateNameand restart the service. IIS and SMTP accept a wildcard normally.
CertAutoPilot checks the certificate's subject before deploying and warns when this combination would be refused, naming the services and the cmdlet that fixes each one — in the plan preview and again on the run itself. It does not block the deployment: IIS and SMTP still bind correctly. Exchange's own warnings from the import and enable are also reported with the run, so a refused service is visible instead of being hidden behind a green result.
Choosing from the organization (live picker)
On an Exchange target set to Explicit list, the Servers field can read the
organization and offer what is there instead of asking you to type names. The
list is what Get-ExchangeServer reports, minus Edge Transport servers (they are
not domain-joined and cannot be reached this way), and each row shows the
server's role and the full domain name the organization publishes for it.
A picked server is stored by that full domain name (the row is labelled with the short name). That is the name a service restart needs: a restart is a separate Kerberos-authenticated connection from CertAutoPilot to that server, and Kerberos cannot address a short name or an IP. Typed short names work as well — before restarting a server the run asks the organization for its full domain name — so the FQDN rule is: the server is restarted when the organization reports an FQDN for it, or when the entry you typed is one.
One thing on each row is worth reading before you pick: whether the server can be restarted. A server the organization reports no full domain name for takes the certificate but has its restart skipped, with a warning naming that server. The list says so up front.
Listing is read-only: it enumerates servers and touches no certificate. Every field still accepts a typed value, so an organization CertAutoPilot cannot reach never blocks the form. Browsing through a saved target is a project operator action; browsing from a target form you have not saved yet uses the connection details on screen and needs project admin, as does browsing a saved target after changing its host, port, TLS, realm or KDC.
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.
One failure is different: if the Exchange management session itself stops opening partway through
(Exchange caps concurrent remote shells per user, so a leaked session can exhaust them mid-run), the
remaining servers are not attempted — every one of them would open its session against the same box.
The servers already deployed keep their certificate, and the ones never reached are listed by name as
failures with the reason "not attempted", so the count you see always covers the whole server list.
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.
Time budget per server
Each server gets its own budget — twice the command timeout plus 60 seconds for the service restart (660 s with the defaults) — shortened to what the job's execution timeout has left, minus a 15-second reserve so the run can still report. Before each server the run checks the time left: a server that could not get at least 45 seconds, and every server after it, is reported "not attempted (time budget)" (the target is partial with that reason) instead of being started and cut off halfway. The servers already finished keep their certificate and their recorded state.
A server's deploy script is re-run once after a short pause only when the request provably never reached the server (the name did not resolve, or the connection was refused or unroutable). It is never re-run after a timeout, a dropped connection or a server that accepted the request and went quiet — the first script may still be running there — nor when that script restarts services itself (the connected server with Restart services on), after a management-session failure, or when too little time is left. The job log carries one line per server with how long its script took and, when there was one, its service restart.
When services were restarted, the post-deploy validation waits 30 seconds and retries more patiently before it judges the endpoint; if it still cannot confirm the certificate, the distribution is unverified and re-checked automatically five minutes later — the usual case for the connected server, which is restarted last.
Deploy order
Servers are deployed one at a time, and the order is chosen for you — there is nothing to configure and no dependency between servers to get right.
The rule that matters: the server CertAutoPilot connects to is always deployed last. Every
server's Exchange session is opened through that server's /PowerShell endpoint, which IIS hosts.
If it were deployed first and restarted W3SVC, the endpoint would go down underneath the servers
still waiting, and they would be reported as never attempted. Deploying it last means its restart
can only affect work that is already finished. The remaining servers run in a stable alphabetical
order, so two runs can be compared line by line. Under explicit_list this overrides the order you
typed — for the connected server only.
If a server joins the organization later
apply_mode defaults to This server only, which is the right default for a standalone box and
the wrong one for a DAG that has just grown. A second Exchange server is then simply never deployed
to, and its certificate expires without any run ever failing.
CertAutoPilot enumerates the organization during every plan preview, so it compares the roster against what the target would actually reach and warns when servers would be left out, naming them. The plan's own target list shows only the servers the run will touch — not the whole organization.
Set apply_mode: all_org and this takes care of itself: the organization is re-resolved on every
run, so a server added after the target was created is picked up automatically. Use
explicit_list only when you deliberately want a subset — and remember that list is yours to
maintain.
Service restarts are handled per server. On the server CertAutoPilot is connected to, the deployment script restarts the services itself. For any other server, CertAutoPilot opens its own WinRM connection to that server and restarts them there.
That second connection is why the prerequisites ask for WinRM reachability to every server. Earlier
releases tried to reach across from the connected server with Get-Service -ComputerName, which
cannot work: that call runs outside the Exchange session that solves the Kerberos double-hop, so it
had no way to authenticate to the other machine — and the failure was invisible, because the error
was swallowed and the deployment reported success with nothing restarted.
The other server is dialled by the FQDN the organization reports for it — the deployment script
looks it up with Get-ExchangeServer inside the session it already holds — so a server listed by its
short name is restarted too. A typed FQDN is used only when the organization reports none.
A restart that fails never fails the deployment: the certificate is already imported and enabled. It is reported as a warning naming the server, the service and the reason, and the next scheduled run re-executes so the restart is attempted again. If no fully-qualified name is known for a server (the organization reports none and the entry you configured is not one), CertAutoPilot says so, naming the server, rather than attempting a connection it cannot authenticate.
Exchange services with no matching Windows service (UM, UMCallRouter, Federation) are reported
as such instead of being silently skipped.
When the stored fingerprint, thumbprint, services and binding all match, the run does not trust that record on its own. It opens one management session on the connected server and checks every server the record names: the certificate's thumbprint must still be on the server (on the connected server, the local certificate store also confirms the private key) and, when IIS is a requested service, the IIS :443 binding must still carry it. Other servers' IIS bindings are read over their own WinRM connection. Only then does it report success as unchanged ("verified on N server(s)").
If a certificate was removed or re-bound by hand, a binding cannot be read, or the check cannot connect, the run redeploys instead and warns with the reason. SMTP, POP and IMAP bindings cannot be read remotely, so drift there is still invisible; a tls_fingerprint validation endpoint catches it. An unchanged run therefore costs one extra management session, plus one WinRM connection per additional server when IIS is bound.
With restart_services on, the redeploy restarts services only when a mismatch was actually observed (the certificate is gone or present without its private key, or IIS serves another one). When the check merely could not complete — a connection failure or a binding that cannot be read — the certificate is re-enabled but no service is restarted, and the run says so; restart by hand if a server really drifted.
On servers other than the connected one, the private key of an already-present certificate cannot be seen. A public-only copy with the same thumbprint pre-staged on such a server skips the import there, and the enable then fails on that server with Exchange's own error.
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. A repeat
rollback to a version the server still holds is skipped only after the server confirms it, and a
rollback to a version still in the store re-enables it without importing it again.
Troubleshooting
| Error | Meaning |
|---|---|
EXCHANGE_CONNECT | WinRM unreachable, or a Kerberos domain controller that could not be reached (a KDC that rejects the login is EXCHANGE_AUTH instead) — the message names the layer (nothing is listening on …, TLS on/off mismatched with the listener, DNS) and the fix; see WinRM troubleshooting (retried). |
EXCHANGE_AUTH | The server refused the WinRM request (HTTP 401/403 — the message quotes the auth schemes the listener offered), or a TLS-verify failure (an untrusted or mismatched listener certificate) / Kerberos misconfiguration (terminal, not retried). Exchange targets are Kerberos-only: bare username, Realm, AD FQDN. |
EXCHANGE_EMS_UNAVAILABLE | The Exchange /PowerShell RBAC session did not open (the reason is included) — not a real Exchange server (Mailbox/CAS), remote PowerShell disabled for the account, the account has no Exchange RBAC role at all, the per-user concurrent-session limit is exhausted (a leaked session from a killed run), or the endpoint is down while IIS restarts on the connected server. Retried: the last two clear on their own; a permanent cause simply fails again. |
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; or the host refused an upload for its size (MaxEnvelopeSizekb too low — restore it to 500). |
EXCHANGE_ENABLE | Enable-ExchangeCertificate failed — services/thumbprint. |
EXCHANGE_UNSUPPORTED_KEY_TYPE | The certificate does not use an RSA key. Exchange accepts RSA only; re-issue it with an RSA key. |
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.
These situations are reported as warnings rather than failures, each naming the server and the reason: a service restart that failed, a server for which no fully-qualified name is known (the organization did not report one and the configured entry is not one, so it cannot be reached to restart services), and an Exchange service with no matching Windows service to restart.
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 — and, when restarts are on, opens a short-lived
connection to each other server purely to restart services, which is outside
that lock. 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.
See also
Timeouts
| Field | Default | Notes |
|---|---|---|
connect_timeout_seconds | 30 | The TCP connect. Every WinRM request then waits at least 60 s (or this value, if longer) for an answer. |
command_timeout_seconds | 300 | Applied per server, so an all_org fan-out multiplies it; a server's whole budget is twice this plus 60 s (details). |
One WinRM shell on the connected server serves the whole deploy (fewer round trips on slow links); set CERTAUTOPILOT_WINRM_SHELL_REUSE=false in the backend's environment to go back to a shell per command.
A health check gives each Exchange target 60 seconds (the whole check is capped at 100 s), and checks up to five hosts at a time. A dry run checks several targets in parallel (one host at a time per host) with a per-target budget inside the 100-second dry-run limit; a target it had no time left for is reported "not checked". The live servers picker has 90 seconds, since opening the management session alone can take tens of seconds.
Automatic warnings
On a run:
tls_skip_verifyis on — "TLS verification disabled — insecure transport". Emitted on every run; the health check lists it as a warning on the target's row.- IIS is among the enabled services but
restart_servicesis off — the new certificate will not serve on IIS until someone restarts it. Emitted on every successful run. - A requested service restart failed on a server, or could not be attempted: no FQDN is known for the server, the server could not be reached over WinRM, or an Exchange service has no matching Windows service (
UM,UMCallRouter,Federation). The run still succeeds; the remote-state fingerprint is blanked so the next run re-attempts. - The previous certificate was not removed (
delete_old_certon,Remove-ExchangeCertificatefailed) — remove it manually. - Some servers failed — "some servers failed: …" with each server's reason (the target result is partial).
- Servers were not attempted because the management session stopped opening partway through the list — they are named separately.
- A server recorded by the previous run was not addressed by this one (dropped from
explicit_list) while still holding the previous certificate. Names are compared tolerant of short-name/FQDN form, so a single-server target and a list re-picked from short names to FQDNs do not warn. - Another certificate is also deployed to this target — see One certificate per target.
- The certificate's subject is a wildcard while POP or IMAP is among the services — Exchange will refuse those two.
- Exchange itself warned during the import or enable. Its warnings are reported verbatim, because that is how Exchange reports a service it declined to bind.
In the plan preview (dry run), in addition:
- The certificate's key is not RSA (or is under 2048 bits) — the run would refuse it.
- The organization reports servers this target would not reach — see If a server joins the organization later.
- Which certificate currently serves a requested service on the connected server (from the IIS
:443binding; or, when no443binding carries a certificate, the first HTTPS binding that does), and that multi-server previews inspect only the connected server. Import-orEnable-ExchangeCertificateis not available in this session — the account's RBAC role does not expose it and the run would fail.
One certificate per target
A service is served by exactly one certificate, organization-wide.
Enable-ExchangeCertificate hands the service to whichever certificate ran last, so
two CertAutoPilot certificates pointed at the same Exchange target take the bindings
away from each other on every deployment and every renewal.
This is unlike IIS, where two certificates occupy different bindings and coexist
happily. On Exchange, give each certificate its own target — or, if you really
need two, split the services between them (one target with IIS,SMTP, another with
POP,IMAP) so they are not competing for the same slot.
CertAutoPilot detects the situation and warns on every run. It also stops trusting its own record while it lasts: normally an unchanged certificate is skipped on the strength of the stored deployment record, but a sibling certificate can take the bindings away without that record ever changing — so with more than one certificate on the target, every run re-imports and re-enables instead of skipping. The import and enable are idempotent, so this costs a little time and changes nothing else.