Streamlining PowerCLI Scripts: Using YAML for Variable Management
The true power of automation lies in simplicity—storing all your home lab variables in a single YAML file transforms complexity into clarity, making every deployment seamless and every configuration effortless.
Why am I using a YAML file:
Many automation tools and platforms rely on YAML files for configuration due to their readability and flexibility. In VMware VCF Automation—something I frequently work with—YAML is used extensively for Design Templates. To maintain consistency across my automation scripts, I centralized all my Home Lab variables in a single YAML file.
To work with YAML in PowerShell, I’m using the “PowerShell-YAML” module, which provides a simple and efficient way to read and write YAML data. In this blog post, I’ll share several sample scripts demonstrating how easy it is to integrate YAML into PowerShell-based automation.
All required variables for the PowerShell scripts are centralized in a single YAML configuration file for streamlined management.
For 2025, I’ve been invited to be a VMware Community {code} Coach! This year, my blogs will feature even more code examples to help others get started with automation, VMware solutions, and much more…
Code Examples:
The sample YAML file contains all the variables for my home lab:
You can see that I have defined variables for several products within my home lab
The YAML file is organized into major categories, such as NH, vCenter, Host101, and OPS, each representing a different area of configuration for my Lab
Within each top-level section, specific settings are defined using key-value pairs. For example, in NH, values like VIServer, VIUsername, and VIPassword store configuration details for a nested host setup
Some sections, like NH → Hosts, contain lists (denoted by -), where multiple items (e.g., ESXi hosts) are grouped together with attributes such as Hostname, IP, and Create
Separate Functional Areas
NH configures the nested ESXi environment
vCenter stores vCenter connection details
Host101 provides credentials for a single ESXi host
# NH is Nested HostNH:VIServer:192.168.6.100VIUsername:administrator@vcrocs.localVIPassword:VMware1!NestedESXiApplianceOVA:/Users/dalehassinger/Downloads/Nested_ESXi8.0u3c_Appliance_Template_v1.ovaverboseLogFile:/Users/dalehassinger/Documents/GitHub/PS-TAM-Lab/VCF-Nested-Step-1-Host-Only-YAML.logNestedESXiMGMTvCPU:12NestedESXiMGMTvMEM:88NestedESXiMGMTCachingvDisk:4NestedESXiMGMTCapacityvDisk:500NestedESXiMGMTBootDisk:32VMDatastore:ESX-04-2TB-02VMCluster:CLUSTER-03-VCFVMNetwork:VMsVMNetmask:255.255.252.0VMGateway:192.168.4.1VMDNS:192.168.6.1VMNTP:time.google.comVMPassword:VMware1!VMDomain:vcrocs.localVMSyslog:192.168.6.94Hosts:- Hostname:VCF-DDC-ESX178IP:192.168.4.178Create:True- Hostname:VCF-DDC-ESX179IP:192.168.4.179Create:False- Hostname:VCF-DDC-ESX180IP:192.168.4.180Create:True# vCenter ConnectionvCenter:server:vcsa8x.vcrocs.localusername:administrator@vcrocs.localpassword:VMware1!ignoreCertErrors:true# A Single ESXi Host ConnectionHost101:server:192.168.6.101username:rootpassword:VMware1!# VCF OperationsOPS:opsURL:https://vao.vcrocs.localopsUsername:adminopsPassword:VMware1!authSource:local
Sample Script to create Nested ESXi VMs:
The initial lines of code ensure that the necessary PowerShell modules are installed before executing the script
The PowerShell code then reads the Home-Lab-Config.yaml file, loads its content as a raw string, and converts it from YAML format into a structured PowerShell object stored in $cfg
The PowerShell code accesses the properties within the NH section of the $cfg object, which was previously loaded from a YAML configuration file
The code references the NH section of the YAML configuration, accessing its VIServer value, where NH represents a structured key within the YAML file, organizing configuration settings into hierarchical sections.
# Ensure required modules are installed before proceeding$requiredModules=@("powershell-yaml","VMware.PowerCLI")foreach($modulein$requiredModules){if(-not(Get-Module-ListAvailable-Name$module)){Write-Host"$module is not installed. Install it using 'Install-Module $module'."-ForegroundColorRedexit}}# Read YAML configuration$cfgFile="/Users/dalehassinger/Documents/GitHub/PS-TAM-Lab/Home-Lab-Config.yaml"$cfg=Get-Content-Path$cfgFile-Raw|ConvertFrom-Yaml# Function to display configuration detailsFunctionShow-ConfigValue{param([string]$label,[string]$value,[switch]$newLine)Write-Host-NoNewline-ForegroundColorGreen"${label}: "Write-Host-ForegroundColorWhite$valueif($newLine){Write-Host"`n"}}# Display Nested ESXi Build DetailsWrite-Host-ForegroundColorMagenta"`nNested ESXi Build Details:`n"Write-Host-ForegroundColorYellow"---- vCenter Server used to Build Nested ESXi Host(s) ----"Show-ConfigValue"vCenter Server Address"$cfg.NH.VIServer-newLineShow-ConfigValue"VM Network"$cfg.NH.VMNetworkShow-ConfigValue"VM Storage"$cfg.NH.VMDatastoreShow-ConfigValue"VM Cluster"$cfg.NH.VMCluster-newLineWrite-Host-ForegroundColorYellow"---- ESXi Configuration for VCF Management Domain ----"$hostnames=($cfg.NH.Hosts|Where-Object{$_.Create}|ForEach-Object{$_.Hostname})-join", "Show-ConfigValue"ESXi VM Name(s)"$hostnames$hostips=($cfg.NH.Hosts|Where-Object{$_.Create}|ForEach-Object{$_.ip})-join", "Show-ConfigValue"IP Address(s)"$hostipsShow-ConfigValue"vCPU"$cfg.NH.NestedESXiMGMTvCPUShow-ConfigValue"vMEM""$($cfg.NH.NestedESXiMGMTvMEM) GB"Show-ConfigValue"Caching VMDK""$($cfg.NH.NestedESXiMGMTCachingvDisk) GB"Show-ConfigValue"Capacity VMDK""$($cfg.NH.NestedESXiMGMTCapacityvDisk) GB"-newLineShow-ConfigValue"Netmask"$cfg.NH.VMNetmaskShow-ConfigValue"Gateway"$cfg.NH.VMGatewayShow-ConfigValue"DNS"$cfg.NH.VMDNSShow-ConfigValue"NTP"$cfg.NH.VMNTPShow-ConfigValue"Syslog"$cfg.NH.VMSyslog-newLine# Build Start Time$StartTime=Get-Date# Function to log eventsFunctionNew-LogEvent{param([string]$message)$timeStamp=Get-Date-Format"MM-dd-yyyy HH:mm:ss"Write-Host"[$timeStamp] $message"-ForegroundColorGreen"[$timeStamp] $message"|Out-File-Append-LiteralPath$cfg.NH.verboseLogFile}# Connect to vCenterNew-LogEvent"Connecting to vCenter: $($cfg.NH.VIServer)..."$viConnection=Connect-VIServer$cfg.NH.VIServer-User$cfg.NH.VIUsername-Password$cfg.NH.VIPassword-WarningActionSilentlyContinue-Protocolhttps-Force# Get datastore, cluster, and random ESXi host$datastore=Get-Datastore-Server$viConnection-Name$cfg.NH.VMDatastore|Select-Object-First1$cluster=Get-Cluster-Server$viConnection-Name$cfg.NH.VMCluster$vmhost=$cluster|Get-VMHost|Get-Random-Count1# Iterate over each Nested ESXi Host in YAMLforeach($nestedhostin$cfg.NH.Hosts){if(-not$nestedhost.Create){New-LogEvent"Skipping host: $($nestedhost.Hostname) as per configuration."continue}# Check if the VM already existsif(Get-VM-Name$nestedhost.Hostname-ErrorActionSilentlyContinue){New-LogEvent"Nested ESXi Host: $($nestedhost.Hostname) already exists. Skipping deployment."continue}# Configure VM settings$VMHostName="$($nestedhost.Hostname).$($cfg.NH.VMDomain)"$ovfconfig=Get-OvfConfiguration$cfg.NH.NestedESXiApplianceOVA$networkMapLabel=($ovfconfig.ToHashTable().keys|Where {$_-Match"NetworkMapping"}).replace("NetworkMapping.","").replace("-","_").replace(" ","_")$ovfconfig.NetworkMapping.$networkMapLabel.value=$cfg.NH.VMNetwork$ovfconfig.common.guestinfo.hostname.value=$VMHostName$ovfconfig.common.guestinfo.ipaddress.value=$nestedhost.IP$ovfconfig.common.guestinfo.netmask.value=$cfg.NH.VMNetmask$ovfconfig.common.guestinfo.gateway.value=$cfg.NH.VMGateway$ovfconfig.common.guestinfo.dns.value=$cfg.NH.VMDNS$ovfconfig.common.guestinfo.domain.value=$cfg.NH.VMDomain$ovfconfig.common.guestinfo.ntp.value=$cfg.NH.VMNTP$ovfconfig.common.guestinfo.syslog.value=$cfg.NH.VMSyslog$ovfconfig.common.guestinfo.password.value=$cfg.NH.VMPassword$ovfconfig.common.guestinfo.ssh.value=$trueNew-LogEvent"Deploying Nested ESXi VM: $($nestedhost.Hostname) with IP $($nestedhost.IP) ..."$vm=Import-VApp-Source$cfg.NH.NestedESXiApplianceOVA-OvfConfiguration$ovfconfig-Name$nestedhost.Hostname-Location$cfg.NH.VMCluster-VMHost$vmhost-Datastore$datastore-DiskStorageFormatthin-ForceNew-LogEvent"Updating vCPU: $($cfg.NH.NestedESXiMGMTvCPU) & vMEM: $($cfg.NH.NestedESXiMGMTvMEM) GB ..."Set-VM-VM$vm-NumCpu$cfg.NH.NestedESXiMGMTvCPU-CoresPerSocket$cfg.NH.NestedESXiMGMTvCPU-MemoryGB$cfg.NH.NestedESXiMGMTvMEM-Confirm:$false|Out-File-Append-LiteralPath$cfg.NH.verboseLogFileNew-LogEvent"Updating vSAN Disks (Boot: $($cfg.NH.NestedESXiMGMTBootDisk) GB, Cache: $($cfg.NH.NestedESXiMGMTCachingvDisk) GB, Capacity: $($cfg.NH.NestedESXiMGMTCapacityvDisk) GB)..."Get-HardDisk-VM$vm-Name"Hard disk 1"|Set-HardDisk-CapacityGB$cfg.NH.NestedESXiMGMTBootDisk-Confirm:$false|Out-File-Append-LiteralPath$cfg.NH.verboseLogFileGet-HardDisk-VM$vm-Name"Hard disk 2"|Set-HardDisk-CapacityGB$cfg.NH.NestedESXiMGMTCachingvDisk-Confirm:$false|Out-File-Append-LiteralPath$cfg.NH.verboseLogFileGet-HardDisk-VM$vm-Name"Hard disk 3"|Set-HardDisk-CapacityGB$cfg.NH.NestedESXiMGMTCapacityvDisk-Confirm:$false|Out-File-Append-LiteralPath$cfg.NH.verboseLogFileNew-LogEvent"Powering On $($nestedhost.Hostname) ..."$vm|Start-Vm-RunAsync|Out-Null}# Disconnect from vCenterNew-LogEvent"Disconnecting from vCenter..."Disconnect-VIServer-Server*-Confirm:$false$EndTime=Get-Date$duration=[math]::Round((New-TimeSpan-Start$StartTime-End$EndTime).TotalMinutes,2)New-LogEvent"VCF Lab Nested ESXi Hosts Build Complete!"New-LogEvent"StartTime: $StartTime"New-LogEvent"EndTime: $EndTime"New-LogEvent"Duration: $duration minutes to deploy Nested ESXi Hosts"
Click to see Larger Image of Screen Shot
Sample Script to connect to vCenter using variables from a YAML file:
# Ensure required modules are installed before proceeding$requiredModules=@("powershell-yaml","VMware.PowerCLI")foreach($modulein$requiredModules){if(-not(Get-Module-ListAvailable-Name$module)){Write-Host"$module is not installed. Install it using 'Install-Module $module'."-ForegroundColorRedexit}}# Read YAML configuration$cfgFile="/Users/dalehassinger/Documents/GitHub/PS-TAM-Lab/Home-Lab-Config.yaml"$cfg=Get-Content-Path$cfgFile-Raw|ConvertFrom-Yaml# Connect to vCenter$credential=New-ObjectPSCredential($cfg.vCenter.username,(ConvertTo-SecureString$cfg.vCenter.password-AsPlainText-Force))$vCenter=Connect-VIServer-Server$cfg.vCenter.server-Credential$credential-ForceGet-VM|Select-ObjectName|Sort-ObjectNameDisconnect-VIServer-Server*-Confirm:$false
Sample Script to Connect to an ESXi Host Using Variables from a YAML File:
# Ensure required modules are installed before proceeding$requiredModules=@("powershell-yaml","VMware.PowerCLI")foreach($modulein$requiredModules){if(-not(Get-Module-ListAvailable-Name$module)){Write-Host"$module is not installed. Install it using 'Install-Module $module'."-ForegroundColorRedexit}}# Read YAML configuration$cfgFile="/Users/dalehassinger/Documents/GitHub/PS-TAM-Lab/Home-Lab-Config.yaml"$cfg=Get-Content-Path$cfgFile-Raw|ConvertFrom-Yaml# Connect to a ESXi Host$credential=New-ObjectPSCredential($cfg.Host101.username,(ConvertTo-SecureString$cfg.Host101.password-AsPlainText-Force))$hostConnection=Connect-VIServer-Server$cfg.Host101.server-Credential$credential-ForceGet-VM|Select-ObjectName|Sort-ObjectNameDisconnect-VIServer-Server*-Confirm:$false
Sample Script to Connect to a VCF Operations Appliance Using Variables from a YAML File:
# Ensure required modules are installed before proceeding$requiredModules=@("powershell-yaml","VMware.PowerCLI")foreach($modulein$requiredModules){if(-not(Get-Module-ListAvailable-Name$module)){Write-Host"$module is not installed. Install it using 'Install-Module $module'."-ForegroundColorRedexit}}# Define YAML configuration file path$cfgFile="/Users/dalehassinger/Documents/GitHub/PS-TAM-Lab/Home-Lab-Config.yaml"# Read YAML configuration into PowerShell object$cfg=Get-Content-Path$cfgFile-Raw|ConvertFrom-Yaml# Extract Aria Operations (OPS) credentials from the YAML file$opsURL=$cfg.OPS.opsURL$opsUsername=$cfg.OPS.opsUsername$authSource=$cfg.OPS.authSource$opsPassword=$cfg.OPS.opsPassword# ----- Obtain Aria Operations authentication token -----$authUri="$opsURL/suite-api/api/auth/token/acquire?_no_links=true"# Construct the request body as a hashtable$bodyHashtable=@{username=$opsUsernameauthSource=$authSourcepassword=$opsPassword}# Convert the hashtable to JSON format$body=$bodyHashtable|ConvertTo-Json# Execute REST API call to retrieve the authentication token$tokenResponse=Invoke-RestMethod-Uri$authUri-MethodPost-Headers@{"accept"="application/json""Content-Type"="application/json"}-Body$body-SkipCertificateCheck# Extract the token from the API response$authorization="OpsToken "+$tokenResponse.token# ----- Retrieve the VM Operations identifier -----$resourceUri="$opsURL/suite-api/api/resources?resourceKind=HostSystem&page=0&pageSize=1000&_no_links=true"# Execute REST API call to fetch ESXi host system resources$resourceResponse=Invoke-RestMethod-Uri$resourceUri-MethodGet-Headers@{"accept"="application/json""Authorization"=$authorization}-SkipCertificateCheck# Extract resource list and convert to JSON for better readability$identifierList=$resourceResponse.resourceList|ConvertTo-Json-Depth10# Convert the JSON string back to a PowerShell object$data=$identifierList|ConvertFrom-Json# Display the names of all retrieved ESXi hosts$data.resourceKey.name
Lessons Learned:
I enjoy bringing consistency to my scripts by leveraging a YAML file, similar to the approach used in automation platforms I work with.
All the scripts in this blog were created and tested on a Mac using PowerShell.
Don’t dismiss PowerShell just because it was created by Microsoft. Embrace it to enhance your automation journey!
Integrating PowerShell with YAML has been something I’ve wanted to explore for a while. I’m glad I took the time to develop the necessary code and share it with the vCommunity.
vCROCS Deep Dive Podcast
I created a Google NotebookLM Podcast based on the content of this blog. While it may not be entirely accurate, is any podcast ever 100% perfect, even when real people are speaking? Take a moment to listen and share your thoughts with me!
In my blogs, I often emphasize that there are multiple methods to achieve the same objective. This article presents just one of the many ways you can tackle this task. I’ve shared what I believe to be an effective approach for this particular use case, but keep in mind that every organization and environment varies. There’s no definitive right or wrong way to accomplish the tasks discussed in this article.
Lab
Always test new setups and processes, like those discussed in this blog, in a lab environment before implementing them in a production environment.
Tips and Tricks
If you found this blog helpful, consider buying me a coffee to kickstart my day.