Need Help Reading an XML Document

Tagged: 

This topic contains 10 replies, has 2 voices, and was last updated by Profile photo of Jonathan Shewbridge Jonathan Shewbridge 6 days, 5 hours ago.

  • Author
    Posts
  • #58673
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    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

    2) 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
    
  • #58676
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    .

  • #58678
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    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.

  • #58694
    Profile photo of Fredrik Kacsmarck
    Fredrik Kacsmarck
    Participant

    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.

    • #58696
      Profile photo of Jonathan Shewbridge
      Jonathan Shewbridge
      Participant

      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
  • #58700
    Profile photo of Fredrik Kacsmarck
    Fredrik Kacsmarck
    Participant

    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 🙂

  • #58721
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    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
    
  • #58724
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    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
    
  • #58726
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    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
    
  • #58729
    Profile photo of Fredrik Kacsmarck
    Fredrik Kacsmarck
    Participant

    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.

  • #58735
    Profile photo of Jonathan Shewbridge
    Jonathan Shewbridge
    Participant

    Thanks for your help!

You must be logged in to reply to this topic.