The Ticket That Started This Walkthrough
The ticket said a scheduled DSC pull job was failing on three of seven nodes. Twenty minutes of log reading later, the pattern was obvious: every failure node deserialized YAML into a Hashtable, and the calling function expected a PSCustomObject. The powershell-yaml module had been working fine for months — nobody on the client’s team realized the round-trip changes the object type.
This walkthrough rebuilds that environment from scratch so you can see exactly where the conversion behavior bites. I’ll cover installation, serializing, deserializing, the type gotcha, and how we standardized usage across the customer’s automation fleet.
Layer 1: Why YAML and Why a Third-Party Module
PowerShell ships native cmdlets for JSON and XML. It does not ship native YAML support. If you want ConvertTo-Yaml and ConvertFrom-Yaml, you install the powershell-yaml module from the PowerShell Gallery. There is no built-in alternative.
YAML earns its place in automation work because it uses Python-style indentation for nesting, encodes scalars natively (strings, integers, lists, hashtables), and is more compact than JSON for human-edited configuration. Ansible and the DSC ecosystem — particularly Datum — lean on YAML for the same reason: humans read it without grimacing. RFC-style strictness it is not, but for configuration data it works.
Installing the Module
From an elevated or user-scoped PowerShell session:
Install-Module PowerShell-yaml -Scope CurrentUserGet-Command -Module powershell-yaml
The Get-Command output enumerates the available cmdlets. You’ll see ConvertTo-Yaml and ConvertFrom-Yaml as the two you actually use day to day. On locked-down customer environments without Gallery access, we mirror the module to an internal PSRepository — same pattern we use for hardening package supply for backup monitoring scripts.
Serializing a PowerShell Object to YAML
Start with a custom object. This is the same shape we use to emit per-host backup manifests on a managed environment:
$Object = [PSCustomObject]@{ Property = 'Value' AnotherProperty = 'AnotherValue'}$Object | ConvertTo-Yaml
The output is exactly what you’d expect:
Property: ValueAnotherProperty: AnotherValue
No document markers, no trailing dots. Clean, indented, paste-ready into any Ansible inventory or Datum tree.
Serializing Richer Types
Mixed scalar and array properties survive the round trip:
$customObj = [PSCustomObject]@{ StringProperty = 'StringValue' IntProperty = 42 ArrayProperty = 1,2,3}$yamlString = $customObj | ConvertTo-Yaml
Strings stay strings, integers stay integers, the array renders as a YAML sequence. Good enough for 95% of configuration work.
Deserializing YAML Back Into PowerShell
Here is where the gotcha lives. Read this carefully.
$Text = @'Property: ValueAnotherProperty: AnotherValue'@$Deserialized = $Text | ConvertFrom-Yaml$Deserialized.GetType() | Select-Object Name, BaseType
Output:
Name BaseType---- --------Hashtable System.Object
ConvertFrom-Yaml returns a Hashtable, not a PSCustomObject. That single line of behavior is what broke the client’s DSC pull job. Functions written against $obj.PropertyName still work because Hashtables support member access in PowerShell, but anything calling .PSObject.Properties or piping into Select-Object -ExpandProperty with strict mode will misbehave.
Opinionated Take: Always Cast on the Boundary
If a function consumes deserialized YAML, cast or convert at the entry point. Do not let the Hashtable type leak through three call layers. The pattern we standardized for the customer:
$config = Get-Content config.yml -Raw | ConvertFrom-Yaml$config = [PSCustomObject]$config
For nested structures, write a recursive converter. It’s twenty lines and saves hours of debugging. JSON has the same problem in reverse — ConvertFrom-Json in older Windows PowerShell builds returned PSCustomObject, while PowerShell 7 added -AsHashtable. Pick a target type and enforce it.
The Limitation Nobody Mentions
Neither YAML nor JSON can fully serialize a .NET object the way Export-Clixml does. Type fidelity, methods, and attached members get stripped. If you need full object reconstruction — for example, exporting a backup job definition with all its scriptblock properties intact — use CliXml, not YAML. We learned that one the hard way when an inherited OneDrive backup automation script tried to round-trip credential objects through YAML and silently dropped the SecureString password field.
Where This Fits in Real Automation
YAML in PowerShell shines for three things in our customer engagements: DSC configuration data via the Datum module, Ansible inventory generation from CMDB exports, and Google Cloud deployment templates (which use YAML where Azure uses JSON). Each use case follows the same serialize-edit-deserialize loop, and each one benefits from the explicit type casting discipline above.
For configuration management at scale, the powershell-yaml module pairs cleanly with the Veeam PowerShell SDK and with Ubuntu hosts running cloud-init manifests. The same YAML file can drive a Windows-side backup definition and a Linux-side validator without translation. If you’re managing infrastructure as code with Terraform alongside PowerShell-driven automation, YAML becomes the shared serialization format that both toolchains read without complaint.
If you’re standing up an automation pipeline and want a second set of eyes on the data flow — particularly the boundary casts and credential handling — reach out and we’ll walk through it. Related reading on adjacent automation tooling: PowerShell Remoting Auth: Kerberos vs NTLM vs CredSSP and Linux Process Management: ps, top, kill, and systemctl Audit.
Practical Takeaway
Install powershell-yaml, use ConvertTo-Yaml freely, but treat ConvertFrom-Yaml output as a Hashtable on day one. Cast to PSCustomObject at the function boundary, never deeper in the call stack. Reserve YAML for human-editable configuration data and CliXml for full-fidelity object persistence. That single discipline prevents the exact failure mode that pulled me into the client’s DSC environment in the first place.


