1

I'm looking at writing some PowerShell code that can either execute immediately, or produce the commands it would execute as generated scripts.

I'd like to avoid this scenario:

if($Generating){
  write-Output "somecommand.exe"
} 
else{
  somecommand.exe  
}

I got looking at ScriptBlocks, which at first looked promising because I can write the contents of the ScriptBlock to the console without executing it. Such as:

$sc = { somecommand.exe }
$sc
 somecommand.exe

My specific question is, if my scriptblock contains parameters, can I get them to resolve when I'm writing the scriptblock contents to the console, but WITHOUT invoking the scriptblock?

For example given the following scriptblock:

$b2 = { Param([string]$P) Write-Host "$P" }

When I just type "$b2" at the console and hit enter I see this:

Param([string]$P) Write-Host "$P"

What I'd like to see is this (if the parameter value is "Foo"):

Param([string]$P) Write-Host "Foo"

I realize this can be done when it's invoked, either via "&" or using Invoke(), but would there be any way to get the parameters to resolve without invoking to make my script generation a little more elegant without needing a bunch of conditional statements throughout the code?

5 Answers 5

3

In PowerShell v3, you can get the param info via the AST property e.g.:

PS> $sb = {param($a,$b) "a is $a b is $b"}
PS> $sb.Ast.ParamBlock

Attributes           Parameters          Extent              Parent
----------           ----------          ------              ------
{}                   {$a, $b}            param($a,$b)        {param($a,$b) "a...
Sign up to request clarification or add additional context in comments.

Comments

2

Solution suitable for PowerShell v2:

# given the script block
$b2 = { Param([string]$P) Write-Host "$P" }

# make a function of it and "install" in the current scope
Invoke-Expression "function tmp {$b2}"

# get the function and its parameters
(Get-Command tmp).Parameters

Comments

1

When displaying a here-string with double quotes @" , it expands the variables. For the variables that should'nt expand, escape the variable with a backtick ( ` ).

So try this:

$P = "Foo"

$b2 = @"
{ Param([string]`$P) Write-Host "$P" }
"@

Test:

PS-ADMIN > $b2
{ Param([string]$P) Write-Host "Foo" }

If you want to convert it to scriptblock-type again:

#Convert it into scriptblock
$b3 = [Scriptblock]::Create($b2)

PS-ADMIN > $b3
{ Param([string]$P) Write-Host "Foo" }

PS-ADMIN > $b3.GetType().name
ScriptBlock

2 Comments

The problem is that $b2 is of type [string] and not [scriptblock], it seem cast doesn't works. $c = [scriptblock]::create($b2) and use $c
He just said he wanted the output, not that it should be scriptblock, if so he can use $b3 = [Scriptblock]::Create($b2). including it in answer now
1

Using some of the suggestions I think I've found the best solution for my needs. Consider the following code

function TestFunc
{
    Param(
        [Parameter(Mandatory=$true)]
        [string]$Folder,

        [Parameter(Mandatory=$true)]
        [string]$Foo
    )    

    $code = @"
Write-Host "This is a folder $Folder"
Write-Host "This is the value of Foo $Foo"
"@

    $block = [Scriptblock]::Create($code)

    Write-Host "Running the block"  -BackgroundColor Green -ForegroundColor Black
    &$block

    Write-Host "Displaying block code" -BackgroundColor Green -ForegroundColor Black
    $block

}

And it's output:

Running the block
This is a folder c:\some\folder
This is the value of Foo FOOFOO
Displaying block code
Write-Host "This is a folder c:\some\folder"
Write-Host "This is the value of Foo FOOFOO"

By doing it this way, I still get all the benefit of keeping my existing functions and their parameters, parameter validation, CBH etc. I can also easily generate the code that the function would execute or just let it execute. Thanks for all the input, it's definitely been a good learning experience.

1 Comment

It was an interesting question, too. Just in case, you can accept your own answer.
0

If you want to express your block as a block, not a string, the following works:

$printable = invoke-expression ('"' + ($block -replace '"', '`"') + '"')

Essentially, you're wrapping everything in quotes and then invoking it as an expression. The -replace call ensures any quotes in the block itself are escaped.

I'm using this in this handy function, which also halts execution if the invoked command failed.

# usage: exec { dir $myDir }
function exec($block)
{
    # expand variables in block so it's easier to see what we're doing
    $printable = invoke-expression ('"' + ($block -replace '"', '`"').Trim() + '"')
    write-host "# $printable" -foregroundcolor gray

    & $block
    if ($lastExitCode -ne 0)
    {
        throw "Command failed: $printable in $(pwd) returned $lastExitCode"
    }
}

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.