1

I have the following variable $Obj set to the following string value:

$Obj = '@{Version=1; Name=a;}'

How do I convert this value from a string into a custom psobject?

I would like to be able to call

$Obj.Version and get the value 1. Currently this call returns nothing.

Note: Due to how I am retrieving this variable, I can't initialize it without the single quotes.

Edit:

Here is the current code:

$Command = "script.ps1 -ExtraInfo $_"
Write-Host $Command
Invoke-Expression -Command $Command

where $_ is @{Version=1; Name=a;} (without the quote)

Originally this code was written as

. script.ps1 -ExtraInfo $_

and worked, but when I added unit tests I changed it to use Invoke-Expression so that it could be testable with Pester unit tests. Is there a better way?

Edit2:

Turns out this can be solved by putting a back tic ` before the expression and that solves the issue for me. Thank you everyone for your input.

$Command = "script.ps1 -ExtraInfo `$_"
Write-Host $Command
Invoke-Expression -Command $Command
satoukum
  • 1,188
  • 1
  • 21
  • 31
  • Please show us how `$Obj` gets generated and why you can't initialize it without single quotes. – Santiago Squarzon Nov 29 '21 at 17:13
  • This is indeed a strange situation that I can't help but believe it can be solved earlier in the code base – PowerShellGuy Nov 29 '21 at 17:15
  • Edited my question to show more information. Thank you for your comments so far. – satoukum Nov 29 '21 at 17:38
  • Not sure how pester works, never bothered on it's use, but `. script.ps1 -ExtraInfo $_` was looking far better than `iex`. – Santiago Squarzon Nov 29 '21 at 17:51
  • When using Pester the issue with `. script.ps1 -ExtraInfo $_` is that it can't be mocked and I don't want the test running this each time. – satoukum Nov 29 '21 at 17:54
  • Why does it fail when using that syntax, what error message do you get? – Santiago Squarzon Nov 29 '21 at 17:57
  • When I execute `$Obj.Version` from `script.ps1` I get an empty value instead of the actual value. – satoukum Nov 29 '21 at 18:02
  • We already know that, I'm trying to understand why does Pester not like this syntax: `. script.ps1 -ExtraInfo $_` and what error it gives. – Santiago Squarzon Nov 29 '21 at 18:06
  • Pester will not let me mock this call, so when I run my unit tests I end up running production code in my unit test, which causes lots of issues. I need to be able to not run this script in my tests. – satoukum Nov 29 '21 at 18:10
  • 1
    Someone with experience on Pester might be able to give you an alternative to what you're doing, imo there __should not__ be a need to use `iex` in any case (unless very niche). If you want to get a "coding best practice" answer I would recommend you to add your Pester code as well as minimal example of `script.ps1` (at least it's `param(...)` block). In addition, add the [tag:pester] tag to your question. – Santiago Squarzon Nov 29 '21 at 18:31

3 Answers3

1

The stringified version of a [pscustomobject] instance, which resembles a hashtable literal, is not suitable for programmatic processing, as the following example demonstrates:

# Stringify a [pscustomobject] instance.
PS> "$([pscsutomobject] @{ Version=1; Name='a value' })" 

@{Version=1; Name=a value}  # !! Quoting of the .Name value was lost

The problem gets worse for property values that are themselves complex objects.


Since you do appear to have access to the original [pscustomobject] instance, the solution is not to stringify.

For that, you simply need to avoid up-front string interpolation by using a verbatim (single-quoted) string literal ('...') and letting Invoke-Expression - which should generally be avoided - interpret the $_ variable as its original type:

# Use *single* quotes to prevent up-front expansion.
$Command = 'script.ps1 -ExtraInfo $_'
Write-Host $Command
Invoke-Expression -Command $Command

Note that the use of a verbatim (non-interpolating) string literal makes the use of Invoke-Expression safe here, though, as Santiago Squarzon points out, there may be a better alternatives in general, and particularly in the context of Pester.

A script-block-based solution ({ ... }) that receives the object as an argument:

$Command = { script.ps1 -ExtraInfo $args[0] }
Write-Host "Calling { $Command } with argument $_"
. $Command $_
mklement0
  • 382,024
  • 64
  • 607
  • 775
0

This doesn't work with Name=a because a is not a known object (or at least not defined in my PS Session). But if this is a string, this can be done with the following script:

$Obj = '@{Version=1; Name="a";}'
$s= [System.Management.Automation.ScriptBlock]::Create("New-Object -TypeName PSObject -Property $Obj")
$o = Invoke-Command -ScriptBlock $s
$o.Version
Hazrelle
  • 758
  • 5
  • 9
-1

As I stated in my comment, this is odd, and should be resolved earlier in the code base. However, if that is not possible, use Invoke-Expression

like so

$newObj = Invoke-Expression $Obj

Further reading on Invoke-Expression

PowerShellGuy
  • 733
  • 2
  • 8
  • If `$Obj` is a string with content `'@{Version=1; Name=a;}'`, your `Invoke-Expression` call fails, because (a) it'll try to parse the string as a _hashtable literal_, not a `[pscustomobject]`, and (b), more importantly, `a` is interpreted as a _command to execute_, not a string. To put it differently: You cannot robustly re-create a `[pscustomobject]` instance from its stringified representation. Also, using `Invoke-Expression` this way is a security risk. – mklement0 Nov 29 '21 at 19:37