Get Host Alarms and Error Messages

vCenter alarms are good, but after you finish configuring emails for all the important alarms you want to receive that is when someone creates an email rule to ignore all these alarms. This started me down the path of adding an environment reporting email to let me know if there are any actual issues I need to worry about when I get to work in the morning.

Since the report I have runs at the same time each day I created a variable called “yesterday”. This will be used to find error messages that happened since yesterday’s run.

$yesterday = (Get-Date).AddDays(-1)

Then we need to find host error events. I’m getting every host, then getting all the error events for each host that were generated after $yesterday.

$hostErrors = Get-VMHost | Get-VIEvent -Types Error -Start $yesterday

Previously, I was filtering the start time for a CreatedTime greater than yesterday because I didn’t realize -Start and -Finish were options. I measured the difference and I saved over 2 minutes by using the -Start switch I saved over 150 seconds.

The output events have a lot of info we aren’t interested in. The only thing you need to capture is the Full Formatted Message

We’ll display the output using $hostErrors.FullFormattedMessage

Full script:

$yesterday = (Get-Date).AddDays(-1)
$hostErrors = Get-VMHost | Get-VIEvent -Types Error -Start $yesterday
$hostErrors.FullFormattedMessage

Now that’s great for error messages, but what about alarms?

First we’ll get all the hosts that have an active alarm.

$triggeredhosts = get-vmhost | get-view | where {$_.TriggeredAlarmState.OverallStatus -ne $null}

In my environment you can see that I have 1 host with an alarm, but when we view the TriggeredAlarmState we don’t see what the alarm is for or even the name of the host that has an alarm:

Now we need to do some lookups on this data to give us useful information. Because we can have more than 1 alarm at a time, we need to build a hash array and state populating data.

First we create the hash array:

$alarmReport = @()

Now we create a ForEach loop for every triggered event. We use $triggeredHosts.TriggeredAlarmState instead of just $triggeredHosts because we can have multiple alarms per host and since we’ll resolve the host name on each alarm in this loop this is an easier way to do it.

ForEach ($trigger in $triggeredHosts.TriggeredAlarmState) {

Then we resolve the host system ID to name:

$hostName = Get-VMHost | Where {$_.Id -eq $trigger.Entity}

Now we need to get the type of alarm:

$getAlarm = (Get-View -Id $trigger.Alarm).Info.Name

Then we define the columns for our array. We’re capturing host name, cluster name, the state of the host (connected, maintenance, not responding), and then the name of the alarm:

$row = "" | Select Host, Cluster, State, Alarm

Now we can populate our columns:

$row.Host = $hostName.Name
$row.Cluster = $hostName.Parent
$row.State = $hostName.State
$row.Alarm = $getAlarm

Now we add this row to the hash array and close the ForEach loop:

$alarmReport += $row }

And when we view the output of $alarmReport we see the host name, the cluster, the state, and the current active alarm.

Here’s the whole script:

$triggeredhosts = get-vmhost | get-view | where {$_.TriggeredAlarmState.OverallStatus -ne $null}
$alarmReport = @()
ForEach ($trigger in $triggeredHosts.TriggeredAlarmState) {
$hostName = Get-VMHost | Where {$_.Id -eq $trigger.Entity}
$getAlarm = (Get-View -Id $trigger.Alarm).Info.Name
$row = "" | Select Host, Cluster, State, Alarm
$row.Host = $hostName.Name
$row.Cluster = $hostName.Parent
$row.State = $hostName.State
$row.Alarm = $getAlarm
$alarmReport += $row }

Track Powered Off VMs in vCenter

Tracking powered off VMs is a struggle most people have. Depending on the number of VM Admins there are in your environment, each person can have a different way of tracking. We’ve tried renaming VMs, placing them in a “Powered Off” labeled folder, adding notes to the VM, but the real issue is that if it isn’t automated and you don’t include the date you aren’t ever sure what VMs you can safely delete.

As part of my Daily vCenter health script I now automatically add a date to each powered off VM so I know what VMs I can delete. The only prerequisite is to create a custom attribute called “OffDate” or anything else you want for the VMs.

First thing we do is get the current date in a readable and reportable format. So we use the $date variable and get today’s date then convert it to month-day-year format:

$date = (Get-Date).ToString('MM-dd-yyyy')

After that we get a list of VMs that are currently powered off and that don’t live in a folder that contains the word “template”. In our environment we have a handful of VMs that haven’t been converted to templates, but are used as templates for provisioning. I don’t want to accidentally tag and delete these so they get excluded.

$vmlist = Get-VM | Where {$_.PowerState -eq "PoweredOff" -AND $_.Folder -notlike "*template*"}

Then we create a variable of the custom attribute we’ll be searching for.

$attributeName = "OffDate"

Now we need to gather a list of VMs that are Powered off, but do not currently have their custom attribute populated. We are doing a Get-Annotation for that attribute name we just defined and looking for a value that is empty.

$noDateVM = ForEach ($vm in $vmlist) {$vm | Get-Annotation -CustomAttribute $attributeName | Where {$_.value -eq ""}}

Once we have a list of each of those VMs, we can now tag each of these VMs with the current date. I capture this as a variable so I can add the output to my daily report. I like to know what VMs are getting tagged in case something is added by mistake or an important VM was powered off that shouldn’t have been.

$offDateList = ForEach ($blankVM in $noDateVM) {$blankVM.AnnotatedEntity | Set-Annotation -CustomAttribute $attributename -Value $date}


With the tagging complete, we can now add it to our report. I do HTML formatted reports and I don’t want the section to be in the email if it’s empty. In this portion we check to see if $offDateList is empty and if it is we don’t do anything else. However, if there are VMs, we then create the output with a title and a list the VM along with today’s date.

IF (!$offDateList) {} ELSE {
$outputdateOutput = $offDateList | Select @{N="VM Name";E={$_.AnnotatedEntity}},Value | ConvertTo-Html -PreContent "<h4>New Powered Off VMs</h4>" | Out-String

Here is the complete script:

$date = (Get-Date).ToString('MM-dd-yyyy')
$vmlist = Get-VM | Where {$_.PowerState -eq "PoweredOff" -AND $_.Folder -notlike "*template*"}
$attributeName = "OffDate"
$noDateVM = ForEach ($vm in $vmlist) {$vm | Get-Annotation -CustomAttribute $attributeName | Where {$_.value -eq ""}}
$offDateList = ForEach ($blankVM in $noDateVM) {$blankVM.AnnotatedEntity | Set-Annotation -CustomAttribute $attributename -Value $date}
IF (!$offDateList) {} ELSE {
$outputdateOutput = $offDateList | Select @{N="VM Name";E={$_.AnnotatedEntity}},Value | ConvertTo-Html -PreContent "<h4>New Powered Off VMs</h4>" | Out-String }

But wait, there’s more.

Sure, we can tag VMs, but now we need to report on any VMs that have a Powered Off date, but are currently running.
First thing we do is grab a list of Powered On VMs:

$poweredOnVMs = Get-VM | Where {$_.PowerState -eq "PoweredOn"}

Now for each of these VMs we want to find those that have an OffDate that isn’t empty:

$runningVMs = ForEach ($VM in $poweredOnVMs) {
$vm | Get-Annotation -CustomAttribute $attributeName | Where {$_.value -ne ""}}

Once again, if the list is empty, I don’t want it to show up in my report so if it’s empty we don’t do anything else. If not, we create our output:

IF (!$runningVMs) {} ELSE {
$outputrunningVMReport = $runningVMs | Select @{N="VM name";E={$_.AnnotatedEntity}},Value | ConvertTo-Html -PreContent "<h4>Running VMs with Powered Off Date</h4>" | Out-String }

And here is the full script:

$poweredOnVMs = Get-VM | Where {$_.PowerState -eq "PoweredOn"}
$runningVMs = ForEach ($VM in $poweredOnVMs) {
$vm | Get-Annotation -CustomAttribute $attributeName | Where {$_.value -ne ""}}
IF (!$runningVMs) {} ELSE {
$outputrunningVMReport = $runningVMs | Select @{N="VM name";E={$_.AnnotatedEntity}},Value | ConvertTo-Html -PreContent "<h4>Running VMs with Powered Off Date</h4>" | Out-String }

Update Host DCUI with PowerCLI

With randomly generated host names keeping track of what host you’re working on can get difficult. Recently I was in the process of performing host name changes along with cluster migrations/rebalancing which made it even more difficult to keep track of what I was working.

So, of course, I decided to write a script that will update the DCUI of a host.

Normally this is what we see in the DCUI:

We can see the name, IP address, processors, RAM, version and build. A decent amount of info, but I wanted cluster, powered on VM count, and even the serial number of the server.

Now for the script itself.

First we need a list of all the hosts that are currently available. No need to try to update a host that is not currently responding:

$allHosts = Get-VMHost | Where {$_.ConnectionState -ne "NotResponding"}

Now we open a ForEach loop so these commands can be run against each of these hosts:

ForEach ($vmhost in $allHosts) {

First we’ll get a count of powered on VMs on the host:

$vmCount = ($vmhost | Get-VM | Where {$_.PowerState -eq "PoweredOn"}).Count

Now we’ll get the cluster name:

$cluster = $vmhost.Parent.Name

Then we capture the make and model of the host:

$make = "$($vmhost.Manufacturer) $($vmhost.model)"

Let’s capture the number of CPUs:

$cpuCount = $vmhost.ExtensionData.Summary.Hardware.NumCpuPkgs

Then we’ll get the model of the CPUs:

$cpuModel = $vmhost.ExtensionData.Summary.Hardware.CpuModel

And let’s get the amount of RAM in GB rounded to 2 decimal places:

$memory = [math]::round($vmhost.MemoryTotalGB,2)

This is where create our DCUI text and use the variables we’ve captured above:

$DCUIString = "{color:white}
		{esxproduct} (VMKernel Release {esxversion})
		
		$make
		
		$cpuCount x $cpuModel
		$memory GB Memory
		
		ServiceTag: {servicetag}
		{/color}
		
		{color:yellow}
		Hostname: {hostname}
		IP Address: {ip}
		Cluster: $cluster
		Powered On VMs: $vmCount
		{/color}
		
		
		
		
		<F2> Customize System/View Logs
		<F12> Shut Down/Restart
		"

Then we apply these settings to the host and close the ForEach loop:

$vmhost | Set-VMHostAdvancedConfiguration -Name Annotations.WelcomeMessage -Value $DCUIString }

The script will loop through all your hosts and now you’ll see a DCUI that looks like this:

I have this script scheduled to run daily so it is mostly up to date. In addition, I run another version every 15 minutes. In the event that a host has been placed in maintenance mode we update only that host so we can see if any powered on VMs reside on it.

For that to work, replace the first part of the script searching for all hosts with the part below:

$allHosts = Get-VMhost | where {$_.connectionstate -eq "Maintenance"}

Full script below:

$allHosts = Get-VMhost | where {$_.connectionstate -ne "NotResponding"}
ForEach ($vmhost in $allHosts) {
$vmCount = ($vmhost | Get-VM | where {$_.powerstate -eq "poweredon"}).count
$cluster = $vmhost.parent.name
$make = "$($vmhost.Manufacturer) $($vmhost.model)"
$cpuCount = $vmhost.ExtensionData.Summary.Hardware.NumCpuPkgs
$cpuModel = $vmhost.ExtensionData.Summary.Hardware.CpuModel
$memory = [math]::round($vmhost.MemoryTotalGB,2)
$DCUIString = "{color:white}
		{esxproduct} (VMKernel Release {esxversion})
		
		$make
		
		$cpuCount x $cpuModel
		$memory GB Memory
		
		ServiceTag: {servicetag}
		{/color}
		
		{color:yellow}
		Hostname: {hostname}
		IP Address: {ip}
		Cluster: $cluster
		Powered On VMs: $vmCount
		{/color}
		
		
		
		
		<F2> Customize System/View Logs
		<F12> Shut Down/Restart
		"
$vmhost | Set-VMHostAdvancedConfiguration -Name Annotations.WelcomeMessage -Value $DCUIString}

Removing Values in XML Using PowerShell

We’ve gathered values from our XML file and we’ve managed to add values to an XML file using PowerShell, but what about deleting? What I’m talking about is deleting XML tags and attributes, not deleting the value in an existing tag. Let’s look at our sample file and I’ll explain.

Above is our sample.xml file that was updated in the last post. Inside we have individual portgroups with a name and elements that we were added. In the context of the project I was working on, I needed the ability to delete a portgroup and all of its elements (VLAN, virtual switch, and cluster), but in some situations I needed to just delete the cluster. If a portgroup needed to be removed from one specific cluster, but remaining available to another cluster I couldn’t have the entire portgroup removed as it could affect virtual machines that are still running.

How do we do it? Let’s find out.

Just like before, let’s get the contents of our XML file so we can work with the data.

$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

The two things we need in order to remove a portgroup are the name of the portgroup and the name of the cluster. Based on the sample.xml file, we see that “VM Network” is on two clusters (cluster1 and NestedCluster) so let’s start there. Let’s get some simple variables setup just like we did last time.

$name = Read-Host -Prompt "Enter the name of the Portgroup (ex: Management Network)"
$cluster = Read-Host -Prompt "Enter the name of the cluster for this Portgroup (ex: Cluster1)"

I’ve created a new powershell file called “removePortgroup.ps1” and it has the above variables in there prompting me to enter the name and the cluster of the portgroup to be removed.

Now that we have the XML contents and our variables defined it’s time to see what data is there. If you recall from my earlier post about getting data from XML, we can see the contents of our file by following the hierarchy. With the following command we can see all portgroups that have been defined.

$xmlConfig.config.portgroups.ChildNodes

Now that we know what’s there, we need to filter the results and only display the Web Network. We’ll pass this as a variable so we can use that info later.

$clusters = $xmlConfig.config.portgroups.ChildNodes | ? {$name -eq $_.name}

Using the same command as before but passing it as a variable, we added a parameter to search for the name of our Portgroup (VM Network) using the $name variable we created earlier and we are trying to match it with another portgroup of the same name. When we run the command “$clusters” we see what portgroup came back as a match. We can see the VM Network portgroup that has 2 clusters.

With this portgroup living on two clusters we are only interested in removing it from one which is NestedCluster. We’ll work on the logic for this later one, for now let’s focus on how you remove a single from this portgroup.

The following commands will remove the element that matches our $name and $cluster variables we created earlier.

$text = "#text"
$clusterTag = $xmlConfig.config.portgroups.portgroup | Where {$_.name -eq $name}
$updateTag = $clusterTag.SelectNodes("cluster") | Where {$_.$text -eq $cluster}
$updateTag.ParentNode.RemoveChild($updateTag)

We are looking for the Text inside of the tag. As you can see from the output at the bottom, it has a label (or whatever it is called) of #text. The problem is, when working within PowerShell, if I add a “#” to a line, it will comment out everything after it. We get around that limitation but creating a variable called $text and given it a value of #text. Maybe there is a better way of doing it, but this worked so I went with it.

The next line is looking for a portgroup matching $name. The next line is selecting the tag that matches $cluster. The final line is performing the removal of that tag. This still needs to be saved, so we’re not done yet, but at this point we have removed from . If you run the command below you can see that “NestedCluster” has been removed as a cluster in the “VM Network” portgroup.

$xmlConfig.config.portgroups.ChildNodes

Now what if this portgroup is only on one cluster and we want to delete it and all of its attributes completely? We can do so with the following command.

$xmlConfig.config.portgroups.ChildNodes | ? {$name -eq $_.name} | % {$xmlConfig.config.portgroups.RemoveChild($_)} | Out-Null

In this command, we are getting all the portgroups in the first part, then filtering the results where $name matches the name of an existing portgroup. Then we are removing that entire and all its contents from the XML file. The Out-Null is required to make it work, but I don’t really know why. Once again, this isn’t saved yet, we can do that in the next step.

In order to save we need to run the following command.

$xmlConfig.Save($xmlFile)

Now that this file is saved, we can re-check the XML file using the commands below

$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

If we run the command $xmlConfig.config.portgroups.childnodes we will see that “VM Network” is now gone

We can remove just an attribute or we can remove an entire element. How can we create a script to determine which one to use? If we wrap this all in an IF/ELSE statement we can do it pretty easily.

Using the $clusters variable we created earlier, we can perform a count of how many tags there are. Running the following command will show many clusters a portgroup is provisioned to.

$clusters.cluster.count

In our IF/ELSE statement, we are checking to see if the count is greater than 1 (-gt 1) and then performing the command to remove just the cluster tag. If it’s not greater than 1, we are running the command to remove the entire portgroup and all of its attributes.

$clusters = $xmlConfig.config.portgroups.ChildNodes | ? {$name -eq $_.name}
IF ($clusters.cluster.count -gt 1){
$text = "#text"
$clusterTag = $xmlConfig.config.portgroups.portgroup | Where {$_.name -eq $name}
$updateTag = $clusterTag.SelectNodes("cluster") | Where {$_.$text -eq $cluster}
$updateTag.ParentNode.RemoveChild($updateTag)
$xmlConfig.Save($xmlFile) } ELSE {
$xmlConfig.config.portgroups.ChildNodes | ? {$name -eq $_.name} | % {$xmlConfig.config.portgroups.RemoveChild($_)} | Out-Null
$xmlConfig.Save($xmlFile) }

Notice how we see the same #text and “NestedCluster” output after the commands ran when we were just trying to remove a cluster? That’s because powershell sees there are more than 1 cluster assigned to the VM Network portgroup and it just removed it.

Here is the full script to check if there are multiple XML attributes named “cluster” and remove it or remove the entire portgroup element.

#Define XML file and Get its contents
$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

#Define variables and prompt for input
$name = Read-Host -Prompt "Enter the name of the Portgroup (ex: Management Network)"
$cluster = Read-Host -Prompt "Enter the name of the cluster for this Portgroup (ex: Cluster1)"

#IF/ELSE Statement to remove a single attribute or an entire element
$clusters = $xmlConfig.config.portgroups.ChildNodes | ? {$name -eq $_.name}
IF ($clusters.cluster.count -gt 1){
$text = "#text"
$clusterTag = $xmlConfig.config.portgroups.portgroup | Where {$_.name -eq $name}
$updateTag = $clusterTag.SelectNodes("cluster") | Where {$_.$text -eq $cluster}
$updateTag.ParentNode.RemoveChild($updateTag)
$xmlConfig.Save($xmlFile) } ELSE {
$xmlConfig.config.portgroups.ChildNodes | ? {$name -eq $_.name} | % {$xmlConfig.config.portgroups.RemoveChild($_)} | Out-Null
$xmlConfig.Save($xmlFile) }

Adding Values to XML Using PowerShell

Continuing on from the previous post on getting values from an XML file using PowerShell, I’m going to talk about how to add values to an XML files. With the recent project I was working on I needed a way to easily add new portgroups into the XML configuration file. Every environment changes and while having something manually update an XML file would work there is always the possibility of messing up the syntax or something much worse.

Let’s have a look at the sample XML file again.

What we have here is 3 port groups (Management Network, VM Network, Web Network) with a VLAN, VirtualSwitch, and Cluster defined. The entire file sits inside the “config” tags. Each portgroup lives inside the “portgroups” tag, and the values for each portgroup live inside the individual “portgroup” tags.

The goal here is to add a brand new portgroup to this XML file so when we can then add that portgroup with the correct values to our ESXi hosts. Given our existing structure we’ll need a name, VLAN, Virtual Switch, and Cluster for this new portgroup.

In order to update our XML file we need to get the XML file contents and we do that by using the following command (again, using the file path as a variable so I can easily change it later).

$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

Now that we have the contents of the XML file, we need to gather the name, VLAN, Virtual Switch, and Cluster for this new portgroup. The question you’ll have to ask yourself is do you want to update the script file with each new portgroup you need to add or do you just want the script to prompt for those values each time it’s run?

If you want to manually update the file and let it run by itself just define the following variables in your script. For this example we’re creating a new portgroup called “App Network” with VLAN 31, on vSwitch1 and on cluster2.

$name = "App Network"
$vlanID = "31"
$virtualSwitch = "vSwitch1"
$cluster = "cluster2"

When we need to add these values into our XML file, we will refer to them using $name, $vlanID, $virtualSwitch, and $cluster.

Let’s do the same thing, but where we’re prompted for the values each time.

$name = Read-Host -Prompt "Enter the name of the new Portgroup (ex: Management Network)"
$vlanId = Read-Host -Prompt "Enter the VLAN Id number for the Portgroup (ex: 13)"
$virtualSwitch = Read-Host -Prompt "Enter the name of the Virtual Switch for Portgroup (ex: vSwitch1)"
$cluster = Read-Host -Prompt "Enter the name of the cluster for this Portgroup (ex: Cluster1)"

I saved the variables above as a file named “addPortgroup.ps1” in the C:\Scripts directory. Running that script in PowerShell prompted for my input for each of those lines. Instead of updating a script file each time I need to add a new Portgroup, I can run the script and enter the values myself.

Now that we’ve recorded these values, let’s start using them.

First off we need to create a new portgroup tag since we’re adding a new portgroup. We use a variable name (in this case $newPortgroup) to define where this tag will live. $xmlConfig.config.Portgroups is the parent of each of the Portgroups we currently have. What we’re doing here is saying make a change to by adding a new element called .

$newPortgroup = $xmlConfig.config.Portgroups.AppendChild($xmlConfig.CreateElement("Portgroup"))

Once we do that, now we’re going to start adding those attributes we already defined. We’ll start with name.

$newPortgroup.SetAttribute("Name",$name)

In the new Portgroup element we created, we set an attribute for and set it to the value of $name (“App Network” for our example).

Now, we can repeat that process for each attribute just like above and save our work (I’ll show how to save in a later step), but if we do that our XML formatting may not be what we’re looking for.

On line 20 of our sample.xml file we see how this data gets saved.

As you can see each attribute is added on the same line. Maybe this works for you, and if so, great. For me, I wanted to maintain the existing formatting, so there is a little more work to do.

Now we need to add each remaining attribute as a child to the already created “App Network” portgroup. We do that with the following command.

$newvlanIdAttribute = $newPortgroup.AppendChild($xmlConfig.CreateElement("vlanId"))
$newvlanIdValue = $newvlanIdAttribute.AppendChild($xmlConfig.CreateTextNode($vlanId))

The first line creates a new attribute called “vlanId” that’s been added to our new then the second line set the value of that attribute to $vlanId (VLAN 31 for this example).

We can now add Virtual Switch and Cluster the exact same way.

$newvirtualSwitchAttribute = $newPortgroup.AppendChild($xmlConfig.CreateElement("virtualSwitch"))
$newvirtualSwitchValue = $newvirtualSwitchAttribute.AppendChild($xmlConfig.CreateTextNode($virtualSwitch))
$newclusterAttribute = $newPortgroup.AppendChild($xmlConfig.CreateElement("cluster"))
$newclusterValue = $newclusterAttribute.AppendChild($xmlConfig.CreateTextNode($cluster))

With all the new elements created and the values set, we need to save this configuration. If we close down PowerShell right now none of our updates are saved. Save using the following command

$xmlConfig.Save($xmlFile)

Now that we’ve updated the xml file, let’s see what it looks like In PowerShell. We’ll need to re-check the contents of the XML file as it changed since we lasted checked.

$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

And now let’s look at $xmlConfig.config.portgroup.portgroups

And if we look at the contents of the XML file we can see that “App Network” matches the layout of our other portgroups

And here is what the script looks like all together:

#Define XML file and Get its contents
$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

#Define variables and prompt for input
$name = Read-Host -Prompt "Enter the name of the new Portgroup (ex: Management Network)"
$vlanId = Read-Host -Prompt "Enter the VLAN Id number for the Portgroup (ex: 13)"
$virtualSwitch = Read-Host -Prompt "Enter the name of the Virtual Switch for Portgroup (ex: vSwitch1)"
$cluster = Read-Host -Prompt "Enter the name of the cluster for this Portgroup (ex: Cluster1)"

#Create a new XML element with the input entered above and save
$newPortgroup = $xmlConfig.config.Portgroups.AppendChild($xmlConfig.CreateElement("Portgroup"));
$newPortgroup.SetAttribute("Name",$name);
$newvlanIdAttribute = $newPortgroup.AppendChild($xmlConfig.CreateElement("vlanId"));
$newvlanIdValue = $newvlanIdAttribute.AppendChild($xmlConfig.CreateTextNode($vlanId));
$newvirtualSwitchAttribute = $newPortgroup.AppendChild($xmlConfig.CreateElement("virtualSwitch"));
$newvirtualSwitchValue = $newvirtualSwitchAttribute.AppendChild($xmlConfig.CreateTextNode($virtualSwitch));
$newclusterAttribute = $newPortgroup.AppendChild($xmlConfig.CreateElement("cluster"));
$newclusterValue = $newclusterAttribute.AppendChild($xmlConfig.CreateTextNode($cluster));
$xmlConfig.Save($xmlFile)

Getting Values from XML Using PowerShell

Working on a recent project I was tasked with maintaining VMware host configuration via XML files. The goal was to have a central location to modify the settings of a host (DNS, vmkernel interfaces, vswitch config, port groups) without having to update each host by hand. This lead me down the path of using PowerShell with XML files as the configuration source.

Below is the sample of my XML file.

What we have here is 3 port groups (Management Network, VM Network, Web Network) with a VLAN, Virtual Switch, and Cluster defined. The entire file sits inside the “config” tags. Each portgroup lives inside the “portgroups” tag, and the values for each portgroup live inside the individual “portgroup” tags.

Below is a sample command to get values from an XML file. We pass the file location as a variable and then we store all those values inside another variable to make working with the values easier.

$xmlFile = "C:\Scripts\sample.xml"
$xmlConfig = [System.Xml.XmlDocument](Get-Content $xmlFile)

And once we type in $xmlConfig into Powershell we can see that it is pulling in the data from our XML file.

We can see the “config” section, but how do we dig deeper? Type in $xmlconfig.config and let’s look at the output

Now we can see the “portgroups” section. Let’s go deeper and see what’s inside of that by typing the following:

$xmlconfig.config.portgroups

Nice! Now we can see all the portgroups we have defined. In order to get detail on each of these, we can type $xmlconfig.config.portgroups.portgroup and we’ll get a list of all the portgroups and all their values.

Now let’s say we are only concerned about retrieving values from one of those portgroups. We can add a Where clause to find only the Portgroups that exist on vSwitch1 as an example with the following command:

$xmlconfig.config.portgroups.portgroup | Where {$_.virtualswitch -eq "vSwitch1"}

Grabbing values out of an XML file is pretty straightforward. In the next post we’ll talk about how to add information to an XML file.