What is the least effort way to insert an xml fragment into an existing [xml]
value ([xml]
of course is syntactic sugar for System.Xml.XmlDocument
)?
For example, say we have this newrelic.config file:
<configuration xmlns="urn:newrelic-config" agentEnabled="true">
<service licenseKey="**redacted**" />
<application>
<name>My Application</name>
</application>
<log level="info" />
<transactionTracer enabled="true" transactionThreshold="apdex_f" stackTraceThreshold="500" recordSql="obfuscated" explainEnabled="false" explainThreshold="500" />
<crossApplicationTracer enabled="true" />
<errorCollector enabled="true">
<ignoreErrors>
<exception>System.IO.FileNotFoundException</exception>
<exception>System.Threading.ThreadAbortException</exception>
</ignoreErrors>
<ignoreStatusCodes>
<code>401</code>
<code>404</code>
</ignoreStatusCodes>
</errorCollector>
<browserMonitoring autoInstrument="true" />
<threadProfiling>
<ignoreMethod>System.Threading.WaitHandle:InternalWaitOne</ignoreMethod>
<ignoreMethod>System.Threading.WaitHandle:WaitAny</ignoreMethod>
</threadProfiling>
</configuration>
And we want to insert an instrumentation
node under configuration
; not just one element but a whole fragment:
<instrumentation>
<applications>
<application name="MyApplication.exe" />
</applications>
</instrumentation>
Most other answers on SO flail around a lot with creating XML elements and attributes; for example this one:
# Create ExitProcess node
$exitProcessNode = $Document.CreateElement('ExitProcess')
# Create ProcessName node, add process name as inner text
$processNameNode = $Document.CreateElement('ProcessName')
$processNameNode.InnerText = $ProcessName
# Create ForceClose node, add setting as inner text
$forceCloseNode = $Document.CreateElement('ProcessName')
$ForceCloseNode.InnerText = $ForceClose.IsPresent
How can I make this shorter?
Note: I don't have huge documents so performance is not really an issue; I'm more after shorter PowerShell code.
CodePudding user response:
The correct way to do this is to import the node (ImportNode
) in the main document and than append the concerned child (AppendChild
) to the specific node:
$Main = [xml]'
<configuration xmlns="urn:newrelic-config" agentEnabled="true">
<service licenseKey="**redacted**" />
<application>
<name>My Application</name>
</application>
<log level="info" />
<transactionTracer enabled="true" transactionThreshold="apdex_f" stackTraceThreshold="500" recordSql="obfuscated" explainEnabled="false" explainThreshold="500" />
<crossApplicationTracer enabled="true" />
<errorCollector enabled="true">
<ignoreErrors>
<exception>System.IO.FileNotFoundException</exception>
<exception>System.Threading.ThreadAbortException</exception>
</ignoreErrors>
<ignoreStatusCodes>
<code>401</code>
<code>404</code>
</ignoreStatusCodes>
</errorCollector>
<browserMonitoring autoInstrument="true" />
<threadProfiling>
<ignoreMethod>System.Threading.WaitHandle:InternalWaitOne</ignoreMethod>
<ignoreMethod>System.Threading.WaitHandle:WaitAny</ignoreMethod>
</threadProfiling>
</configuration>'
$Fragment = [xml]'
<instrumentation>
<applications>
<application name="MyApplication.exe" />
</applications>
</instrumentation>'
$NewNode = $Main.ImportNode($Fragment.instrumentation, $True)
$Main.Configuration.AppendChild($NewNode)
[System.Xml.Linq.XDocument]::Parse($Main.OuterXml).ToString()
<configuration xmlns="urn:newrelic-config" agentEnabled="true">
<service licenseKey="**redacted**" />
<application>
<name>My Application</name>
</application>
<log level="info" />
<transactionTracer enabled="true" transactionThreshold="apdex_f" stackTraceThreshold="500" recordSql="obfuscated" explainEnabled="false" explainThreshold="500" />
<crossApplicationTracer enabled="true" />
<errorCollector enabled="true">
<ignoreErrors>
<exception>System.IO.FileNotFoundException</exception>
<exception>System.Threading.ThreadAbortException</exception>
</ignoreErrors>
<ignoreStatusCodes>
<code>401</code>
<code>404</code>
</ignoreStatusCodes>
</errorCollector>
<browserMonitoring autoInstrument="true" />
<threadProfiling>
<ignoreMethod>System.Threading.WaitHandle:InternalWaitOne</ignoreMethod>
<ignoreMethod>System.Threading.WaitHandle:WaitAny</ignoreMethod>
</threadProfiling>
<instrumentation xmlns="">
<applications>
<application name="MyApplication.exe" />
</applications>
</instrumentation>
</configuration>
CodePudding user response:
This function appends a string
xml fragment as a child of an xmlelement
:
function AppendChildXml {
param (
[System.Xml.XmlElement]$element,
[string]$fragment
)
[System.Xml.XmlDocumentFragment]$fragxml = $element.OwnerDocument.CreateDocumentFragment();
$fragxml.innerXml = $fragment;
$element.AppendChild($fragxml);
}
Since the function has an XmlElement
as its first parameter, this makes it easy to mix with the "PowerShell way" of referring to deep elements with properties, e.g. $xmldoc.configuration.errorCollector.ignoreErrors
For example:
# read in the existing newrelic.config
[xml]$newRelicConfigXml = [xml]::new();
$newRelicConfigXml.Load((Convert-Path $newRelicConfigPath));
# instrumentation section to be added
[string]$fragment = '<instrumentation><applications><application name="MyApplication.exe"/></applications></instrumentation>'
# add instrumentation under configuration
AppendChildXml $newRelicConfigXml.configuration $fragment