Home > Net >  Powershell: How can I append to an existing XML document correctly?
Powershell: How can I append to an existing XML document correctly?

Time:04-23

I could really use some help with a simple XML operation.

I have a function that queries and retrieves WHOIS data from a domain via XMLAPI (Invoke-RestMethod). The function spits out a PSObject with properties corresponding to the WHOIS request. It outputs either CSV/JSON/XML/XLSX data for each domain when you query it.

Right now, I am focused on getting the XML output to work correctly. The format of that output is detailed in the full XML file I pasted at the bottom of my post.

For the sake of reference, here's a small snippet of the XML that is generated when the -Append flag is not set:

<Objects>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="DomainName" Type="System.String">google.com</Property>
    <Property Name="DomainExtension" Type="System.String">.com</Property>
    <Property Name="RegistryDomainID" Type="System.String">2138514_DOMAIN_COM-VRSN</Property>
    <Property Name="DNSSEC" Type="System.String">unsigned</Property>
    ... Snipped for length
    </Object>
</Objects>

When the -Append flag is set, I need to generate XML like the following:

<Objects>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="DomainName" Type="System.String">google.com</Property>
    <Property Name="DomainExtension" Type="System.String">.com</Property>
    <Property Name="RegistryDomainID" Type="System.String">2138514_DOMAIN_COM-VRSN</Property>
    <Property Name="DNSSEC" Type="System.String">unsigned</Property>
    ... Snipped for length
  </Object>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="DomainName" Type="System.String">bing.com</Property>
    <Property Name="DomainExtension" Type="System.String">.com</Property>
    <Property Name="RegistryDomainID" Type="System.String">49639_DOMAIN_COM-VRSN</Property>
    <Property Name="DNSSEC" Type="System.String">unsigned</Property>
    ... Snipped for length
  </Object>
</Objects>

And ideally, I would also like to change some attributes and node names to as follows (This is lower in priority and just icing on the cake if someone could explain how):

<Domains>
  <Domain Id="google.com">
    <Property Name="DomainName" Type="System.String">google.com</Property>
    <Property Name="DomainExtension" Type="System.String">.com</Property>
    <Property Name="RegistryDomainID" Type="System.String">2138514_DOMAIN_COM-VRSN</Property>
    <Property Name="DNSSEC" Type="System.String">unsigned</Property>
    ... Snipped for length
  </Domain>
  <Domain Id="bing.com">
    <Property Name="DomainName" Type="System.String">bing.com</Property>
    <Property Name="DomainExtension" Type="System.String">.com</Property>
    <Property Name="RegistryDomainID" Type="System.String">49639_DOMAIN_COM-VRSN</Property>
    <Property Name="DNSSEC" Type="System.String">unsigned</Property>
    ... Snipped for length
  </Domain>
</Domains>

Essentially what I want to do is append new data to the XML document (if data exists). It should just simply be listed as another object in the <Objects> root node as shown above.

Here is the code so-far:

if($Append){

    # Added suggested code here:

    $NewFile    = $FileNameNoExt   "_new"   $FileNameExt
    $NewFile    = Join-Path $BasePath $NewFile
    $InputPath  = $FinalPath
    $OutputPath = $NewFile

    [XDocument]$XMLExisting = [XDocument]::Load($InputPath)
    [XElement]$SelectedNode = $XMLExisting.Element("Objects")
    [XName]$name = "Object"
    
    [XElement]$newObjectElement = New-Object -TypeName System.Xml.Linq.XElement $name, "some value"
    $SelectedNode.Add($newObjectElement)
    $XMLExisting.Save($OutputPath)

    
}else{

    [System.Xml.XmlDocument]$xml = ""
    $xml.PreserveWhitespace = $true
    
    # $Properties variable here holds all paramaters that need to be converted to XML.
    # This needs to be taken into account when -Append is specified.
    [System.Xml.XmlDocument]$xml = $Properties | ConvertTo-Xml -as Stream -Depth 3
    $xml.Save($FinalPath)
}

Full XML Document showing every property:

<?xml version="1.0" encoding="utf-8"?>
<Objects>
  <Object Type="System.Management.Automation.PSCustomObject">
    <Property Name="DomainName" Type="System.String">google.com</Property>
    <Property Name="DomainExtension" Type="System.String">.com</Property>
    <Property Name="RegistryDomainID" Type="System.String">2138514_DOMAIN_COM-VRSN</Property>
    <Property Name="DNSSEC" Type="System.String">unsigned</Property>
    <Property Name="WhoisServer" Type="System.String">whois.markmonitor.com</Property>
    <Property Name="WhoisLastUpdate" Type="System.String">04/22/2022 10:56:41 AM</Property>
    <Property Name="CreatedDate" Type="System.String">09/15/1997 07:00:00 AM</Property>
    <Property Name="UpdatedDate" Type="System.String">09/09/2019 03:39:04 PM</Property>
    <Property Name="ExpiresDate" Type="System.String">09/13/2028 07:00:00 AM</Property>
    <Property Name="AuditCreatedDate" Type="System.String">04/22/2022 11:01:49 AM</Property>
    <Property Name="AuditUpdatedDate" Type="System.String">04/22/2022 11:01:49 AM</Property>
    <Property Name="RegistrarRegistrationExp" Type="System.String">09/13/2028 07:00:00 AM</Property>
    <Property Name="RegistrarName" Type="System.String">MarkMonitor, Inc.</Property>
    <Property Name="RegistrarContactEmail" Type="System.String">[email protected]</Property>
    <Property Name="RegistrarAbuseEmail" Type="System.String">[email protected]</Property>
    <Property Name="RegistrarAbusePhone" Type="System.String"> 1.2083895770</Property>
    <Property Name="RegistrarParseCode" Type="System.Int64">3515</Property>
    <Property Name="RegistrarIANAID" Type="System.String">292</Property>
    <Property Name="RegistrarURL" Type="System.String">http://www.markmonitor.com</Property>
    <Property Name="EstimatedDomainAge" Type="System.Int64">8985</Property>
    <Property Name="NS" Type="System.Object[]">
      <Property Type="System.String">ns3.google.com</Property>
      <Property Type="System.String">ns4.google.com</Property>
      <Property Type="System.String">ns2.google.com</Property>
      <Property Type="System.String">ns1.google.com</Property>
    </Property>
    <Property Name="NS00" Type="System.String">ns3.google.com</Property>
    <Property Name="NS01" Type="System.String">ns4.google.com</Property>
    <Property Name="NS02" Type="System.String">ns2.google.com</Property>
    <Property Name="NS03" Type="System.String">ns1.google.com</Property>
    <Property Name="Status" Type="System.Object[]">
      <Property Type="System.String">clientUpdateProhibited</Property>
      <Property Type="System.String">clientTransferProhibited</Property>
      <Property Type="System.String">clientDeleteProhibited</Property>
      <Property Type="System.String">serverUpdateProhibited</Property>
      <Property Type="System.String">serverTransferProhibited</Property>
      <Property Type="System.String">serverDeleteProhibited</Property>
    </Property>
    <Property Name="Status00" Type="System.String">clientUpdateProhibited</Property>
    <Property Name="Status01" Type="System.String">clientTransferProhibited</Property>
    <Property Name="Status02" Type="System.String">clientDeleteProhibited</Property>
    <Property Name="Status03" Type="System.String">serverUpdateProhibited</Property>
    <Property Name="Status04" Type="System.String">serverTransferProhibited</Property>
    <Property Name="Status05" Type="System.String">serverDeleteProhibited</Property>
    <Property Name="RegistrantName" Type="System.String">
    </Property>
    <Property Name="RegistrantOrganization" Type="System.String">Google LLC</Property>
    <Property Name="RegistrantCity" Type="System.String">
    </Property>
    <Property Name="RegistrantStreet" Type="System.String">
    </Property>
    <Property Name="RegistrantPostalCode" Type="System.String">
    </Property>
    <Property Name="RegistrantState" Type="System.String">CA</Property>
    <Property Name="RegistrantCountry" Type="System.String">UNITED STATES</Property>
    <Property Name="RegistrantCountryCode" Type="System.String">US</Property>
    <Property Name="RegistrantEmail" Type="System.String">Select Request Email Form at https://domains.markmonitor.com/whois/google.com</Property>
    <Property Name="RegistrantPhone" Type="System.String">
    </Property>
    <Property Name="RegistrantFax" Type="System.String">
    </Property>
    <Property Name="AdminName" Type="System.String">
    </Property>
    <Property Name="AdminOrganization" Type="System.String">Google LLC</Property>
    <Property Name="AdminStreet" Type="System.String">
    </Property>
    <Property Name="AdminCity" Type="System.String">
    </Property>
    <Property Name="AdminState" Type="System.String">CA</Property>
    <Property Name="AdminPostalCode" Type="System.String">
    </Property>
    <Property Name="AdminCountry" Type="System.String">UNITED STATES</Property>
    <Property Name="AdminCountryCode" Type="System.String">US</Property>
    <Property Name="AdminPhone" Type="System.String">
    </Property>
    <Property Name="AdminFax" Type="System.String">
    </Property>
    <Property Name="AdminEmail" Type="System.String">Select Request Email Form at https://domains.markmonitor.com/whois/google.com</Property>
    <Property Name="TechName" Type="System.String">
    </Property>
    <Property Name="TechOrganization" Type="System.String">Google LLC</Property>
    <Property Name="TechStreet" Type="System.String">
    </Property>
    <Property Name="TechCity" Type="System.String">
    </Property>
    <Property Name="TechState" Type="System.String">CA</Property>
    <Property Name="TechPostalCode" Type="System.String">
    </Property>
    <Property Name="TechCountry" Type="System.String">UNITED STATES</Property>
    <Property Name="TechCountryCode" Type="System.String">US</Property>
    <Property Name="TechPhone" Type="System.String">
    </Property>
    <Property Name="TechFax" Type="System.String">
    </Property>
    <Property Name="TechEmail" Type="System.String">Select Request Email Form at https://domains.markmonitor.com/whois/google.com</Property>
    <Property Name="RegistryDataDomainName" Type="System.String">google.com</Property>
    <Property Name="RegistryDataRegistrarName" Type="System.String">MarkMonitor Inc.</Property>
    <Property Name="RegistryDataRegistrarParseCode" Type="System.Int64">251</Property>
    <Property Name="RegistryDataRegistrarIANAID" Type="System.String">292</Property>
    <Property Name="RegistryDataCreatedDate" Type="System.String">09/15/1997 04:00:00 AM</Property>
    <Property Name="RegistryDataUpdatedDate" Type="System.String">09/09/2019 03:39:04 PM</Property>
    <Property Name="RegistryDataExpiresDate" Type="System.String">09/14/2028 04:00:00 AM</Property>
    <Property Name="RegistryDataAuditCreatedDate" Type="System.String">04/22/2022 11:01:49 AM</Property>
    <Property Name="RegistryDataAuditUpdatedDate" Type="System.String">04/22/2022 11:01:49 AM</Property>
    <Property Name="RegistryDataNS" Type="System.Object[]">
      <Property Type="System.String">ns1.google.com</Property>
      <Property Type="System.String">ns2.google.com</Property>
      <Property Type="System.String">ns3.google.com</Property>
      <Property Type="System.String">ns4.google.com</Property>
    </Property>
    <Property Name="RegistryDataNS00" Type="System.String">ns1.google.com</Property>
    <Property Name="RegistryDataNS01" Type="System.String">ns2.google.com</Property>
    <Property Name="RegistryDataNS02" Type="System.String">ns3.google.com</Property>
    <Property Name="RegistryDataNS03" Type="System.String">ns4.google.com</Property>
    <Property Name="RegistryDataStatus" Type="System.Object[]">
      <Property Type="System.String">clientDeleteProhibited</Property>
      <Property Type="System.String">clientTransferProhibited</Property>
      <Property Type="System.String">clientUpdateProhibited</Property>
      <Property Type="System.String">serverDeleteProhibited</Property>
      <Property Type="System.String">serverTransferProhibited</Property>
      <Property Type="System.String">serverUpdateProhibited</Property>
    </Property>
    <Property Name="RegistryDataStatus00" Type="System.String">clientDeleteProhibited</Property>
    <Property Name="RegistryDataStatus01" Type="System.String">clientTransferProhibited</Property>
    <Property Name="RegistryDataStatus02" Type="System.String">clientUpdateProhibited</Property>
    <Property Name="RegistryDataStatus03" Type="System.String">serverDeleteProhibited</Property>
    <Property Name="RegistryDataStatus04" Type="System.String">serverTransferProhibited</Property>
    <Property Name="RegistryDataStatus05" Type="System.String">serverUpdateProhibited</Property>
    <Property Name="StrippedText" Type="System.String">Domain Name: google.com
Registrar WHOIS Server: whois.markmonitor.com
Registrar URL: http://www.markmonitor.com
Updated Date: 2019-09-09T15:39:04 0000
Creation Date: 1997-09-15T07:00:00 0000
Registrar Registration Expiration Date: 2028-09-13T07:00:00 0000
Registrar: MarkMonitor, Inc.
Registrar IANA ID: 292
Registrar Abuse Contact Email: [email protected]
Registrar Abuse Contact Phone:  1.2083895770
Domain Status: clientUpdateProhibited (https://www.icann.org/epp#clientUpdateProhibited)
Domain Status: clientTransferProhibited (https://www.icann.org/epp#clientTransferProhibited)
Domain Status: clientDeleteProhibited (https://www.icann.org/epp#clientDeleteProhibited)
Domain Status: serverUpdateProhibited (https://www.icann.org/epp#serverUpdateProhibited)
Domain Status: serverTransferProhibited (https://www.icann.org/epp#serverTransferProhibited)
Domain Status: serverDeleteProhibited (https://www.icann.org/epp#serverDeleteProhibited)
Registrant Organization: Google LLC
Registrant State/Province: CA
Registrant Country: US
Registrant Email: Select Request Email Form at https://domains.markmonitor.com/whois/google.com
Admin Organization: Google LLC
Admin State/Province: CA
Admin Country: US
Admin Email: Select Request Email Form at https://domains.markmonitor.com/whois/google.com
Tech Organization: Google LLC
Tech State/Province: CA
Tech Country: US
Tech Email: Select Request Email Form at https://domains.markmonitor.com/whois/google.com
Name Server: ns3.google.com
Name Server: ns4.google.com
Name Server: ns2.google.com
Name Server: ns1.google.com
</Property>
  </Object>
</Objects>

Hopefully this clears things up a bit. Let me know if I can explain a specific part of my problem with more clarity and I will do my best.

Thanks so much for any assistance.

CodePudding user response:

Not sure what kind of new data you want to add, but try the following:

using namespace System.Xml.Linq

$InputPath = "data.xml"
$OutputPath = "data_new.xml"

[XDocument]$XMLExisting = [XDocument]::Load($InputPath)
[XElement]$SelectedNode = $XMLExisting.Element("Objects")
[XName]$name = "SomeNewElement"
[XElement]$newObjectElement = New-Object -TypeName XElement $name, "some value"

$SelectedNode.Add($newObjectElement)
$XMLExisting.Save($OutputPath)

The output will be:

...you whole XML goes here...
</Property>
  </Object>
  <SomeNewElement>some value</SomeNewElement>
</Objects>

CodePudding user response:

I ended up figuring this out after a lot of trial and error:

switch ($FileNameExt) {
    ".CSV" {
        # Snipped for brevity
    }
    ".XML" {

        # Feed our Properties object to ConvertTo-Xml to get our starting point
        [xml]$InputXML = $Properties | ConvertTo-Xml -as Stream -Depth 4
        
        # Clean up DomainName before we insert it as an attribute
        $DName    = $DomainName.Replace('www.','')
        $DName    = $DName.Replace(' ','')

        # Isolate our new Domain Properties object for cleanup
        # Set the 'Name' attribute to the actual domain being queried
        # Remove the 'Type' attribute as it's unecessary
        # Format the XML to preserve indentations and linebreaks
        # Replace <Object* tags with <Domain* tags

        $DomainNode = $InputXML.Objects.SelectSingleNode("//Object")
        $DomainNode.SetAttribute("Name","$DName")
        $DomainNode.RemoveAttribute("Type")
        $DomainNodeFormatted = Format-XMLPretty -XML $DomainNode.OuterXml
        $DomainNodeFormatted = $DomainNodeFormatted.Replace('<Object Name=','<Domain Name=')
        $DomainNodeFormatted = $DomainNodeFormatted.Replace('</Object>','</Domain>')
        [xml]$DomainNodeXML = $DomainNodeFormatted

        # Pass our Domain Properties off to remove the uneeded type attribute
        Format-XMLRemoveNestedAttributes -XMLNodeList ($DomainNodeXML.Domain.ChildNodes) -Attribute 'Type'

        # Save the final properties node so it's ready for import
        $Node = $DomainNodeXML.SelectSingleNode( "//Domain" )
         
        if(!$Append){

            # Since we're not appending, create a new XML Document 
            # and create the root Domains node.

            $NewXML = New-Object System.Xml.XmlDocument
            $NewXML.AppendChild($NewXML.CreateElement("Domains")) | Out-Null

            # Import and append our domain properties node to the
            # root domains node.

            $NewXMLNode = $NewXML.ImportNode($Node, $true)
            $NewXMLRoot = $NewXML.SelectSingleNode("//Domains")
            $NewXMLRoot.AppendChild($NewXMLNode) | Out-Null
            
            # Make our XML pretty!

            [xml]$NewXMLFormatted = Format-XMLPretty -Xml $NewXML
            $NewXMLFormatted.Save($FinalPath)

        } else {

            # Since we're appending, load from disk.

            $OutputXMLPath = $FinalPath
            $ExistingXML = New-Object System.Xml.XmlDocument
            $ExistingXML.Load($FinalPath)

            # Import the final domain properties node and 
            # Append it to the existing defined domains nodes

            $NewXMLNode = $ExistingXML.ImportNode($Node, $true)
            $ExistingXMLRoot = $ExistingXML.SelectSingleNode("//Domains")
            $ExistingXMLRoot.AppendChild($NewXMLNode) | Out-Null

            # Make our XML pretty!

            [xml]$ExistingXMLFormatted = Format-XMLPretty -Xml $ExistingXML
            $ExistingXMLFormatted.Save($OutputXMLPath)
            
        }
        break
    }
    ".JSON" {
        # Snipped for brevity
    }
    ".XLSX" {
        # Snipped for brevity
    }
    default {
        throw [System.ArgumentException] "Cannot save file: Invalid file extension passed in SavePath."
    }
}

Here is a Gist with the full code and associated helper functions: https://gist.github.com/visusys/eaa379c35fe0bfce4bb9a62fad524cf7

  • Related