1

I expected the following script blocks to execute and return numbers of type byte and int but everything inside quotation marks becomes just a string instead of a script.

$a=(&{Param($t);"[$t]`$num=10;`$num+=2;`$num"} byte)
$a
# OUTPUTS STRING [byte]$num=10;$num+=2;$num
# NOT NUMBER 12

$ty="int"
$b=(&{"[$ty]`$num=5;`$num+=2;`$num"})
$b
# OUTPUTS STRING [int]$num=5;$num+=2;$num
# NOT NUMBER 7

How do I make it work as expected? Thanks

2 Answers 2

1

When you wrap in quotes (see about_quoting_rules) you are creating a string:

"[$t]`$num=10;`$num+=2;`$num"

Because that's what you are assembling, that is what is being returned.


I assume you are trying to cast to a specific type (int/byte):

[$t]`$num=10

You can instead use the LanguagePrimitives.ConvertTo Method as it will do this for you:

[System.Management.Automation.LanguagePrimitives]::ConvertTo(Object, Type)

I would define the script block first, then invoke it separately:

$a={ codehere }
& $a param1 param2

Taking all that onboard:

$a={Param($t,$num);$num=[System.Management.Automation.LanguagePrimitives]::ConvertTo($num, $t);$num+2}
& $a byte 5
Sign up to request clarification or add additional context in comments.

Comments

1

To complement James C.'s helpful answer, which explains the problem well and contains a solution with limitations[1], with alternative solutions:

Script blocks used in isolation are indeed like anonymous functions (and they're also the foundation of scripts and named functions).

While a cast / type-constraint with a non-literal type name isn't possible, you can use the -as operator to similar effect:

PS> & { param($t) $num = 10; $num+=2; $num -as $t } byte # !! use -as $t on OUTPUT
12  # type is [byte]

Caveats:

  • Unlike PowerShell casts, the -as operator is culture-sensitive - for certain types, but not others: e.g., it is culture-sensitive for [datetime], but not for [double]

  • If conversion isn't possible, -as quietly returns $null, so you may have to handle that case explicitly.

  • Because -as cannot be used to type-constrain a variable, subsequent operations can change the value's data type.

    • This is indeed what would happen if you did $num = 10 -as [byte] followed by $num += 2: The addition would implicitly change the type to [int].
    • Therefore, apply the -as operator on output, or, if necessary, on each intermediate operation.
      • Beware of compound operators such as +=, where -as won't work as intended; instead of $num += 2 -as [byte], use $num = ($num + 2) -as [byte], for instance.
  • As an aside: if New-Variable supported type-constraining, it would be an alternative, along the lines of New-Variable -Type $t -Name num -Value 10; it currently doesn't (as of Windows PowerShell v5.1 / PowerShell Core 6.1.0), but it may in the future.


Alternatively, create your script block from a string representation with [scriptblock]::Create(), which allows you to "bake" the type name into the string:

PS> & ([scriptblock]::Create('[{0}] $num=10; $num+=2; $num' -f 'byte'))
12  # type is [byte]

Caveats:

  • Only use this techniques on strings whose content you either fully control or trust, given that arbitrary commands can be constructed this way.

  • Additionally, in the interest of maintainability, this technique is only suitable for small pieces of code.


[1] [System.Management.Automation.LanguagePrimitives]::ConvertTo() isn't fully equivalent to a cast, because it doesn't cover "dynamic objects that provide their own conversions."

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.