Chicanery! .foreach{} operator causing [XElement] obj to change output behavior?

Welcome Forums General PowerShell Q&A Chicanery! .foreach{} operator causing [XElement] obj to change output behavior?

Viewing 4 reply threads
  • Author
    Posts
    • #236656
      Participant
      Topics: 6
      Replies: 15
      Points: 141
      Rank: Participant

      I’m attempting to build an array of [XElement] objects as demonstrated in the .Net docs, and add them to an XMLTree using LINQ.

      I’m a .foreach{} loop person, and being a for each loop person the way to do this seemed obvious:

      Boom, an array. (Typecast to an Array, otherwise, it’s created as a generic collection)

      All that was left was to add it to the XML tree

      Output:

      Malformatted 🙁

      After a bunch of testing and dismay, I realized it was passing the Array as a string to the [XElement] constructor, which results in the encoding of the brackets. You can test this by using [XElement]::Parse() on the first index of the Array, and it outputs in the well-formatted and correct manner.

      This is only an issue when using the .foreach() operator.

      Any other variation of foreach does not have the issue:

      Output:

      BUT….

      If the array is instantiated first, and then each individual [XElement] obj is added to the array using += operator. It works as expected.

      Output:

      The only thing that looks different is that if you look at the BaseObject property of the psobject. You will see the following:

      String representation of [XElement] obj

      Node Name of [XElement] obj

      What is causing this behavior?

      Please note that this behavior happens before the array of [XElements] are added to the Root XMLTree in the examples.

      • This topic was modified 3 months ago by Phatmandrake.
      • This topic was modified 3 months ago by Phatmandrake.
      • This topic was modified 3 months ago by Phatmandrake. Reason: Shpelling
    • #236719
      Participant
      Topics: 7
      Replies: 557
      Points: 2,121
      Helping Hand
      Rank: Community Hero

      That’s weird. I went as far as to doing sanity checks after making your code cleaner.

      They seem to be identical except the baseobject like you showed…

      But when you call it like this, they again look identical.

      Which brings me to my point, I have no idea why the heck this is happening nor have I found a fix. When doing the parse there is a “saveoptions” parameter you can use with tostring() but that didn’t have any effect on this example. I assume we will have to dig in the source code to find the differences in the way the foreach method wraps things up (or just as likely, unwraps?)

      I would skip the method in this case as there are several other ways to do this that you’ve found works. I’ve had other weird issues with using nested foreach methods calls that I didn’t have time to figure out. Interesting find none-the-less, thanks for sharing. Maybe someone else is typing up an explanation at this very moment.

      Take care.

    • #236890
      Participant
      Topics: 6
      Replies: 15
      Points: 141
      Rank: Participant

      I stumbled across this: https://stackoverflow.com/questions/20803345/preventing-powershell-from-wrapping-value-types-in-psobjects, which got me thinking:

      So this is an example of a known method for preventing the issue of the text representation of the XElement obj, then using the same method to create another array, and running into the issue, then typing the pipeline variable while creating the array to prevent the issue.

      The same technique fails when using .foreach{} to build the source array.

      I think this demonstrates that a behavior with script blocks is causing the values to unwrap in some way? I dunno I’m butting up against the limits of my knowledge here. It’s like powershell implicitly understand it’s still of the same object type, so it’s displaying the correct properties to console when you print the Array, but when it’s apart of the pipeline it’s interpreting it as text because of something to do with script blocks??

      Edit:

      Look what a script block does:

      The output is also of type [System.Collections.ObjectModel.Collection`1], just like when you use the .foreach{} method. With this I can expand my search to the behaviors of script blocks, which are better documented. I am so close to an answer.

    • #236905
      Participant
      Topics: 6
      Replies: 15
      Points: 141
      Rank: Participant

      At least I’m not crazy.

      • This reply was modified 3 months ago by Phatmandrake.
      • This reply was modified 3 months ago by Phatmandrake. Reason: Hunting for answers
      • This reply was modified 3 months ago by Phatmandrake.
      • This reply was modified 3 months ago by Phatmandrake.
    • #237118
      Participant
      Topics: 6
      Replies: 15
      Points: 141
      Rank: Participant

      I went on a journey for this one.

      TL;DR [XElement[]]$array=(0..2).foreach{[XElement]::new("Name","$_")} Specify the data type of the array using [XElement[]]. To use this with the .foreach() operator.

      Here is what I’ve learned.
      Is that same as this.

      At least in how the object appears when inspecting their members.

      If you assign either to the value of  [XElement](XName,Object[]), they will display malformed XML as demonstrated in the OP.
      However, if you extract each value of the collection, and enforce its type, it will interpret correctly, but this is only true if using ForEach-Object or foreach loop.
      From here I started playing around with the idea that the script block is causing the behavior. I stumbled across:
      This is when I realized
      Creates an array like foreach and ForEach-Object
      Creates a Collection like the .foreach() operator and .invoke()
      Some of this behavior can be explored here: https://powershell.one/tricks/performance/pipeline#how-it-works
      It was here I was the cusp of going ‘Too Deep”, at the very least what I now understood was:
      1. The .foreach() operator behaves like a scriptblock that is executed with the .invoke() method
      2. You cannot force the .foreach() operator to use the .InvokeReturnAsIs() method.
      3. The objects of the collection, when cast individually using ForEach-Object as [XElement] objects work as expected.
      Equipped with this knowledge I determined that the only way to use the .foreach() operator would be to somehow ensure
      the values being assigned to the variable would be passed explicitly as the [XElement] type…the light bulb went off.
      et voila
      It works as expected when you explicitly type the collection to expect [XElement] objects.
      (I actually thought of this a while ago but failed to recognize the [] syntax was necessary for doing this to arrays. :<)

      I do not know what to call the behavior that is causing objects processed using .invoke() on a script block to behave as if they are strings when passed as parameters to a method, but that appears to be the core problem.

      Ensuring that the elements of the collection are treated as their native type makes everything behave correctly. I believe the reason ForeEach-Object and foreach do not have this issue is because of how Powershell is handling the objects. They are assigning the objects as their native types without any ancillary behaviors.

      • This reply was modified 3 months ago by Phatmandrake.
      • This reply was modified 3 months ago by Phatmandrake. Reason: Grammar Level: Atrocious
      • This reply was modified 3 months ago by Phatmandrake.
      • This reply was modified 3 months ago by Phatmandrake.
      • This reply was modified 3 months ago by Phatmandrake.
Viewing 4 reply threads
  • The topic ‘Chicanery! .foreach{} operator causing [XElement] obj to change output behavior?’ is closed to new replies.