Need Help Reading an XML Document

I currently have a flat file that I use to release source code changes to a number of servers, and I would like to convert that flat file to XML. A while back, I ran across this article http://www.powershellmagazine.com/2013/08/19/mastering-everyday-xml-tasks-in-powershell/ that illustrates an easy way to generate XML using PowerShell.

I’d like to get feedback from two areas:

  1. Here is the XML file that I generated using the concepts found in the link above. I’m not an XML expert by any means; so, if there is a better way to represent the “one source code file to many servers” relationship for DestinationComputer_NME, I’m open to suggestions.

https://gist.github.com/shew01/b856c835ae5417848e3068bd1478c7c5#file-c-cron-dba-jps-xml_question-release_code_source_code-xml

  1. When I need to release a change to a piece of source code, I would like to read the above XML document for the file group and then release (i.e., copy) all of files within that file group to all of the servers that are applicable to that file group. (I want to copy all of the files for a given file group so that I can be assured that all of the individual files for each file group are of the same “version.”) For example, if I make a change to any file in the “FileGroup_C” file group, I would like to loop through the XML (for lack of a better term) and determine that I need to copy “my_script_d.ps1” and “my_script_e.ps1” to “WORKSTATIONA” and “WORKSTATIONB”.

Here is the code that I have so far, which partially works. What I need is a way to “drill down” into the DestinationComputers element and display the individual names for DestinationComputer_NME. (Currently, DestinationComputers does not return correctly.)

	$XMLFilePath_NME = "C:\cron\dba\jps\xml_question\release_code_source_code.xml"

	$XML_OBJ = New-Object -TypeName XML

	$XML_OBJ.Load($XMLFilePath_NME)

	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup_C.SourceCodeFile | Select-Object -Property File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC

	foreach ($Element in $Loop_OBJ)
	{
		$a = $Element.File_NME

		$b = $XML_OBJ.SourceCodeFiles.FileGroup_C.SourceCodeFile.DestinationComputers.DestinationComputer_NME

		write-output "$a $b"
	}

	exit

Here is the output that I currently get. It does not contain the appropriate computer names–it appears to repeat all of the computer names.

my_script_d.ps1 SERVER MANAGEMENT REPORTING WORKSTATIONA WORKSTATIONB SERVER MANAGEMENT REPORTING WORKSTATIONA WORKSTATIONB
my_script_e.ps1 SERVER MANAGEMENT REPORTING WORKSTATIONA WORKSTATIONB SERVER MANAGEMENT REPORTING WORKSTATIONA WORKSTATIONB

.

Please excuse the extra posts. I finally found a way to point to the XML on GitHub Gist instead of having the browser attempt to render it and make it unreadable.

Any help is appreciated on the above scenario for reading XML child elements using PowerShell.

In the foreach loop you reference the $XMLObject every time and not the specific item in the $Loop_Obj.

So if you change the $b line to:

$b = $Element.DestinationComputers.DestinationComputer_NME

You should not get the repeated behaviour.
As you are looking at the current item in the Loop_Obj.

Thanks, that fixed the repeat behavior.

Hmmm… I just noticed another problem. I need to find a way to “search” the XML for the appropriate file list group, for example, “FileGroup_C.” My example has “FileGroup_C” hard coded. I presume that I need to use a “where-object” command, but I’m not sure how to put it into the following line.

[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup_C.SourceCodeFile | Select-Object -Property File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC

Maybe there is a better way but if it’s not a problem I would use an attribute on the Filegroup tag.
So instead of: (xml chevrons removed)
FileGroup_A /FileGroup_A
FileGroup_B /FileGroup_B

I would use:
FileGroup group=“A” /FileGroup
FileGroup group=“B” /FileGroup

Then you could reference them by using:

$groupLetter = 'A'
$XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter}

Edit: XML seems to be rendered :slight_smile:

I’m still doing something wrong, but I am don’t know what it is. Here is a link to the revised XML file. https://gist.github.com/shew01/134f4f0919f5b517e2a803ed1c93f1dd#file-release_code_source_code-xml

Here is the revised code, but the output is not correct.

	$XMLFilePath_NME     = "C:\cron\dba\jps\xml_question\release_code_source_code.xml"

	$groupLetter = 'A'

	$XML_OBJ = New-Object -TypeName XML

	$XML_OBJ.Load($XMLFilePath_NME)

#	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup_C.SourceCodeFile | Select-Object -Property File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC
	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq "A"} | Select-Object -Property Group, File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC
#	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq "A"}

$Loop_OBJ.count

$Loop_OBJ | gm

$a = $Loop_OBJ.File_NME
$a
	foreach ($Element in $Loop_OBJ)
	{
		$a = $Element.File_NME

		$b = $Element.DestinationComputers.DestinationComputer_NME

		write-output "$a $b"
	}

	exit

Here is the output that I am getting.

1


   TypeName: Selected.System.Xml.XmlElement

Name                    MemberType   Definition
----                    ----------   ----------
Equals                  Method       bool Equals(System.Object obj)
GetHashCode             Method       int GetHashCode()
GetType                 Method       type GetType()
ToString                Method       string ToString()
Comment_DSC             NoteProperty object Comment_DSC=null
DestinationComputers    NoteProperty object DestinationComputers=null
DestinationLocation_DIR NoteProperty object DestinationLocation_DIR=null
File_NME                NoteProperty object File_NME=null
Group                   NoteProperty string Group=A
OverwriteFile_IND       NoteProperty object OverwriteFile_IND=null
SourceLocation_DIR      NoteProperty object SourceLocation_DIR=null

This version of the script is close, but all of the destination computers are displaying on a single line. I’m trying to find a way to break them out into separate lines so that I can loop through them.

	$XMLFilePath_NME     = "C:\cron\dba\jps\xml_question\release_code_source_code.xml"

	$groupLetter = 'C'

	$XML_OBJ = New-Object -TypeName XML

	$XML_OBJ.Load($XMLFilePath_NME)

#	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup_C.SourceCodeFile | Select-Object -Property File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC
#	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter} | Select-Object -Property Group, File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC
	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter} | Select-Object -Property Group, SourceCodeFile


$GroupHit_CNT = $Loop_OBJ.count

write-output ""
write-output "`$GroupHit_CNT = $GroupHit_CNT"
write-output ""

#$Loop_OBJ.SourceCodeFile.File_NME
#$Loop_OBJ.SourceCodeFile.OverwriteFile_IND
#$Loop_OBJ.SourceCodeFile.SourceLocation_DIR
#$Loop_OBJ.SourceCodeFile.DestinationLocation_DIR
#$Loop_OBJ.SourceCodeFile.DestinationComputers
#$Loop_OBJ.SourceCodeFile.Comment_DSC
#
#write-output ""
#write-output "-- Just before loop --------------------"

	foreach ($Element in $Loop_OBJ.SourceCodeFile)
	{
		$File_NME                = $Element.File_NME
		$OverwriteFile_IND       = $Element.OverwriteFile_IND
		$SourceLocation_DIR      = $Element.SourceLocation_DIR
		$DestinationLocation_DIR = $Element.DestinationLocation_DIR
		$Comment_DSC             = $Element.Comment_DSC

#$Element.DestinationComputers | gm

		write-output ""
		write-output "`$File_NME                = $File_NME"
		write-output "`$OverwriteFile_IND       = $OverwriteFile_IND"
		write-output "`$SourceLocation_DIR      = $SourceLocation_DIR"
		write-output "`$DestinationLocation_DIR = $DestinationLocation_DIR"
		write-output "`$Comment_DSC             = $Comment_DSC"

#$Element.DestinationComputers | gm

		write-output "`$DestinationComputer_NME ="

		foreach ($Computer in $Element.DestinationComputers)
		{
			$a = $Computer.DestinationComputer_NME
			write-output "    $a"
		}
	}

	exit

Here is the current output.

$GroupHit_CNT = 1


$File_NME                = my_script_d.ps1
$OverwriteFile_IND       = Y
$SourceLocation_DIR      = W:\DBA\DBA\my_dir_d\
$DestinationLocation_DIR = c:\cron\dba\dest_d\
$Comment_DSC             = Comment D
$DestinationComputer_NME =
    WORKSTATIONA WORKSTATIONB

$File_NME                = my_script_e.ps1
$OverwriteFile_IND       = Y
$SourceLocation_DIR      = W:\DBA\DBA\my_dir_e\
$DestinationLocation_DIR = c:\cron\dba\dest_e\
$Comment_DSC             = Comment E
$DestinationComputer_NME =
    WORKSTATIONA WORKSTATIONB

There may be a better way to do it, but this code appears to work.

	$XMLFilePath_NME     = "C:\cron\dba\jps\xml_question\release_code_source_code.xml"

	$groupLetter = 'C'

	$XML_OBJ = New-Object -TypeName XML

	$XML_OBJ.Load($XMLFilePath_NME)

#	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup_C.SourceCodeFile | Select-Object -Property File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC
#	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter} | Select-Object -Property Group, File_NME, OverwriteFile_IND, SourceLocation_DIR, DestinationLocation_DIR, DestinationComputers, Comment_DSC
	[array]$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter} | Select-Object -Property Group, SourceCodeFile


$GroupHit_CNT = $Loop_OBJ.count

write-output ""
write-output "`$groupLetter  = $groupLetter"
write-output "`$GroupHit_CNT = $GroupHit_CNT"

#$Loop_OBJ.SourceCodeFile.File_NME
#$Loop_OBJ.SourceCodeFile.OverwriteFile_IND
#$Loop_OBJ.SourceCodeFile.SourceLocation_DIR
#$Loop_OBJ.SourceCodeFile.DestinationLocation_DIR
#$Loop_OBJ.SourceCodeFile.DestinationComputers
#$Loop_OBJ.SourceCodeFile.Comment_DSC
#
#write-output ""
#write-output "-- Just before loop --------------------"

	foreach ($Element in $Loop_OBJ.SourceCodeFile)
	{
		$File_NME                = $Element.File_NME
		$OverwriteFile_IND       = $Element.OverwriteFile_IND
		$SourceLocation_DIR      = $Element.SourceLocation_DIR
		$DestinationLocation_DIR = $Element.DestinationLocation_DIR
		$Comment_DSC             = $Element.Comment_DSC

		write-output ""
		write-output "`$File_NME                = $File_NME"
		write-output "`$OverwriteFile_IND       = $OverwriteFile_IND"
		write-output "`$SourceLocation_DIR      = $SourceLocation_DIR"
		write-output "`$DestinationLocation_DIR = $DestinationLocation_DIR"
		write-output "`$Comment_DSC             = $Comment_DSC"

#$Element.DestinationComputers | gm

		foreach ($Computer in $Element.DestinationComputers.GetEnumerator()."#text")
		{
			$DestinationComputer_NME = $Computer

			write-output "`$DestinationComputer_NME = $DestinationComputer_NME"
		}
	}

	exit

Here is the output.

$groupLetter  = C
$GroupHit_CNT = 1

$File_NME                = my_script_d.ps1
$OverwriteFile_IND       = Y
$SourceLocation_DIR      = W:\DBA\DBA\my_dir_d\
$DestinationLocation_DIR = c:\cron\dba\dest_d\
$Comment_DSC             = Comment D
$DestinationComputer_NME = WORKSTATIONA
$DestinationComputer_NME = WORKSTATIONB

$File_NME                = my_script_e.ps1
$OverwriteFile_IND       = Y
$SourceLocation_DIR      = W:\DBA\DBA\my_dir_e\
$DestinationLocation_DIR = c:\cron\dba\dest_e\
$Comment_DSC             = Comment E
$DestinationComputer_NME = WORKSTATIONA
$DestinationComputer_NME = WORKSTATIONB

Not exactly sure what you want to do but there is really just a small change to the original code.
The difference is that the “Where” statement is checking on the tag “one step up” in the hierarchy in the XML tree compared to what was done earlier.
E.g.

$XMLFilePath_NME = '"C:\cron\dba\jps\xml_question\release_code_source_code.xml'
$XML_OBJ = New-Object -TypeName XML
$XML_OBJ.Load($XMLFilePath_NME)

$groupLetter = 'A'
$Group_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter}
$Loop_OBJ = $Group_OBJ.SourceCodeFile

foreach ($Element in $Loop_OBJ)
{
	$a = $Element.File_NME
	$b = $Element.DestinationComputers.DestinationComputer_NME

	write-output "$a $b"
}

So the Group_OBJ contain the data from the selected group and downwards.
The Loop_OBJ contain the information one step down from the Group_OBJ.
You could do it on one line but I seperated the steps so that it was easier to read.
But you could do it like this:

$Loop_OBJ = $XML_OBJ.SourceCodeFiles.FileGroup | Where {$_.Group -eq $groupLetter} | Select -expandproperty SourceCodeFile

Hope that helps.

Thanks for your help!