0

I'm not sure where' I'm going wrong here. I have the following PowerShell error message:

Exception setting "Value": "Cannot set a value on node type
'Document'." At line:18 char:5
+     $XMLDoc.$controlSource='$replaceText'
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : CatchFromBaseAdapterSetValue

My code is the following:

[xml]$XMLDoc = (Get-Content "C:\process.xml")

foreach($file in Get-ChildItem $outputDirectory) {
    $replaceText = <tested working string of the value to replace in the XML doc>
    $controlSource = $XMLDoc.SelectNodes("/beans/bean[@id='$bNode']/property[@name='configOverrideMap']/map/entry[@key='sfdc.extractionSOQL']/@value")

    $XMLDoc.$controlSource = '$replaceText'

    #$XMLDoc -replace ('^"$controlSource"$','^"$replaceText"$') | Out-File $XMLDoc
}

I'm trying to replace a value in an XML file with a string and write the contents of variable $XMLDoc to the file C:\process.xml until I get the above message. I can get the part about writing $XMLDoc to the file, the problem is that I still can't get my string to replace the variable and save. I understand this may be sloppy because I'm doing the same task multiple of times in a foreach loop, but I'm aiming for functionality at the moment as opposed to optimal.

<beans>
  <bean id="MenuGet">
    <property name="name" />
    <property name="MapOverride">
      <map>
        <entry key="node.infoblock" value="k4jk2jb54B$T45bt2j5ktb3B%$" />
      </map>
    </property>
  </bean>
  <bean id="SystemGet">
    <property name="name" />
    <property name="MapOverride">
      <map>
        <entry key="node.infoblock" value="b34t34bhj54b%B#Y$%Bn45ht5h" />
      </map>
    </property>
  </bean>
</beans>
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
murkywaters
  • 95
  • 2
  • 11
  • You should update the question with a short sample of what's in the XML file, what you're doing with `$file`, and how you're getting `$bNode`. You typed `$XMLDoc` as `System.Xml.XmlDocument`, so you shouldn't be using the `-replace` (`[string]`) operator on it. – kuujinbo May 01 '18 at 21:02
  • Sure. It was from a previous thread: https://stackoverflow.com/questions/49862296/is-it-possible-to-define-two-selectsinglenodes-statements-while-searching-for/49862980?noredirect=1#comment86743870_49862980 – murkywaters May 01 '18 at 21:04
  • I have the variables I need. It's just a matter of getting one to replace the other. I tried -replace and -creplace alternatives to no avail. – murkywaters May 01 '18 at 21:05
  • That's what I meant - `$XMLDoc` is typed as `System.Xml.XmlDocument`, so you **cannot** use a `string` operator like `-replace` - that's what's causing your error. To perform the replacement(s), you need to call a `XmlDocument` method or set it's respective property. – kuujinbo May 01 '18 at 21:34
  • I don't have enough information on XmlDocument methods for powershell. Could you point me in the right direction? – murkywaters May 01 '18 at 21:39
  • 1
    `[xml]$XMLDoc | Get-Member` – trebleCode May 01 '18 at 21:45

2 Answers2

1

There are essentally 3 problems with your code:

  1. $XMLDoc.SelectNodes() returns a collection of nodes. You can't use that in a dot-access statement ($XMLDoc.$controlSource), and you don't need to anyway. Just drop the $XMLDoc. and loop over the elements of the collection.

  2. Your XPath expression selects value attribute nodes. However, you can't assign a value directly to the node. You must assign it to its Value or #text property. Note that unlike Value the property #text accepts only values of type string.

  3. The value you want to assign is a single-quoted string, so PowerShell will not expand the variable $replaceText in it. Remove the single quotes.

This will do what you want:

$xpath = "/beans/bean[@id='$bNode']/property[@name='MapOverride']/map/entry[@key='node.infoblock']/@value"
$XMLDoc.SelectNodes($xpath) | ForEach-Object {
    $_.Value = $replaceText
}
Ansgar Wiechers
  • 193,178
  • 25
  • 254
  • 328
  • Thanks Ansgar. I have a lot to learn about PS XML in-general. I'm guessing dot-access is only useful for reading attributes and slash notation is for modifying the attributes. – murkywaters May 02 '18 at 15:51
  • No, in general you can use both dot-access and XPath for reading and modifying things. However, there are some edge cases (like yours) where dot-access doesn't behave like one would expect. XPath yields more predictable results in my experience. – Ansgar Wiechers May 02 '18 at 17:49
1

Had to create sample XML a little different than what's in the question to get a short and simple working example based on your XPath:

[xml]$XMLDoc= @'
<beans>
  <bean id="MenuGet">
    <property name="name" />
    <property name="MapOverride">
      <map>
        <entry key="node.infoblock" value="k4jk2jb54B$T45bt2j5ktb3B%$" />
      </map>
    </property>
  </bean>
  <bean id="SystemGet">
    <property name="name" />
    <property name="MapOverride">
      <map>
        <entry key="node.infoblock" value="b34t34bhj54b%B#Y$%Bn45ht5h" />
      </map>
    </property>
  </bean>
</beans>
'@;

And some simple replacement data to mimic the foreach loop:

$replacements = @{
    MenuGet = 'MenuGet-REPLACE';
    SystemGet = 'SystemGet-REPLACE';
};

And here's some tested/working sample code:

foreach($bNode in $replacements.Keys)
{
    $replaceText= $replacements.$bNode;
    $controlSource = $XMLDoc.SelectSingleNode(
        "/beans/bean[@id='$bNode']/property[@name='MapOverride']/map/entry[@key='node.infoblock']/@value"
    );

    $controlSource.Value = $replaceText;
}  
$XMLDoc.OuterXml;
# $XMLDoc.Save($OUT_PATH);
kuujinbo
  • 9,272
  • 3
  • 44
  • 57
  • Thanks kuujinbo. It's hard to entirely use XPath because XPath doesn't take into account the level of depth that some XML documents get into. It's a bit confusing because it also requires multiple nested for loops to do the heavy backlifting and I'm thinking this isn't where Powershell works at its best. – murkywaters May 02 '18 at 15:55
  • @murkywaters - Actually XPath does allow you to take into account multiple levels of depth to a certain extent. For example, in the above code sample I posted, you can replace that XPath selector with `//bean[@id='$bNode']//entry[@key='node.infoblock']/@value` and get *exactly** the same results. Do a bit of research on _XPath axes_, and you'll see how powerful XPATH can be. :) A general cheatsheet can be found here: https://devhints.io/xpath – kuujinbo May 02 '18 at 16:46