Knowledgebase

Working with YAML in PowerShell Using powershell-yaml Print

  • Tutorials, Automation, configuration management, DSC, PowerShell, yaml
  • 0

This guide was originally published on the SSE blog.

Working with YAML in PowerShell Using powershell-yaml

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 CurrentUser
Get-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: Value
AnotherProperty: 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: Value
AnotherProperty: 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.


Was this answer helpful?
Back