Writing
From Autounattend.xml to a Security-Conscious Windows Provisioning Pipeline
How I separated public source, sensitive deployment media, delegated domain join, Hybrid Microsoft Entra join, cleanup, and validation
Ready to read this content aloud.
Automating Windows installation is easy to describe and difficult to operate safely.
A functional Autounattend.xml can select an edition, create accounts, apply settings, run scripts, and complete the operating-system setup with little interaction. But once the file includes Wi-Fi material, administrator passwords, or domain-join credentials, it is no longer just configuration. It becomes sensitive deployment media.
That distinction shaped the design of Windows Unattended Provisioning.
The project began with a working answer file used in a real Windows deployment. The objective was not to publish that operational file. The objective was to transform the validated behavior into a reusable public pipeline while keeping environment-specific secrets outside the repository.
The result is a PowerShell-based build and endpoint workflow that generates a deployment-specific answer file, performs delegated Active Directory domain join, completes Hybrid Microsoft Entra join after restart, records state, removes credential-bearing artifacts, and validates the generated XML with non-production credentials in GitHub Actions.
Unattended installation is not the same as managed provisioning
An answer file can make installation silent, but a managed endpoint requires more than a successful Windows Setup.
For the device to become operational in a hybrid Microsoft environment, several independent transitions must occur:
- Windows Setup must finish.
- The endpoint must obtain network connectivity.
- Active Directory DNS discovery must work.
- The computer account must be created or reused in the correct organizational unit.
- The device must restart as a domain member.
- Hybrid Microsoft Entra join must complete.
- Intune auto-enrollment and policy processing must occur when configured.
- Windows LAPS must rotate the bootstrap local administrator password.
- Setup artifacts and autologon material must be removed.
These transitions do not complete at the same time, and success in one layer does not prove success in the next.
For example, a successful Add-Computer operation proves that the domain join was accepted. It does not prove that AzureAdJoined is already YES, that the device appears in Intune, or that Windows LAPS has rotated the local password.
The provisioning architecture therefore needed explicit phases, retry behavior, state, and acceptance criteria rather than one large “installation succeeded” result.
The first security decision: do not publish the operational answer file
The public repository does not contain a ready-to-deploy Autounattend.xml.
Instead, it contains:
- a sanitized compressed template;
- non-secret example configuration;
- a local build script;
- endpoint scripts for first logon, Hybrid Join, and cleanup;
- structural validation;
- English and Portuguese documentation;
- a security policy and test guidance.
The local build requests the values that cannot safely exist in source control:
- the delegated domain-join username and password;
- the provisioning Wi-Fi password;
- optionally, the temporary bootstrap Administrator password.
When no bootstrap password is supplied, the build generates a new one using RandomNumberGenerator, guarantees characters from multiple classes, shuffles the result, and displays it once for controlled recovery during the pilot.
The resulting output/Autounattend.xml contains real secrets because Windows Setup and the current Add-Computer flow need them. It is excluded from Git, but exclusion alone is not enough. The file and the USB media must be treated as short-lived credentials.
This is an important boundary: the repository is designed to be public; the generated artifact is not.
Treating the build as a compiler
The build script behaves more like a small compiler than a file copy operation.
It performs four classes of work.
Configuration validation
The script requires explicit values for the project version, domain, target OU, Wi-Fi profile, timeout, Hybrid Join behavior, cleanup choices, and selected legacy behaviors.
Passwords do not belong in the PSD1 configuration file. They are collected separately as secure inputs.
Context-aware escaping
Values are inserted into several different syntactic contexts:
- XML text;
- XML embedded inside another XML value;
- PowerShell single-quoted strings;
- embedded CDATA scripts.
A value that is safe in one context may break another. The build therefore applies separate escaping functions and rejects the ]]> sequence inside embedded scripts because it would terminate CDATA unexpectedly.
Placeholder resolution and XML parsing
After replacements, the build rejects unresolved placeholders and parses the final content as XML. An invalid answer file is stopped before it reaches deployment media.
Artifact identity
The generated file receives a SHA-256 hash. The hash does not make the secret-bearing file safe, but it provides a precise identity for the artifact being tested and copied to media.
This build boundary makes the public template reviewable while keeping each deployment artifact explicit and traceable.
Domain join begins with network and DNS validation
The endpoint-side workflow does not immediately call Add-Computer.
At first logon, it:
- starts the Windows Wi-Fi service;
- requests connection to the configured provisioning profile;
- queries
_ldap._tcp.dc._msdcs.<domain>DNS SRV records; - selects a domain-controller candidate;
- verifies LDAP availability on TCP 389;
- retries until the configured deadline.
Only after a reachable domain controller is found does it reconstruct the delegated credential and call Add-Computer with the configured domain, target OU, and selected server.
This does not eliminate all environmental failure modes, but it replaces a blind join attempt with observable prerequisite checks.
The domain-join identity is also constrained by policy rather than code alone. The security model requires a dedicated account delegated only to create or reuse computer objects in the target OU. It must not be a Domain Admin, an interactive account, an RDP account, or a general-purpose service identity.
Failure behavior matters more than the happy path
A provisioning script that works only when everything is available is not operationally complete.
If domain join fails, the workflow:
- records the exception in the domain-join log;
- writes
domainJoin: Failedto the state document; - records that Hybrid Join was not started;
- clears autologon registry values;
- schedules cleanup;
- avoids the automatic restart;
- exits with a failure code.
Stopping the restart is deliberate. A silent reboot after a failed join would make troubleshooting harder and could leave the endpoint in an ambiguous state.
The testing guide also calls for negative tests covering:
- unavailable domain controller;
- invalid delegated credential;
- incorrect Wi-Fi secret;
- an endpoint already joined to a domain;
- disabled Hybrid Join;
- interrupted cleanup.
The repository’s current automated validator is structural, so these environmental cases still require a representative pilot. That limitation is documented rather than hidden.
Hybrid Microsoft Entra join is a deferred phase
Domain join and Hybrid Join are intentionally separated.
After a successful domain join, the first-logon script registers a scheduled task running as SYSTEM. The task can execute at startup and repeatedly after a short delay. Its responsibility is to process policy and invoke the native Automatic-Device-Join workflow until the expected identity state is reached.
This separation solves two timing problems:
- the device needs to restart as a domain member before the hybrid identity path is reliable;
- Group Policy, synchronization, registration, and cloud visibility may not be immediate.
The project records Hybrid Join independently using states such as Pending, WaitingForDomain, Succeeded, or Disabled.
It also keeps Microsoft Intune and Windows LAPS outside the project’s trust boundary. The script can observe and document its own actions, but a successful Hybrid Join does not prove that Intune policy delivery or password rotation completed.
Those controls must be verified in the appropriate Microsoft administration portals.
Cleanup is a provisioning stage, not housekeeping
The most security-sensitive parts of the workflow exist only temporarily:
- answer files copied into Windows Setup locations;
- the provisioning Wi-Fi XML;
- a first-logon script containing the delegated join credential;
- autologon registry values;
- the generated file on removable media.
Cleanup is therefore part of the architecture.
The first-logon script copies the cleanup logic to a controlled location and schedules it after restart so the script currently executing can also be removed. Depending on configuration, cleanup can also remove the provisioning Wi-Fi profile.
This reduces persistence on the endpoint, but it does not retroactively protect copied or lost installation media. If media custody is uncertain, the delegated credential must be rotated.
That operational rule is as important as the cleanup code itself.
Machine-readable state makes the process supportable
The project writes a non-secret JSON state file under:
C:\ProgramData\WindowsDomainProvisioning\state.json
The state includes:
- project version;
- target domain;
- domain-join status;
- Hybrid Join status;
- cleanup status;
- LAPS management expectation;
- update timestamp;
- last error when present.
Logs are separated by phase under the same base directory.
This provides a practical support interface. An administrator can inspect the endpoint without reconstructing the full setup history from Windows Setup logs alone.
It also prevents a common observability mistake: treating the absence of an error dialog as evidence that all downstream enrollment steps succeeded.
CI validates the artifact without using production secrets
The GitHub Actions workflow runs on Windows and creates a local configuration from the public example.
It then supplies dummy credentials to the build script, generates the answer file, runs structural validation, and verifies that neither the generated XML nor the local deployment configuration is tracked by Git.
The validator checks:
- XML parsing;
- unresolved placeholders;
- presence of the required embedded scripts;
- expected domain-join and cleanup markers;
- known warnings inherited from the validated template.
This is not an end-to-end domain or Entra test. It is a deterministic build-integrity test that proves the public source can still generate the expected artifact without relying on production credentials.
What version 1.0.0 deliberately does not solve
The initial release preserves several opinionated behaviors from the validated deployment baseline, including application removal, hardware-requirement bypasses, Remote Desktop enablement, a temporary local standard account, and system-drive ACL changes.
These settings may be inappropriate in another environment. They require review against the organization’s security baseline, Windows version, hardware policy, and software requirements.
The largest remaining architectural issue is the reusable domain-join credential temporarily embedded in the generated answer file.
The current mitigations are:
- least-privilege OU delegation;
- controlled build workstation;
- physically controlled media;
- short deployment batches;
- credential rotation;
- endpoint cleanup;
- Windows LAPS after enrollment.
The preferred future direction is Offline Domain Join, which can reduce dependence on a reusable join credential inside the answer file.
Lessons from turning a setup file into a project
The most useful lesson was that a provisioning project is not defined by how many setup steps it automates.
It is defined by how clearly it handles boundaries:
- public source versus sensitive output;
- build workstation versus endpoint;
- domain join versus cloud registration;
- automation versus external policy systems;
- success versus partial success;
- cleanup versus credential revocation;
- structural validation versus real-environment acceptance.
Once these boundaries were explicit, the original answer file could become a maintainable public project rather than a one-off operational artifact.
Windows Unattended Provisioning v1.0.0 is not a universal zero-touch deployment system. It is a documented, testable, pilot-validated foundation for controlled Windows provisioning—and a clear base for the next security improvement.