Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does PowerShell silently convert a string to an object[] on testing length?

Tags:

powershell

Trustedhosts pukes on strings longer than 1023 characters, and I've just busted through that limit. I was coding up a nice little FILO queue to make sure I can always add a new host when needed and only omit my least-used servers when I found this oddity. I cured it before I'd lost all my hair, so that's good. What I don't understand is why it's happening.

$namesString = 'server1.there.com,server2.there.com,server3.there.com'
Write-Host ("So, we have a nice little list of servers. Let's trust them.")
Set-Item wsman:localhost\client\trustedhosts -Value $namesString -Force 
Write-Host ("They're nicely trusted now")
if ($namesString.length -gt 1) {
    Write-Host ("Now we know it's longish")
}
Write-Host ("OK. Let's try trusting the same string.")
Write-Host ("(By the way, it's a $($namesString.getType()))")
Write-Host ("(and let's check whether it's an array: $($namesString -is [array]))")
Set-Item wsman:localhost\client\trustedhosts -Value $namesString -Force 
Write-Host ("Why did testing the length of the string cause this confirmed and tested string to report as an object array?")

Results in the following output

So, we have a nice little list of servers. Let's trust them.
They're nicely trusted now
Now we know it's longish
OK. Let's try trusting the same string.
(By the way, it's a string)
(and let's check whether it's an array: False)
Set-Item : Cannot convert 'System.Object[]' to the type 'System.String' required by the parameter. Specified method is not sup
ported.
At C:\Users\...\AppData\Local\Temp\2\07da58ce-2578-4603-8291-83e1cc231522.ps1:11 char:9
+ Set-Item <<<<  wsman:localhost\client\trustedhosts -Value $namesString -Force 
    + CategoryInfo          : NotSpecified: (:) [Set-Item], InvalidOperationException
    + FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.SetItemCommand
Why did testing the length of the string cause this confirmed and tested string to report as an object array?

That oddity was buried in 2000 lines of code and the string in question underwent 3 transformations. At every turn the string reported itself as a string, but wsman rejected it as an array. It took some serious binary searching to finally figure out it was the length test silenty turning into some kind of crypto-array.

like image 698
codepoke Avatar asked Mar 20 '23 13:03

codepoke


2 Answers

In PowerShell V1 and V2, when you get or set a property or invoke a member, PowerShell wrapped the value in a PSObject so that it could use it's internal type adapters to perform the operation.

Creating this PSObject was somewhat expensive, so if your object was a simple variable reference, the PSObject was stored in the variable so that subsequent operations wouldn't pay for the overhead of creating another PSObject.

If this isn't clear, here are some examples:

$value = @{ Property = 1 }    # $value is a hashtable

$value.Property    # $value is now a PSObject wrapping the hashtable

$value.Property    # don't need to create another PSObject

# Under the covers, PowerShell V2 creates a PSObject wrapping the
# string before getting the length, but that PSObject isn't saved anywhere
"hello".Length

PowerShell V3 made significant changes to improve performance and this odd side effect was no longer necessary to get good performance.

like image 132
Jason Shirk Avatar answered Mar 23 '23 03:03

Jason Shirk


I tried in PowerShell v4 and it works correctly there, so it would seem as though the issue which is causing this behaviour has been fixed.

In PowerShell v2, however, I get the same error as you do.

I have no idea why the object gets wrapped when you observe the Length property. I have, however, found a solu... workaround.

If you get the PSObject of the PowerShell string object and then the BaseObject property of that psobject; this will be the string as a string (as opposed to as a wrapped string). This will work both in the case where it's a string in the first case (by forcing a wrap and then unwrapping) and in the case when it's been wrapped. So you could change the set call to:

set-item WSMAN:\localhost\client\TrustedHosts $str.psobject.BaseObject -Force
like image 26
Robert Westerlund Avatar answered Mar 23 '23 03:03

Robert Westerlund