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