Production automation — patterns from real ZEUS scripts
This lesson brings everything together: what the real, production ZEUS probes
look like — Get-LsnForest, Get-LsnInventory, Get-LsnPerf. These are
patterns proven in client environments, where a script must run unattended and
hand a clean result to the backend.
The contract with the backend: -AsJson on stdout
The ZEUS backend runs a probe remotely and reads only stdout as JSON. That's
why diagnostics go to Write-Verbose/Write-Error, and only a single JSON
object reaches stdout.
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Forest,
[switch]$AsJson
)
$ErrorActionPreference = "Stop"
try {
$data = Get-LsnInventoryData -Forest $Forest # collects objects
$out = [pscustomobject]@{ Ok = $true; Forest = $Forest; Items = $data }
}
catch {
$out = [pscustomobject]@{ Ok = $false; Error = $_.Exception.Message }
}
if ($AsJson) {
$out | ConvertTo-Json -Depth 8 -Compress # THE ONLY output on stdout
} else {
$out
}
ProfessNet standard: in
-AsJsonmode exactly one JSON document appears on stdout. NoWrite-Host, banners or debug prints — they'd pollute the stream the backend parses. Diagnostics →Write-Verbose.
Paged LDAP — large forests without a timeout
A large forest inventory returns tens of thousands of objects. We fetch them in pages (paged search) so as not to exceed the LDAP server's limit or memory.
function Invoke-LsnLdapPaged {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$Filter,
[int]$PageSize = 1000
)
$searcher = [adsisearcher]$Filter
$searcher.PageSize = $PageSize # crucial — without it the limit is ~1000
$searcher.PropertiesToLoad.AddRange(@('name', 'dNSHostName', 'operatingSystem'))
foreach ($entry in $searcher.FindAll()) {
[pscustomobject]@{
Name = $entry.Properties['name'][0]
Dns = $entry.Properties['dnshostname'][0]
Os = $entry.Properties['operatingsystem'][0]
}
}
}
ProfessNet standard: LDAP queries always with a
PageSize(e.g. 1000). Without paging the AD server truncates results at the default limit, and the probe returns incomplete data — a silent, dangerous bug.
Remote collection: Invoke-Command over WinRM
We collect performance and local data on the target machines over WinRM. We
close the session in finally and limit concurrency with -ThrottleLimit.
function Get-LsnPerf {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string[]]$ComputerName,
[Parameter(Mandatory)][pscredential]$Credential,
[switch]$AsJson
)
$ErrorActionPreference = "Stop"
$results = Invoke-Command `
-ComputerName $ComputerName `
-Credential $Credential `
-ThrottleLimit 16 `
-ScriptBlock {
[pscustomobject]@{
Host = $env:COMPUTERNAME
Cpu = (Get-CimInstance Win32_Processor).LoadPercentage
MemFreeMB = [math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory / 1KB)
}
}
$out = [pscustomobject]@{ Ok = $true; Count = $results.Count; Hosts = $results }
if ($AsJson) { $out | ConvertTo-Json -Depth 6 -Compress } else { $out }
}
ProfessNet standard:
Invoke-Commandwith a host list runs in parallel with-ThrottleLimit(32 by default; we limit it to ~16 so as not to overload the client's network). Credentials always as a[pscredential]— never in the script.
Combining the patterns
| Pattern | Why |
|---|---|
-AsJson + a single JSON on stdout | a clean contract with the backend |
$ErrorActionPreference = "Stop" + try/catch | error as JSON, not a hang |
Paged LDAP (PageSize) | complete data from large forests |
Invoke-Command + -ThrottleLimit | remote collection, load control |
[pscredential] + finally | security and session cleanup |
Tip: test a probe locally with
-Verbose(you'll see the flow), and in integration with the backend always via-AsJson, and check thatConvertFrom-Jsonsucceeds on the receiving side — that's a real test of the contract.
-AsJson on stdout, paged LDAP and remote Invoke-Command with throttling are
the core of the production ZEUS probes. Put together, they give a script that
runs unattended in the client environment and hands the backend a clean,
complete result.