Configure-WindowsIdentityServices
Turn a fresh Windows Server into a fully configured identity server — Active Directory, DNS, DHCP, domain time, a security baseline, and (optionally) PKI/WSUS/log collection — by editing one YAML file and running one script.
This is a sibling project to the Windows Workstation Deployer for Offline Environments. The deployer needs a domain to join workstations to; this project builds that domain (the domain controller). It is standalone and is not part of the deployer’s default workflow.
Read this first. This automation changes core infrastructure and reboots the server. It builds the first domain controller in a brand-new forest. It is not a migration tool and must never be pointed at an existing/production Active Directory without a real migration plan.
Table of contents
Section titled “Table of contents”- What you get
- When to use it (and when not to)
- What you need before you start
- Step 1 — Prepare the Windows Server VM
- Step 2 — Copy the project onto the server
- Step 3 — Install the YAML PowerShell module
- Step 4 — Create your config (the wizard / TUI)
- Step 5 — Preview without changing anything (PlanOnly)
- Step 6 — Run it
- Step 7 — The reboot (this is normal)
- Step 8 — Validate
- Did it work? Quick health check
- Where the logs and evidence live
- If something goes wrong (rollback)
- Passwords
- The golden rule: no defaults in code
- Project layout
What you get
Section titled “What you get”When the run finishes you will have a Windows Server configured as:
- Active Directory Domain Services — a new forest/domain.
- DNS — installed with AD, forwarders, and reverse lookup zones.
- DHCP — authorized in AD, with your scopes, options, and reservations.
- Authoritative domain time (
w32time) on the PDC emulator. - OUs, security groups, and service accounts from your config.
- GPO baseline — password/lockout policy, plus hardening, firewall, and Defender settings you explicitly turn on.
- Optional add-ons (each is off unless you enable it): PKI (AD CS), WSUS, Windows Event Forwarding, Wazuh agent prep, and GitLab/Keycloak/YubiKey identity-integration artifacts.
- Evidence + a final report so you can prove what was done.
When to use it (and when not to)
Section titled “When to use it (and when not to)”Use it for: standing up the first domain controller in a new forest on a clean Windows Server (lab, greenfield site, or the DC the offline deployer will join machines to).
Do NOT use it for: adding a DC to an existing domain, migrating, or touching a production directory. The script refuses to run if the server is already a domain controller for a different domain.
What you need before you start
Section titled “What you need before you start”- A Windows Server VM (Server 2019/2022/2025), freshly installed, not domain-joined.
- A local Administrator login on that server.
- A planned static IP, hostname, domain name, and DNS/DHCP details — written down and approved. The script will not guess any of these.
- A maintenance window — the server reboots during AD promotion.
- Internet access (or a local mirror) only to install the
powershell-yamlmodule once. The script itself never downloads anything from the internet.
Step 1 — Prepare the Windows Server VM
Section titled “Step 1 — Prepare the Windows Server VM”If the server runs on Proxmox (the common case here), do this first so disks and the network behave:
- Install Windows Server and finish initial setup.
- Mount the VirtIO drivers ISO: in Proxmox, VM → Hardware → Add → CD/DVD Drive and
attach
virtio-win.iso. It usually appears as driveD:. - Install the VirtIO drivers (storage + network) from that ISO if Windows did not already pick them up.
- Install the QEMU Guest Agent manually: run
D:\virtio-win-guest-tools.exeand complete the wizard. (The script will not install it silently unless you explicitly allow that in YAML.) - In Proxmox, enable the agent under VM → Options → QEMU Guest Agent.
- Reboot the server.
Not on Proxmox? Set
proxmoxGuest.enabled: falsein your config and skip this step.
Step 2 — Copy the project onto the server
Section titled “Step 2 — Copy the project onto the server”Copy this whole folder to the server, e.g.:
C:\Admin\Configure-WindowsIdentityServicesOpen PowerShell as Administrator (right-click → Run as administrator). The script stops immediately if it is not elevated or not running on Windows Server.
Step 3 — Install the YAML PowerShell module
Section titled “Step 3 — Install the YAML PowerShell module”The script reads YAML, so it needs powershell-yaml. Install it once:
Install-Module powershell-yaml -Scope AllUsersIf the module is missing at runtime, the script stops and tells you this exact command. It will not auto-install modules.
Step 4 — Create your config (the wizard / TUI)
Section titled “Step 4 — Create your config (the wizard / TUI)”Everything the script does comes from your config.yaml. The easiest and safest way to
build it is the interactive wizard, which prompts you for every environment-specific
setting, validates each answer as you type (IP addresses, prefix lengths,
NetBIOS/domain rules, DHCP ranges that must fall inside their subnet, forwarders that must
not point at the server itself, and more), and refuses to move on until the answer is
valid. It runs in any PowerShell console — no GUI, no Python, nothing extra to install
beyond the powershell-yaml module from Step 3.
cd C:\Admin\Configure-WindowsIdentityServices.\New-WindowsServerConfig.ps1What it does:
- Seeds a reviewed baseline from
config.example.yaml(or, if you already have aconfig.yaml, offers to edit that instead). - Walks you through the environment-specific sections in order: environment metadata → execution paths → Proxmox guest → network/identity → Active Directory → DNS → DHCP (incl. building scopes) → domain time → service accounts → optional features (PKI/WSUS/event forwarding/Wazuh/integrations).
- Press Enter to keep the value shown in
[brackets]; type a new value to change it. Bad input is rejected on the spot with an explanation, so you never get a config that fails later. - Writes
config.yamland then runs the project’s own validator as a final gate — if anything is inconsistent it tells you before you ever touch the server.
The wizard intentionally does not prompt for the security/policy baseline (GPO definitions, hardening, firewall, Defender, OU/group structure, validation and reporting). Those are inherited from the template; review and tune them directly in
config.yamlif needed (see the “edit by hand” note below).
Prefer to edit by hand?
Section titled “Prefer to edit by hand?”You can skip the wizard and copy/edit the template directly:
cd C:\Admin\Configure-WindowsIdentityServicesCopy-Item .\config.example.yaml .\config.yamlnotepad .\config.yamlGo through every value. At minimum set:
- Network/identity:
network.computerName,network.interfaceAlias,network.timeZone,network.ipv4.address,prefixLength,defaultGateway, and the before/after-promotion DNS client servers. - Active Directory:
activeDirectory.domainName,netbiosName,forestMode,domainMode, and the NTDS/SYSVOL paths. - DNS:
dns.forwarders,dns.reverseLookupZones,dns.requiredRecordsToValidate. - DHCP:
dhcp.serverDnsName,serverIpAddress, and each scope’sscopeId/startRange/endRange/subnetMaskplus router/DNS/NTP options. - Time:
time.externalNtpServers,time.ntpFlags,time.behaviorIfNotPdc. - Structure:
organizationalUnits.structure[],groups.items[],serviceAccounts.accounts[]. - Policy:
gpo.gpos[](each needs alinkOrder), plushardening.*,firewall.*,defender.*. - Optional features:
eventForwarding,wazuh,pki,wsus,identityIntegrations— each must be explicitlyenabled: trueorenabled: false. If you enable one, fill in all of its required child values.
Notes:
config.yamlis git-ignored on purpose — it holds your real hostnames/IPs/domain. Only the genericconfig.example.yamlis shipped.- The script does not read the YAML comments; they are just guidance. Real values must be set as real keys.
Step 5 — Preview without changing anything (PlanOnly)
Section titled “Step 5 — Preview without changing anything (PlanOnly)”Always dry-run first. This validates your config and prints every operation it would perform, then exits without changing the system:
.\Configure-WindowsServer.ps1 -ConfigPath .\config.yaml -PlanOnlyIf the config is invalid (missing/empty/contradictory values), it stops here and tells you
exactly what to fix. Fix config.yaml and re-run until the plan is clean.
Step 6 — Run it
Section titled “Step 6 — Run it”The simplest path is to run the whole thing — it walks the phases in order and uses a resume task to continue across the reboot:
.\Configure-WindowsServer.ps1 -ConfigPath .\config.yamlPrefer to go one phase at a time? Run them in this order:
.\Configure-WindowsServer.ps1 -ConfigPath .\config.yaml -Phase Preflight.\Configure-WindowsServer.ps1 -ConfigPath .\config.yaml -Phase PromoteDomainController# --- server reboots here ---.\Configure-WindowsServer.ps1 -ConfigPath .\config.yaml -Phase PostPromotion.\Configure-WindowsServer.ps1 -ConfigPath .\config.yaml -Phase Validate- Preflight — checks elevation/OS/pending-reboot, installs the AD/DNS/DHCP roles.
- PromoteDomainController — promotes to a new forest. Prompts for the DSRM (Directory Services Restore Mode) password, then reboots.
- PostPromotion — configures DNS/DHCP/time/OUs/groups/service accounts/GPOs/hardening and any optional features you enabled.
- Validate — runs
dcdiag, replication, DNS/DHCP/time checks, and writes the report.
Step 7 — The reboot (this is normal)
Section titled “Step 7 — The reboot (this is normal)”AD promotion reboots the server. Don’t panic.
- After it comes back up, sign in as the domain Administrator.
- A scheduled resume task continues the run automatically; resume state lives under
execution.statePath(defaultC:\ProgramData\Configure-WindowsIdentityServices). - If you were running phase-by-phase, run the
PostPromotioncommand now.
Re-running is safe: the script is idempotent where it can be and picks up from the right phase instead of starting over.
Step 8 — Validate
Section titled “Step 8 — Validate”If you didn’t run the full pipeline, run validation explicitly:
.\Configure-WindowsServer.ps1 -ConfigPath .\config.yaml -ValidateOnlyThis writes evidence files and summary.txt / summary.json / final-report.json.
Did it work? Quick health check
Section titled “Did it work? Quick health check”Run these and compare against your config:
Get-Service ADWS,DNS,DHCPServer,CertSvc,w32time | Select-Object Name,Status,StartTypeGet-ADDomain | Select-Object DNSRoot,NetBIOSName,DomainMode,PDCEmulatorGet-ADForest | Select-Object ForestMode,RootDomainGet-DnsServerForwarderGet-DhcpServerv4Scopew32tm /query /statusYou want: services Running, domain/forest modes matching your config, your DNS forwarders + reverse zone present, your DHCP scope ranges present, and a healthy NTP source.
Where the logs and evidence live
Section titled “Where the logs and evidence live”All paths come from config.yaml (defaults shown):
- Transcript log:
execution.transcriptPath - Structured operation log (JSON Lines):
execution.jsonLogPath - Evidence + reports:
execution.evidencePath—summary.txt,summary.json,final-report.json,dcdiag.txt,dcdiag-dns.txt,repadmin-replsummary.txt,GPO\*.html. - State/resume markers:
current-phase.json,preflight-complete.json,roles-installed.json,ad-promoted.json,post-promotion-complete.json,validation-complete.json, andfailure.json(only if a run failed).
Secrets are never written to logs.
If something goes wrong (rollback)
Section titled “If something goes wrong (rollback)”Detailed runbooks are in docs/:
docs/operator-runbook.md— the full operator sequence.docs/rollback-runbook.md— how to back out before/after promotion, and when a clean rebuild beats a rollback.docs/ad-backup-restore-runbook.md— system-state backup and authoritative vs non-authoritative restore.docs/security-decisions.md— what hardening was applied, skipped, or not implemented.docs/validation-evidence-template.md— the list of evidence artifacts to expect.
Honest tip: for a lab/greenfield box, restoring a pre-promotion VM snapshot is usually faster and cleaner than unwinding AD. Snapshot the VM before Step 6.
Passwords
Section titled “Passwords”You are never asked to put passwords in YAML. By default the script prompts securely for:
- the DSRM password during forest promotion, and
- each service account password as accounts are created.
For unattended runs you can pre-set them as environment variables (current session only):
$env:CONFIGURE_WIS_DSRM_PASSWORD = 'YourStrongDSRMPassword'$env:CONFIGURE_WIS_SERVICEACCOUNT_PASSWORD = 'YourStrongSvcPassword'The DSRM password is used once and never stored; passwords are never logged.
The golden rule: no defaults in code
Section titled “The golden rule: no defaults in code”There are no hard-coded environment defaults anywhere in the PowerShell. Domain names, IPs, DNS forwarders, NTP servers, OU/group/account names, GPO names, paths — every environment-specific value must come from your YAML. If a required value for an enabled feature is missing, empty, or invalid, the script stops before changing anything and tells you what to fix. It will not silently substitute a guess.
Project layout
Section titled “Project layout”Configure-WindowsIdentityServices/ Configure-WindowsServer.ps1 # entrypoint — runs the build New-WindowsServerConfig.ps1 # interactive wizard/TUI that writes config.yaml config.example.yaml # template — copy to config.yaml (git-ignored) README.md # this guide lib/ # one module per concern (AD, DNS, DHCP, GPO, ...) docs/ # operator + rollback + backup runbooks tests/Pester/ # config-validation tests (run on a dev box) tools/Test-PkiOnHost.ps1 # optional PKI spot-check helper