Lately I seem to be answering a number of PowerShell XML related questions. When that happens I figure its time for an example. I have a script that I think originally was posted in a forum. I apologize that I didn’t note where it came from. But I’ve enhanced it (as I am likely to do). The script reads a bare bones XML file of computernames and builds a new XML file with inventory information such as operating system, computer system, logical disks and more retrieved from WMI. My script will hopefully serve as examples for working with XML files. I also use Write-Progress so that you can track what the script is doing.
#Get-XMLInventory.ps1
Param ([string]$inputfile="c:\psh\inventory.xml",
[string]$outputfile="c:\psh\inventory-out.xml"
)
Function Test-Ping {
Param([string]$computername=$(Throw "You must enter a computername to ping"))
# ping servers and only work with servers that respond
$reply = Get-WmiObject win32_PingStatus -Filter "Address = '$computername'"
if ($reply.statusCode -eq "0"){
#write $TRUE to the pipeline if computer is pingable
write $True
}
else {
write $False
}
} #end Test-Ping
#variables for Write-Progress
$activity="Server Inventory"
$status="Preparing process"
$current="Validating $inputfile"
Write-Progress -Activity $activity -status $status -current $current
# load XML
if (! (Test-Path $inputfile)) {
Write-Warning "Failed to find $inputfile"
Write-Progress -Activity $activity -status "Failure" -completed
return
}
[xml]$xml = Get-Content $inputfile
$count=$xml.computers.computer.count
$status="Enumerating computers nodes"
#enumerate through the computers nodes
for ($i=0; $i -lt $count; $i++) {
[int]$perComplete=($i/$count)*100
# get computername
$name = $xml.computers.computer[$i].getattribute("Name")
Write-Progress -Activity $activity -status $status `
-currentOperation "gathering information from $name" `
-percentcomplete $perComplete
# create status node and get status
$statusnode = $xml.CreateNode("element","Status","")
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Pinging $name" `
-percentcomplete $perComplete
$pingstatus=Test-Ping $name
$statusnode.setattribute("Reported",(Get-Date))
if (! $pingstatus) {
Write-Progress -Activity $activity -status $status `
-currentOperation "failed to ping $name" `
-percentcomplete $perComplete
$statusnode.set_innertext("Unreachable")
}
else {
$statusnode.set_innertext("Complete")
# get OS info
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Getting operating system information from $name" `
-percentcomplete $perComplete
$os = Get-WmiObject win32_operatingsystem -computer $name
$osnode = $xml.CreateNode("element","OS","")
# append OS attrs to node
$osnode.setattribute("Caption",$os.caption)
$osnode.setattribute("BuildNumber",$os.buildnumber)
$osnode.setattribute("ServicePack",$os.servicepackmajorversion)
$osnode.setattribute("Architecture",$os.osarchitecture)
$osnode.setattribute("InstallDate",$os.ConvertToDateTime($os.installDate))
$osnode.setattribute("LastBoot",$os.ConvertToDateTime($os.lastBootUpTime))
# append OS node to Computer node
$xml.computers.computer[$i].appendchild($osnode) | Out-Null
#get computer system information
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Getting computer system information from $name" `
-percentcomplete $perComplete
$csnode = $xml.CreateNode("element","ComputerSystem","")
$system=Get-WmiObject win32_computersystem -computername $name
#add attributes
$csnode.setattribute("Model",$system.model)
$csnode.setattribute("Manufacturer",$system.manufacturer)
$csnode.setattribute("SystemType",$system.systemType)
$memory="{0:F0}GB" -f ($system.totalphysicalmemory/1GB)
$csnode.setattribute("TotalMemory",$memory)
$xml.computers.computer[$i].appendchild($csnode) | Out-Null
# get local disks
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Getting logical disk information from $name" `
-percentcomplete $perComplete
$disks = Get-WmiObject -computer $name -query `
"select * from win32_logicaldisk where drivetype=3"
$disksnode = $xml.CreateNode("element","Disks","")
# go through the logical disks
foreach ($disk in $disks) {
# create disk node
$disknode = $xml.CreateNode("element","Disk","")
#create deviceid and freespace attribs
$disknode.setattribute("DeviceID",$disk.deviceid)
$size="{0:F0} MB" -f ($disk.size / 1MB)
$disknode.setattribute("Size",$size)
$freespace = "{0:F2} MB" -f ($disk.freespace / 1MB)
$disknode.setattribute("FreeSpace",$freespace)
# append Disk node to Disks node
$disksnode.appendchild($disknode) | Out-Null
} #end foreach $disk
# append disks node to Computer node
$xml.computers.computer[$i].appendchild($disksnode) | Out-Null
#get service information
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Getting service information from $name" `
-percentcomplete $perComplete
$services = Get-WmiObject "win32_service" -computer $name
$servicesnode = $xml.CreateNode("element","Services","")
# go through each service
foreach ($service in $services) {
# create service node
$servicenode = $xml.CreateNode("element","Service","")
$servicenode.setattribute("Name",$service.name)
$properties="DisplayName","Description","StartName","PathName","State","StartMode"
#create child nodes for each property
foreach ($property in $properties) {
$propertynode=$xml.CreateElement($property)
$propertynode.set_innertext($service.$property)
#add the property node to the service node
$servicenode.appendChild($propertynode) | Out-Null
} #end for each property
# append service node to services node
$servicesnode.appendchild($servicenode) | Out-Null
} #end foreach $service
# append services node to Computer node
$xml.computers.computer[$i].appendchild($servicesnode) | Out-Null
} #end Else computer is pingable
# append status node to Computer node
$xml.computers.computer[$i].appendchild($statusnode)| out-null
} #end out for loop
# output XML
#delete output file if it already exists
$status="Creating output file $outputfile"
if (Test-Path $outputfile) {
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Deleting $outputfile" -percentcomplete 100
del $outputfile
}
#save the new file
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Saving XML" -percentcomplete 100
$xml.save($outputfile)
Write-Progress -Activity $activity -Status "Finished" `
-CurrentOperation "Inventory Complete" -Completed
#end of script
The script takes parameters for the source and destination XML files. This version of the script is not designed to update an existing file, merely create a new report. The source XML file should look like this:
<Computers>
<Computer Name="Mycompany-dc01" />
<Computer Name="Offline" />
<Computer Name="XP01" />
<Computer Name="Vista01" />
<Computer Name="Win2k801" />
</Computers>
The Get-XMLInventory .ps1 script first verifies the source XML file exists and if it does, creates an XML document.
if (! (Test-Path $inputfile)) {
Write-Warning "Failed to find $inputfile"
Write-Progress -Activity $activity -status "Failure" -completed
return
}
[xml]$xml = Get-Content $inputfile
I need to enumerate all the computer nodes, grabbing the name from each. The name is stored as an attribute so look at the line that calls the GetAttribute() method.
$count=$xml.computers.computer.count
$status="Enumerating computers nodes"
#enumerate through the computers nodes
for ($i=0; $i -lt $count; $i++) {
[int]$perComplete=($i/$count)*100
# get computername
$name = $xml.computers.computer[$i].getattribute("Name")
Write-Progress -Activity $activity -status $status `
-currentOperation "gathering information from $name" `
-percentcomplete $perComplete
Once I have each name, I run it through a Test-Ping function. Assuming I can ping the computer, the script sets an attribute for a new node that indicates if the computer is reachable or not. I’ve added an attribute that captures the current date and time.
$statusnode.setattribute("Reported",(Get-Date))
if (! $pingstatus) {
Write-Progress -Activity $activity -status $status `
-currentOperation "failed to ping $name" `
-percentcomplete $perComplete
$statusnode.set_innertext("Unreachable")
}
else {
$statusnode.set_innertext("Complete")
For machines that are pingable I use Get-WMIObject to get operating system information and create a new xml node.
$os = Get-WmiObject win32_operatingsystem -computer $name
$osnode = $xml.CreateNode("element","OS","")
The node needs some attributes with different operating system properties which is handled by the SetAttribute() method.
# append OS attrs to node
$osnode.setattribute("Caption",$os.caption)
$osnode.setattribute("BuildNumber",$os.buildnumber)
$osnode.setattribute("ServicePack",$os.servicepackmajorversion)
$osnode.setattribute("Architecture",$os.osarchitecture)
$osnode.setattribute("InstallDate",$os.ConvertToDateTime($os.installDate))
$osnode.setattribute("LastBoot",$os.ConvertToDateTime($os.lastBootUpTime))
This node has to be added to the parent node, ie the computer.
# append OS node to Computer node
$xml.computers.computer[$i].appendchild($osnode) | Out-Null
The expression is piped to Out-Null merely for cosmetic purposes so that the screen isn’t cluttered with the results of every method. This process is repeated with the Win32_ComputerSystem class.
The disk information from the Win32_LogicalDisk class is handled a little differently since I may have several instances. I’m first going to create an outer <Disks> node.
$disks = Get-WmiObject -computer $name -query `
"select * from win32_logicaldisk where drivetype=3"
$disksnode = $xml.CreateNode("element","Disks","")
For every disk object returned by WMI I’m going to create a child node and set some attributes from the disk properties.
# go through the logical disks
foreach ($disk in $disks) {
# create disk node
$disknode = $xml.CreateNode("element","Disk","")
#create deviceid and freespace attribs
$disknode.setattribute("DeviceID",$disk.deviceid)
$size="{0:F0} MB" -f ($disk.size / 1MB)
$disknode.setattribute("Size",$size)
$freespace = "{0:F2} MB" -f ($disk.freespace / 1MB)
$disknode.setattribute("FreeSpace",$freespace)
# append Disk node to Disks node
$disksnode.appendchild($disknode) | Out-Null
} #end foreach $disk
Each Disk node is appended to the parent Disks which in turn is appended to the parent Computer node.
# append disks node to Computer node
$xml.computers.computer[$i].appendchild($disksnode) | Out-Null
For service information I wanted to do something a little different, more for teaching purposes than anything. Obviously I need some service information from WMI and I’m going to create an outer <Services> node.
$services = Get-WmiObject "win32_service" -computer $name
$servicesnode = $xml.CreateNode("element","Services","")
I want to create a child <Service> node for each service with selected service properties as singleton nodes so that the final result will look like this for each service:
<Services>
<Service Name="AeLookupSvc">
<DisplayName>Application Experience Lookup Service</DisplayName>
<Description>Process application compatibility lookup requests for applications as they are launched.</Description>
<StartName>LocalSystem</StartName>
<PathName>C:\WINDOWS\system32\svchost.exe -k netsvcs</PathName>
<State>Running</State>
<StartMode>Auto</StartMode>
</Service>
I achieve this by creating a new node and attribute for each service.
foreach ($service in $services) {
# create service node
$servicenode = $xml.CreateNode("element","Service","")
$servicenode.setattribute("Name",$service.name)
I then created an array of the WMI properties I wanted to use. For each property I create a new node element using the property name. The value, or innertext, for the node is the property value of the WMI service object. Each property node is then appended to the parent service node.
$properties="DisplayName","Description","StartName","PathName","State","StartMode"
#create child nodes for each property
foreach ($property in $properties) {
$propertynode=$xml.CreateElement($property)
$propertynode.set_innertext($service.$property)
#add the property node to the service node
$servicenode.appendChild($propertynode) | Out-Null
} #end for each property
The service node is appended to the services node:
# append service node to services node
$servicesnode.appendchild($servicenode) | Out-Null
Which in turn is appended to the computer node:
# append services node to Computer node
$xml.computers.computer[$i].appendchild($servicesnode) | Out-Null
Once completed, all that remains is to take the XML stored in memory and save it to a file. The script first tests to see if the output file already exists and deletes it if it does.
if (Test-Path $outputfile) {
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Deleting $outputfile" -percentcomplete 100
del $outputfile
}
The Save() method writes the XML to a file.
Write-Progress -Activity $activity -Status $status `
-CurrentOperation "Saving XML" -percentcomplete 100
$xml.save($outputfile)
Write-Progress -Activity $activity -Status "Finished" `
-CurrentOperation "Inventory Complete" -Completed
Here’s an excerpt of the final XML inventory report which you can use with any XML-related application such as PrimalXML 2009.
<Computers>
<Computer Name="Mycompany-dc01">
<OS Caption="Microsoft(R) Windows(R) Server 2003 Standard x64 Edition" BuildNumber="3790" ServicePack="2" Architecture="" InstallDate="04/26/2007 17:52:16" LastBoot="06/15/2009 09:38:34" />
<ComputerSystem Model="VMware Virtual Platform" Manufacturer="VMware, Inc." SystemType="x64-based PC" TotalMemory="0GB" />
<Disks>
<Disk DeviceID="C:" Size="8182 MB" FreeSpace="1372.70 MB" />
<Disk DeviceID="E:" Size="2046 MB" FreeSpace="1146.50 MB" />
<Disk DeviceID="F:" Size="4094 MB" FreeSpace="3995.39 MB" />
</Disks>
<Services>
<Service Name="AeLookupSvc">
<DisplayName>Application Experience Lookup Service</DisplayName>
<Description>Process application compatibility lookup requests for applications as they are launched.</Description>
<StartName>LocalSystem</StartName>
<PathName>C:\WINDOWS\system32\svchost.exe -k netsvcs</PathName>
<State>Running</State>
<StartMode>Auto</StartMode>
</Service>
<Service Name="Alerter">
<DisplayName>Alerter</DisplayName>
<Description>Notifies selected users and computers of administrative alerts. If the service is stopped, programs that use administrative alerts will not receive them. If this service is disabled, any services that explicitly depend on it will fail to start.</Description>
<StartName>NT AUTHORITY\LocalService</StartName>
<PathName>C:\WINDOWS\system32\svchost.exe -k LocalService</PathName>
<State>Running</State>
<StartMode>Auto</StartMode>
</Service>
As you can see it is not especially difficult creating XML documents. PowerShell v2.0 offers a bit more XML-related functionality but my script will work with both versions.
Download a zip file with the PowerShell script and a sample inventory.xml file here.
Enjoy and/or Extend!