Files
webhook-server/docs/recipes/zerto-pre-post-scripts.md
T
justin c49a2a12cb Documentation: install/upgrade/uninstall guides + recipes incl. Zerto
Adds a docs/ folder under the repo root with full operator documentation
aimed at sysadmins (not webhook developers). The Zerto pre/post script
recipe is the canonical "why does this exist" walkthrough; the GitHub
HMAC, AD password reset, and UI-on-desktop recipes round out common
patterns.

Pages:
- README.md (index)
- concepts.md (5-minute "what is a webhook" explainer)
- installation.md (interactive + silent install)
- upgrading.md (single-click upgrade flow + edge cases)
- uninstalling.md (clean removal + wiping ProgramData)
- runas-modes.md (Service / InteractiveUser / SpecificUser decision flow)
- service-account-and-ad.md (gMSA setup, delegated rights)
- network-and-security.md (bind addresses, allowlists, HTTPS, secret storage)
- troubleshooting.md (symptom -> first check, common errors)
- recipes/zerto-pre-post-scripts.md (canonical use case)
- recipes/github-style-hmac.md (GitHub / Stripe-shaped webhooks)
- recipes/ad-password-reset.md (gMSA-backed self-service reset)
- recipes/ui-on-desktop.md (InteractiveUser pattern)

Top-level README.md restructured to point at docs/ as the source of
truth, dropping the duplicated installation snippets.

Installer ships docs/ alongside the binaries so they're available
offline at C:\Program Files\WebhookServer\docs\. GUI Help menu gains
a "Documentation" item that opens the docs site in a browser.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:47:44 -04:00

10 KiB

Recipe: Zerto pre/post scripts → AD / DNS update

This is the canonical reason Webhook Server exists. Zerto's failover, move, and clone operations support pre- and post-scripts — but those scripts run on the Zerto Virtual Manager (ZVM), not on the destination domain controller or DNS server. To touch AD or DNS during a failover you need either:

  • A bastion / utility host with the right modules and credentials installed (and you accept the maintenance burden of keeping its scripts in sync)
  • A webhook on a Windows host — Zerto's pre/post calls a single URL, and the webhook server runs the right PowerShell on the right machine with the right identity. This page is about that.

What we're building

A Zerto pre/post script POSTs to http://webhooks.contoso.local:8080/hook/dr-failover-prep with a JSON body identifying the VPG and target VMs. The webhook server, running on a domain-joined utility host as a gMSA with delegated AD rights, runs PowerShell that:

  1. Updates AD computer object descriptions to indicate they're now at the DR site
  2. Updates DNS A records to point app01.contoso.local and friends at the new (DR) IPs
  3. Posts a result line to a Teams channel
  4. Returns 200 with the summary so it shows up in Zerto's pre/post script log

It's about ~30 lines of PowerShell on the server side and 3 lines of script in Zerto.

Prerequisites

On the webhook host:

  • Webhook Server installed (see Installation)
  • The host is domain-joined
  • The service account has the AD permissions it needs. We'll configure this two ways below — the simple way (LocalSystem + delegated rights to the machine account) and the production way (gMSA).
  • DNS PowerShell module installed if you'll modify DNS: Install-WindowsFeature RSAT-DNS-Server (Server) or RSAT installed (Win 10/11).
  • AD PowerShell module: Install-WindowsFeature RSAT-AD-PowerShell (Server).

On the Zerto side:

  • ZVM 8.x or 9.x (this works with both)
  • A Virtual Protection Group (VPG) you want to wire up

1. Plan the script and the inputs

What does the script need to know? At minimum:

  • VPG name — Zerto exposes this as a parameter to the pre/post script
  • VM names — likewise
  • Target IPs — depending on your failover topology, these may be static (DR network has known IPs) or known after Zerto reconfigures the IP

Decide what travels in the request body and what's hardcoded. A pragmatic split:

  • Hardcoded (in the PowerShell script on the webhook host): zone name, AD OU, Teams webhook URL, mapping table from VM hostname → target IP
  • Sent in the body: VPG name, list of VM names, an "operation" field (failover, move, failback, etc.)

Example body the Zerto script will send:

{
  "operation": "failover",
  "vpg": "App-Production",
  "vms": ["app01", "app02", "db01"]
}

2. Write the PowerShell script on the webhook host

Save this as C:\Scripts\dr-failover-prep.ps1 on the webhook host:

[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'

# Read the body from stdin (the webhook server pipes the JSON in for us when
# StdinJson is enabled).
$body = $input | ConvertFrom-Json

# Hardcoded site config - edit for your environment.
$dnsServer    = 'dc01.contoso.local'
$forwardZone  = 'contoso.local'
$adOu         = 'OU=Servers,DC=contoso,DC=local'
$teamsWebhook = 'https://contoso.webhook.office.com/...'   # one-way, no secret to leak
$drIpMap      = @{
    'app01' = '10.42.10.11'
    'app02' = '10.42.10.12'
    'db01'  = '10.42.10.21'
}

$summary = @()

foreach ($vm in $body.vms) {
    if (-not $drIpMap.ContainsKey($vm)) {
        $summary += "skip $vm - no DR IP mapping"
        continue
    }
    $newIp = $drIpMap[$vm]

    # 1. Update DNS A record (delete + recreate is the simplest reliable path)
    $existing = Get-DnsServerResourceRecord -ZoneName $forwardZone -Name $vm `
                  -RRType A -ComputerName $dnsServer -ErrorAction SilentlyContinue
    if ($existing) {
        Remove-DnsServerResourceRecord -ZoneName $forwardZone -Name $vm `
            -RRType A -RecordData $existing.RecordData.IPv4Address `
            -ComputerName $dnsServer -Force
    }
    Add-DnsServerResourceRecordA -ZoneName $forwardZone -Name $vm `
        -IPv4Address $newIp -ComputerName $dnsServer -TimeToLive 00:05:00

    # 2. Update AD computer description so on-call can see at a glance
    Set-ADComputer -Identity $vm -Description "[DR-$($body.operation)] $(Get-Date -Format s)"

    $summary += "ok   $vm -> $newIp"
}

# 3. Notify Teams
$msg = @{
    text = "Webhook DR prep for VPG **$($body.vpg)** ($($body.operation)):`n" +
           ($summary -join "`n")
} | ConvertTo-Json
Invoke-RestMethod -Uri $teamsWebhook -Method POST -ContentType 'application/json' -Body $msg | Out-Null

# 4. Print the summary so Zerto's pre/post script log captures it
$summary -join "`n"

A few choices worth calling out:

  • $input | ConvertFrom-Json — Webhook Server pipes the request body into the script via stdin when "JSON body to stdin" is ticked. $input is PowerShell's automatic variable for pipeline input.
  • $ErrorActionPreference = 'Stop' — turn cmdlet warnings into terminating errors so the script exits non-zero on real problems. Webhook Server then returns 502 (configurable via "Fail on non-zero exit") and Zerto sees the failure.
  • Two-way Teams notification but one-way return — the script's stdout becomes the HTTP response. Zerto logs it. The Teams notification is a separate Invoke-RestMethod.

3. Configure the endpoint in the GUI

In Webhook Server's GUI, File → New endpoint:

Section Setting Value
Identity Slug dr-failover-prep
Identity Description "Zerto pre-script: update AD/DNS during failover"
Auth Mode Bearer
Auth Bearer secret generate a 32-byte random string; copy it for the Zerto script
Allowed clients (one per line) 10.0.0.0/8 (your ZVM's network)
Executor Type Windows PowerShell
Executor Script path C:\Scripts\dr-failover-prep.ps1
Data passing JSON body to stdin
Data passing Headers/query as env vars
Run as Identity Service if the service is running as a gMSA with AD rights, otherwise SpecificUser with a delegated account
Response Mode Sync
Response Timeout (sec) 60
Response Fail on non-zero exit

Save. Right-click the row → Copy URL to grab the full URL, e.g. http://webhooks.contoso.local:8080/hook/dr-failover-prep.

Why Bearer auth and not None? Even though the IP allowlist limits who can reach this endpoint, the Bearer token is a defense-in-depth layer. If someone managed to spoof or get on the trusted network, they still need the token. Generate it once, store it in a secrets manager (or in Zerto's encrypted script parameters), and never email it.

4. The Zerto pre/post script

Zerto pre/post scripts are PowerShell files placed on the ZVM. The path varies by Zerto version; in 9.x it's typically C:\Program Files\Zerto\Zerto Virtual Replication\Scripts\.

Create dr-failover-prep.ps1 on the ZVM:

# Zerto passes context as parameters/environment - exact names vary by version.
# Document yours; this is illustrative.
param(
    [string]$VpgName = $env:ZertoVPGName
)

$webhookUrl = 'http://webhooks.contoso.local:8080/hook/dr-failover-prep'
$bearer     = 'paste-the-bearer-secret-here'  # store via Zerto secret param if available

# Build the body. In a real script, list the VMs by querying Zerto's API or by
# convention from the VPG name.
$body = @{
    operation = 'failover'
    vpg       = $VpgName
    vms       = @('app01','app02','db01')
} | ConvertTo-Json

$response = Invoke-RestMethod -Method POST -Uri $webhookUrl -Body $body `
              -ContentType 'application/json' -TimeoutSec 90 `
              -Headers @{ Authorization = "Bearer $bearer" }

# Print whatever the webhook returned to Zerto's log.
$response.stdout

Wire this script into your VPG's Pre-Recovery or Post-Recovery hook in the Zerto UI.

5. Test before going live

In a maintenance window, hit the endpoint manually with a fake VPG name to confirm the wiring works:

$body = @{ operation='test'; vpg='SmokeTest'; vms=@('app01') } | ConvertTo-Json
Invoke-RestMethod -Method POST `
    -Uri http://webhooks.contoso.local:8080/hook/dr-failover-prep `
    -Headers @{ Authorization = "Bearer paste-the-secret" } `
    -ContentType application/json -Body $body

You should see the summary line(s) come back, AD descriptions update, DNS A records update, and a Teams notification. If anything's off:

  • No response, hang → check the GUI's log panel. The auto-poll updates every 3 seconds. Look for the run line with the slug + exit code.
  • 401 Unauthorized → bearer mismatch
  • 403 Forbidden → IP allowlist blocking you
  • 502 Bad Gateway → script ran but exited non-zero. The response body has stderr.

After a real failover triggers it, audit by checking the daily log file at C:\ProgramData\WebhookServer\logs\webhook-YYYYMMDD.log for the Run <id> dr-failover-prep ok exit=0 line.

Variations

Different actions for failover vs. failback

Pass an operation field in the body and branch on it in the PowerShell. The script above already does this — extend the switch to handle failback (revert DNS to production IPs, clear DR description, etc.).

Per-VPG endpoints

If you want fine-grained access control per VPG, create one endpoint per VPG and give each its own bearer secret. The GUI's grid handles dozens of endpoints fine.

Async + callback for long-running work

If your AD/DNS update genuinely takes minutes (e.g., updating thousands of records in a large environment), set the endpoint to Async mode. Zerto's pre-script gets 202 Accepted immediately and continues. Configure the endpoint's Callback with a URL that records the result (e.g., another endpoint that logs to a file, or your monitoring system's API).

Audit trail to a SIEM

Configure each endpoint's Callback with your SIEM's HTTP collector URL + an HMAC secret. Every run produces a JSON record with runId, exit code, duration, stdout, and stderr — perfect for compliance audit logs.