<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Validation on The Nested Lab</title>
    <link>https://thenestedlab.com/tags/validation/</link>
    <description>Recent content in Validation on The Nested Lab</description>
    <generator>Hugo</generator>
    <language>en-gb</language>
    <lastBuildDate>Thu, 17 Sep 2026 06:30:00 +0100</lastBuildDate>
    <atom:link href="https://thenestedlab.com/tags/validation/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Windows Server 2025 via Aria Automation, part 3: validation as a product, and the details that hurt</title>
      <link>https://thenestedlab.com/posts/windows-2025-aria-part-3/</link>
      <pubDate>Thu, 17 Sep 2026 06:30:00 +0100</pubDate>
      <guid>https://thenestedlab.com/posts/windows-2025-aria-part-3/</guid>
      <description>A self-contained HTML build report — scorecard, per-agent checks, network and storage audits, vCenter placement, an SVG Gantt with reboots detected. Plus the Session-0 traps — vmtoolsd argument mangling, ISO ejection with no Explorer — that cost real hours.</description>
      <content:encoded><![CDATA[<p>Most provisioning pipelines end with &ldquo;Deployment completed&rdquo;. This one ends
with a document a human can read, and it changed how the platform team and
the server team talk to each other: instead of &ldquo;it&rsquo;s built, go check it&rdquo;,
the requester gets a page that says <em>what</em> was built, <em>where</em> it landed,
<em>what passed</em>, and <em>how long each step took</em>.</p>
<p><a href="/posts/windows-2025-aria-part-1/">Part 1</a> was the architecture; <a href="/posts/windows-2025-aria-part-2/">part
2</a> the state machine. This is the payoff
— and then the details that were nowhere in any documentation.</p>
<h2 id="the-report">The report</h2>
<p><code>06-validate-build.ps1</code> is a pure transformation: <code>data-validation.json</code>
in, one self-contained HTML file out. No server, no external assets — CSS,
SVG icons and the timeline chart are all inline — so it opens from the file
share or as an email attachment as-is.</p>
<p><img alt="Server Build Validation Report: header, BUILD SUCCESSFUL banner, scorecard (Software 5/5, Build Time 41m 12s, Domain Trust OK, Pending Reboot NO, Disks OK), system information and security cards" loading="lazy" src="/images/ui/a1-aria-report-top.jpg">
<em>The top of the report. Rendered by the real renderer from an anonymised payload: the hostnames, domain and vendor names are fictional, the code that drew it is not. <a href="/files/ACME-LDN-APP-006_Validation_Report.html">Open the full report</a> — it&rsquo;s one self-contained HTML file — or the <a href="/files/ACME-LDN-APP-006_data-validation.json">JSON it was built from</a>.</em></p>
<p>Header: VM, execution time, project, deployment, requester. Banner:
<strong>BUILD SUCCESSFUL / BUILD FAILED</strong> per the strict rule from part 2. A
sticky status bar keeps hostname and verdict visible while scrolling. Then:</p>
<table>
	<thead>
			<tr>
					<th>Section</th>
					<th>Contents</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>System</td>
					<td>hostname, OS, latest patch, CPU, RAM, uptime, time zone, pending reboot</td>
			</tr>
			<tr>
					<td>Security &amp; AD</td>
					<td>domain, trust status, OU, NTP source; WinRM/RDP/UAC status cards; local admins</td>
			</tr>
			<tr>
					<td>Software</td>
					<td>one card per agent: pass/fail badge + the individual checks (flag, service, path)</td>
			</tr>
			<tr>
					<td>Network</td>
					<td>per adapter: alias, MAC, IP, mask (converted from prefix), gateway — <strong>joined to the vSphere portgroup</strong> via the guestinfo NIC/MAC map</td>
			</tr>
			<tr>
					<td>Storage</td>
					<td>per volume: capacity bars (warn &lt; 20 % free, critical &lt; 10 %) + backing datastore chips</td>
			</tr>
			<tr>
					<td>Tags</td>
					<td>every deployment tag as a colour chip (palette chosen deterministically by key hash, so the same tag is always the same colour)</td>
			</tr>
			<tr>
					<td>Placement</td>
					<td>vCenter → datacenter → cluster → host chain, VM folder; &ldquo;metadata pending&rdquo; if guestinfo was never readable</td>
			</tr>
			<tr>
					<td><strong>Timeline</strong></td>
					<td>SVG Gantt of every step at true wall-clock position; phase colours; <strong>automatic REBOOT detection</strong> (gaps &gt; 30 s shaded and labelled)</td>
			</tr>
			<tr>
					<td>Installed apps</td>
					<td>collapsible full inventory</td>
			</tr>
	</tbody>
</table>
<p><img alt="Software validation cards (flag, service, path per agent), the network table joined to vSphere portgroups, and storage volumes with capacity bars and datastore chips" loading="lazy" src="/images/ui/a2-aria-report-software-network.jpg">
<em>Sections 3 to 5: one card per agent with its three checks; adapters joined to their portgroups; volumes with their backing datastore.</em></p>
<p>The network table is the one that gets the &ldquo;oh&rdquo; reaction: the guest knows
its adapters, vCenter knows the portgroups, and the guestinfo bridge from
part 1 lets one table show both, joined on MAC, with no credentials
crossing the boundary.</p>
<p><img alt="Deployment tags as colour chips and the vCenter to datacenter to cluster to host placement chain with the VM folder" loading="lazy" src="/images/ui/a3-aria-report-tags-placement.jpg">
<em>Tags and placement — all of it from the request and from <code>guestinfo</code>, none of it from a credential in the guest.</em></p>
<p>The Gantt is the one operations teams use. When a build takes 90 minutes
instead of 40, the chart shows whether it was the updates step, a slow
installer, or a 20-minute gap where the machine sat at a boot prompt.</p>
<p><img alt="Provisioning timeline: SVG Gantt with phase colours and the REBOOT gap shaded amber" loading="lazy" src="/images/ui/a4-aria-report-timeline.jpg">
<em>Section 8. Phase colours, true wall-clock positions, and the reboot detected from a gap over 30 seconds — nothing logged &ldquo;rebooting now&rdquo;.</em></p>
<h2 id="the-details-that-hurt">The details that hurt</h2>
<p>Every one of these cost hours and none of them is in a manual.</p>
<h3 id="vmtoolsd-mangles-arguments-under-system-in-session-0">vmtoolsd mangles arguments under SYSTEM in Session 0</h3>
<p>Reading <code>guestinfo.vra.infrastructure</code> via
<code>&amp; vmtoolsd.exe --cmd &quot;info-get guestinfo.vra.infrastructure&quot;</code> works
interactively and <strong>fails silently as SYSTEM in a startup task</strong>: the
argument quoting gets mangled on the way through. Fix: build the process
explicitly with <code>System.Diagnostics.Process</code>, set <code>Arguments</code> as one
string, redirect stdout, and read it yourself. Also: retry — 15 × 10 s —
because the vRO subscription that writes the value can land <em>after</em> the
guest starts looking.</p>
<h3 id="ejecting-an-iso-with-no-explorer">Ejecting an ISO with no Explorer</h3>
<p>Session 0 has no shell, so <code>Shell.Application</code> ejection does nothing. A
P/Invoke to <code>winmm.dll</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="p">[</span><span class="no">mciSend</span><span class="p">]::</span><span class="n">mciSendString</span><span class="p">(</span><span class="s2">&#34;set cdaudio door open&#34;</span><span class="p">,</span> <span class="vm">$null</span><span class="p">,</span> <span class="mf">0</span><span class="p">,</span> <span class="p">[</span><span class="no">IntPtr</span><span class="p">]::</span><span class="n">Zero</span><span class="p">)</span>
</span></span></code></pre></div><p>opens the tray. It feels like 1998. It works.</p>
<h3 id="exit-1003-is-the-only-reboot-you-should-ever-request-from-cloudbase-init">Exit 1003 is the only reboot you should ever request from cloudbase-init</h3>
<p>Call <code>Restart-Computer</code> from a cloudbase-init script and you race the
plugin&rsquo;s own state tracking. Exit <strong>1003</strong> instead: cloudbase-init reboots,
re-runs the part on next boot, and your flag file short-circuits it.
Installers&rsquo; <strong>3010</strong> is a different animal — that&rsquo;s &ldquo;success, reboot
wanted&rdquo; and the build master decides when.</p>
<h3 id="cloudbase-init-has-to-remove-itself">cloudbase-init has to remove itself</h3>
<p>Leaving cloudbase-init installed on a delivered server is an unattended
execution surface: anyone who can present a config drive owns the box.
The cleanup phase stops the service, kills the processes, runs the
uninstaller silently and deletes the directory — and does it <em>before</em>
validation, so the report can confirm it&rsquo;s gone.</p>
<h3 id="uac-was-off-turn-it-back-on">UAC was off. Turn it back on.</h3>
<p>The base image relaxes <code>EnableLUA</code> and <code>FilterAdministratorToken</code> so the
build runs without prompts. If the cleanup forgets to restore them you
ship a server with UAC disabled and a report that says SUCCESS. The
&ldquo;UAC enabled&rdquo; card in the security section exists so this can never be
silent.</p>
<h3 id="secrets-encrypt-to-the-machine-then-scrub">Secrets: encrypt to the machine, then scrub</h3>
<p>The two share passwords are the only secrets that ever touch guest disk.
They&rsquo;re AES-encrypted with a key derived from the BIOS UUID
(<code>Win32_ComputerSystemProduct.UUID</code>) — useless off-box — decrypted only in
memory, and overwritten with <code>*** SCRUBBED ***</code> before the final reboot.
That&rsquo;s containment appropriate to a <em>transient</em> build secret; it&rsquo;s not a
vault, and shouldn&rsquo;t be described as one.</p>
<h3 id="the-requester-is-told-too-early">The requester is told too early</h3>
<p>The &ldquo;your deployment completed&rdquo; email fires at Compute Post Provision —
when vCenter has finished, not when the guest has. Users open a server
that&rsquo;s mid-Windows-Updates. Move it to a deployment-completion topic, or at
minimum include the report&rsquo;s future share path.</p>
<h3 id="hostname-allocation-has-a-race">Hostname allocation has a race</h3>
<p>Read-then-allocate against AD is unsynchronised: two concurrent
deployments can observe the same highest suffix and pick the same name.
Within one multi-machine deployment the count-based batch is safe; across
deployments, wrap the search-and-generate in a vRO <code>LockingSystem</code> lock.</p>
<h2 id="what-id-carry-to-any-build-pipeline">What I&rsquo;d carry to any build pipeline</h2>
<p>Strip the Windows specifics and five ideas survive:</p>
<ol>
<li><strong>The report is the deliverable.</strong> Build it from a single JSON document
so it&rsquo;s testable without a build.</li>
<li><strong>Join guest facts to platform facts</strong> via a one-way metadata channel.</li>
<li><strong>Detect reboots from timing gaps</strong>, not from logging &ldquo;rebooting now&rdquo;.</li>
<li><strong>Health is three checks</strong>, never the installer&rsquo;s exit code.</li>
<li><strong>Clean up before you validate</strong>, so &ldquo;clean&rdquo; is a checkable claim.</li>
</ol>
<h2 id="why-this-matters-outside-the-lab">Why this matters outside the lab</h2>
<p>The report is the part customers remember. It replaces &ldquo;your server is
ready&rdquo; with evidence: what was installed and whether it&rsquo;s healthy, where
the server landed in vCenter, how the network was configured, how long each
step took and where the reboots were. Service desks use it to close the
request, security teams use it to confirm the controls, and platform teams
use the timeline to spot regressions. The same idea — validate, then
publish proof — transfers to any provisioning pipeline, Windows or not.</p>
<h2 id="rules-learned">Rules learned</h2>
<ul>
<li>Ship a <strong>report</strong>, not a status. Self-contained HTML, one JSON source.</li>
<li>Under SYSTEM in Session 0: build processes explicitly, eject media via
<code>mciSendString</code>, expect no shell.</li>
<li><strong>Exit 1003</strong> for cloudbase-init reboots; <strong>3010</strong> is the installer&rsquo;s
word for &ldquo;later&rdquo;.</li>
<li>Remove cloudbase-init and restore UAC — and make both <em>checks</em> in the
report.</li>
<li>Machine-keyed encryption + scrub is right for transient secrets. Say
what it is.</li>
<li>Fix the two timing bugs: the early requester email, and the hostname
race under parallel deployments.</li>
</ul>
<p><em>Previously: <a href="/posts/windows-2025-aria-part-2/">the state machine</a>. This VM
is also where the <a href="/posts/telegraf-windows-2025/">Telegraf</a> and
<a href="/posts/fluent-bit-two-ways/">fluent-bit</a> Windows agents land.</em></p>
<hr>
<p><em>Lab write-up of a production pattern; customer specifics removed. Opinions
my own.</em></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
