Why is PowerShell telling me a String is not a String? And only when calling String.Length, first

powershell

Solution

This is an unfortunate bug in the V2 extended type system where a `psobject` wrapper can get created around a base .NET Type. This can happen when you add members to an object or when you access a property that doesn't exist. It can also happen when you access the `psobject` property on an object IIRC e.g. `$newId.psobject`. When you stay within PowerShell this usually doesn't cause any problems.

Update: This isn't a problem calling out to .NET. Some quick .NET test code shows that it gets an unwrapped object for a property setter. After looking at this with `Trace-Command` it appears to be a bug in PowerShell's `XmlNodeAdapater`:

DEBUG: ETS Information: 0 :  Method      Enter PSObject..ctor():object = System.Management.Automation.RuntimeException:
 Cannot set "Id" because only strings can be used as values to set XmlNode properties. --->
System.Management.Automation.SetValueException: Cannot set "Id" because only strings can be used as values to set
XmlNode properties.
   at System.Management.Automation.XmlNodeAdapter.PropertySet(PSProperty property, Object setValue, Boolean
convertIfPossible)
   at System.Management.Automation.Adapter.BasePropertySet(PSProperty property, Object setValue, Boolean convert)
   at System.Management.Automation.PSProperty.SetAdaptedValue(Object setValue, Boolean shouldConvert)
   at System.Management.Automation.PSProperty.set_Value(Object value)
   at System.Management.Automation.PropertyReferenceNode.SetValue(PSObject obj, Object property, Object value,
ExecutionContext context)
   --- End of inner exception stack trace ---
   at System.Management.Automation.PropertyReferenceNode.SetValue(PSObject obj, Object property, Object value,
ExecutionContext context)
   at System.Management.Automation.AssignablePropertyReference.SetValue(Object value, ExecutionContext context)
   at System.Management.Automation.AssignmentStatementNode.Execute(Array input, Pipe outputPipe, ExecutionContext
context)
   at System.Management.Automation.StatementListNode.ExecuteStatement(ParseTreeNode statement, Array input, Pipe
outputPipe, ArrayList& resultList, ExecutionContext context)

One way to ensure you always get the `base` .NET object is:

$xmlDoc.root.ComponentRef.Id = $newId.psobject.baseobject

Good news is that this issue is fixed in V3 e.g.:

PS> $xmlDoc = [xml]@"
<root>
  <ComponentRef Id="a" />
</root>
"@

PS> $newId = "b"

PS> $newId.Length
1

PS> $newId.psobject.typenames
System.String
System.Object

PS> $xmlDoc.root.ComponentRef.Id = $newId

PS> $xmlDoc | format-xml # From PowerShell Community Extensions
<root>
  <ComponentRef Id="b" />
</root>

Problem

I'm setting an XML attribute with a String, and PowerShell tells me "only strings can be used as values to set XmlNode properties". Here's a simple example. First, I run this: ``` $xmlDoc = [xml]@" <root> <ComponentRef Id="a" /> </root> "@ $newId = "b" $length = $newId.Length Write-Host ("`n`$newId is a string, see: `$newId.GetType().FullName = " + $newId.GetType().FullName + "`n") Write-Host ("Running `"`$xmlDoc.root.ComponentRef.Id = `$newId`"...`n") $xmlDoc.root.ComponentRef.Id = $newId Write-Host ("ComponentRef.Id is now: " + $xmlDoc.root.ComponentRef.Id) ``` For me, the output is: ``` $newId is a string, see: $newId.GetType().FullName = System.String Running "$xmlDoc.root.ComponentRef.Id = $newId"... Cannot set "Id" because only strings can be used as values to set XmlNode properties. At D:\Build\Tools\mass processing\Untitled4.ps1:14 char:27 + $xmlDoc.root.ComponentRef. <<<< Id = $newId + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : PropertyAssignmentException ComponentRef.Id is now: a ``` That error message has got to be wrong. The value on the right side of the equals sign is a String, as shown in the output above. But it errored, so the XML attribute still reads "a". Now it gets weirder. Let's comment out the line that calls $newId.length, and watch it work correctly. Commenting out as such: `#$length = $newId.Length`. The output is now: ``` $newId is a string, see: $newId.GetType().FullName = System.String Running "$xmlDoc.root.ComponentRef.Id = $newId"... ComponentRef.Id is now: b ``` I'm not asking for a fix, because I know how to work around this issue by casting to [string] on the right side of the last assignment operator. What I'd like to know is: Can anyone explain why calling $newId.Length (a getter!) could cause PowerShell to think $newId is no longer a string? Thanks!

Original source