1

I have a xml file which I want to update based on a value from a variable.

I would like to to something like this

$ns = @{ dts = 'www.microsoft.com/SqlServer/Dts' }
$xml2 =  [XML](Get-Content "C:\Users\David\Documents\New.dtsx")

$xml2 | 
Select-Xml -XPath "/dts:Executable/dts:Variables/dts:Variable[@dts:ObjectName = 'BATCH_JOB_ID']/dts:VariableValue"  -Namespace $ns | 
Select-Object -ExpandProperty Node |
Select-Object -ExpandProperty '#text' = "$BATCH_JOB_ID"

That is, I am trying to extract a specific XML element of interest and to update its ObjectName attribute.

Why does this not work?

Another option is to use dot notation, which is what I prefer, but I do not know how to filter the attribute after this point:

$xml2.Executable.Variables.Variable

I have to use the filter @dts:ObjectName = 'BATCH_JOB_ID' I can do like:

$xml2.Executable.Variables.Variable.VariableValue[0].'#text'

But instead of [0] I would like to write ObjectName = 'BATCH_JOB_ID. Is that possible? How can I write something like:

$xml2.Executable.Variables.Variable.VariableValue[BATCH_JOB_ID].'#text'
8
  • 1
    You don't need to include the namespace prefix in the XPath expression, Select-Xml will resolve those for you, so /Executable/Variables/Variable[@ObjectName = 'BATCH_JOB_ID']/VariableValue should do Commented Sep 21, 2020 at 14:19
  • @MathiasR.Jessen what if I want to use to dot notation all the way? Commented Sep 21, 2020 at 14:22
  • @MathiasR.Jessen: Only thing that works without namespace colon is $xml2.Executable.Variables.Variable but how can I filter from here? Commented Sep 21, 2020 at 14:25
  • 2
    Like you'd filter any other object in PowerShell: $BatchIDVariableNodes = $xml2.Executable.Variables.Variable |Where-Object {$_.GetAttribute('ObjectName') -eq 'BATCH_JOB_ID'}; $BatchIDVariableNodes.VariableValue Commented Sep 21, 2020 at 14:34
  • 1
    I tried to make your question a bit clearer re your desire to update the XML document - not sure if I got it right, because now I'm confused about whether you want to update the ObjectName attribute (which my answer is predicated on) or just the contents of the VariableValue element. Commented Sep 21, 2020 at 17:21

1 Answer 1

1
$ns = @{ dts = 'www.microsoft.com/SqlServer/Dts' }
$xml2 = [xml](Get-Content -Raw C:\Users\David\Documents\New.dtsx)
$xpathQuery = "/dts:Executable/dts:Variables/dts:Variable[@dts:ObjectName = 'BATCH_JOB_ID']"

# Use XPath to target the element of interest, as a
# [Microsoft.PowerShell.Commands.SelectXmlInfo] instance whose .Node property is
# the targeted [System.Xml.XmlElement] instance.
# Call .SetAttribute() to update the 'ObjectName' attribute in-place.
(Select-Xml -Xml $xml2 -XPath $xpathQuery -Namespace $ns).
  Node.SetAttribute('ObjectName', $NEW_BATCH_JOB_ID)
}

# Save back to original file.
$xml2.Save("C:\Users\David\Documents\New.dtsx")

However, since you're parsing the XML yourself first anyway, there is no reason to involve
Select-Xml:

$xml2 = [xml](Get-Content -Raw C:\Users\David\Documents\New.dtsx)
$xpathQuery = "/dts:Executable/dts:Variables/dts:Variable[@dts:ObjectName = 'BATCH_JOB_ID']"

# Create the namespace-manager instance.
$nsm = [System.Xml.XmlNamespaceManager]::new($xml2.NameTable)
$nsm.AddNameSpace('dts', 'www.microsoft.com/SqlServer/Dts')

# Locate the element of interest...
$targetEl = $xml2.SelectSingleNode($xpathQuery, $nsm)
# ... and update its attribute.
$targetEl.SetAttribute('ObjectName', $NEW_BATCH_JOB_ID)

# Save back to original file.
$xml2.Save("C:\Users\David\Documents\New.dtsx")

The alternative is to let Select-Xml parse the XML file into a DOM for you, without the need to create an [xml] instance manually first:

$ns = @{ dts = 'www.microsoft.com/SqlServer/Dts' }
$xpathQuery = "/dts:Executable/dts:Variables/dts:Variable[@dts:ObjectName = 'BATCH_JOB_ID']"

# Use XPath to target the element of interest, as a
# [Microsoft.PowerShell.Commands.SelectXmlInfo] instance whose .Node property is
# the targeted [System.Xml.XmlElement] instance.
$targetEl = (Select-Xml -LiteralPath C:\Users\David\Documents\New.dtsx -XPath $xpathQuery -Namespace $ns).Node

# Call .SetAttribute() to update the 'ObjectName' attribute in-place.
$targetEl.SetAttribute('ObjectName', $NEW_BATCH_JOB_ID)

# Save back to original file; the .OwnerDocument property provides
# access to the enclosing document.
$targetEl.OwnerDocument.Save("C:\Users\David\Documents\New.dtsx")
Sign up to request clarification or add additional context in comments.

4 Comments

Great, thanks. What happens if one puts [xml] on the LHS before the variable. I.e like [xml]$xml2 in the first line where I define my variable? Will $xml then be a different object?
@David: Putting a type literal to the left of an assignment (e.g, [xml] $xml = ...) type-constrains the target variable; in short: it locks in that type so that later assignments to the same variable must be of the same type or convertible to it; see this answer for details.
Okay so actually it is not necessary? You write in the beginning that I don't need the 'Select-Xml' because I am parsing the xml myself. Does this happen because I have [xml] on RHS?
If you never assign another value to the variable, [xml] $xml = ... and $xml = [xml] ... behave the same: they both parse a string operand into an XML DOM, type System.Xml.XmlDocument ([xml] is just a short name for it). Once you have such a DOM, you can use its methods directly - no need for Select-Xml. Conversely, using Select-Xml -LiteralPath ... performs this parsing into an XML DOM implicitly, and returns the matching nodes that are a part of this DOM. .Node provides access to the raw matching node, and .Node.OwnerDocument to the entire XML DOM.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.