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'
mklement0
  • 382,024
  • 64
  • 607
  • 775
xhr489
  • 1,957
  • 13
  • 39
  • 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 – Mathias R. Jessen Sep 21 '20 at 14:19
  • @MathiasR.Jessen what if I want to use to dot notation all the way? – xhr489 Sep 21 '20 at 14:22
  • @MathiasR.Jessen: Only thing that works without namespace colon is `$xml2.Executable.Variables.Variable` but how can I filter from here? – xhr489 Sep 21 '20 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` – Mathias R. Jessen Sep 21 '20 at 14:34
  • @MathiasR.Jessen Thanks but can I use this to assign overwrite the value ? I.e. I want to overwrite the value with some `"BATCH_JOB_ID`. It does not seem that this method with piping will change the original `$xml2`... – xhr489 Sep 21 '20 at 14:49
  • @MathiasR.Jessen: I have to include a `$Xml2.Save('C:\Users\David\Documents\New.dtsx')` to overwrite the file with the update but here we will just update a temporaty variable... – xhr489 Sep 21 '20 at 14:54
  • @MathiasR.Jessen: Well it works thanks. The formating of the file changes a bit with intentations but I don't think that is a problem. How come the first method with pipes all the way and assignment at the end does not work.. – xhr489 Sep 21 '20 at 15:13
  • 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. – mklement0 Sep 21 '20 at 17:21

1 Answers1

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")
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 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? – xhr489 Sep 21 '20 at 17:10
  • 1
    @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](https://stackoverflow.com/a/58936005/45375) for details. – mklement0 Sep 21 '20 at 17:14
  • 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? – xhr489 Sep 21 '20 at 19:11
  • 1
    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. – mklement0 Sep 21 '20 at 19:16