Tome's Land of IT

IT Notes from the Powertoe – Tome Tanasovski

Dynamic Code in Powershell

The snow hit NYC today.  Actually, it’s really not that bad out, but people seem to panic whenever our friendly blistering dust of fluffy ice crystals precipitates our way.  Other states would laugh at what New York takes seriously.  Fortunately, however, the panic caused my company to give me my first snow day since college!  With our Northeast offices completely closed I had time to sit down and script without interruption for the first time in a long time.  While there are more important projects that I could have chosen to work on today I couldn’t help but get swayed by the VMware 2500 dollar prize for their Scriptomania contest.  I decided to sit down and get some of the VM scripts I’ve been meaning to work on done.

While I can’t yet write about my super-top-secret-prize-winning script I can talk about one of the techniques I wound up having to use that I think is pretty neat.  Part of one of my projects included the need to either execute a script or generate the same script for later use.  I realized that writing functions for both would be a complete waste of time since I would be duplicating my effort.  Instead I needed to find a way to create a script on the fly and either execute it or dump it to a file later.  Sure, I could have cheated and just written directly to a buffer file the entire time, but that solution doesn’t seem elegant enough for me.

If you’re not aware of the & (ampersand) in Powershell it is a way of executing a string. i.e.

$script = "Get-ChildItem"
& $script

The ampersand will cause the contents of the string to be run.  In the above you will get a listing of your current PS working directory.  If you try to use this with multiple lines, however, you will get errors like this:

Fortunately you can also use brackets {} to set a variable to a block of code and execute the contents of that variable with an ampersand (&):

$script = {
    ls
    echo blah
}
& $script

So the next question I had was how can I continue to build my scriptblock without closing the bracket.  The problem is that I needed to insert different snippits of code and different parameters based off of other factors.  With a string I could do something like:

if ($value -eq $true) {
    $script += "ls *.bak`n"
} else {
    $script += "ls *.trn`n"
}
& $script

The answer I came up with is that you cannot use this technique with a variable that is set to a script block.  I quickly realized another way to do this.  I could store each code snippet as an element in an array of script blocks.  At the very end when I’m ready to execute this code I just need to iterate through the array and execute each in turn or if I want to dump it to a file I can do it by passing the array to out-file:

$scriptsnippets = @()
if ($value -eq $true) {
    $scripsnippets += {
        ls *.bak
     }
} else {
    $scriptsnippets += {
        ls *.trn
    }
}
if ($execute -eq $true) {
    $scriptsnippets |foreach {& $_}
}
if ($dumptofile -eq $true) {
    $scriptsnippets |outfile generatedscript.ps1
}

A very nice side effect to the above is that because you are using brackets you can indent the lines following the opening bracket to make your code fit the style of a script.  This makes it very easy to read and work with as you are developing your script that creates a dynamic script.

The final piece to the puzzle was figuring out how to create code blocks that have the value of a variable.  In other words if I know a server name is “computer 1” I need to ensure that the name “computer1” is in the final script.  At first I thought this was very easy.  Within the array I could use both script blocks and strings.  Any time I need to pass a variable I do it with a string while multi-line code is entered with a script block in brackets {}.

$scriptobjects = @()
#Here we add a script block
$scriptobjects += {
    $blah = "blah"
    echo $blah
}
#Here we add a string that takes the value of a variable
$command = "Get-ChildItem"
$scriptobjects += "$command"
#We execute both the scriptblock and the string
$scriptobjects |foreach {& $_}

The above works fine except for the fact that the ampersand (&) doesn’t execute the whole command if you have parameters… ugh…

I found it would execute positional parameters like this:

& "Get-ChildItem" "*.txt"

However trying to do the above with non-positional parameters like -Exclude would error.  In the end I found my answer here.  It winds up that there is a function just for this purpose.  It allows you to create a script block dynamically.  Here is how you do it:

$scriptsegments = @()
$filetoexclude = "test.txt"
$scriptsegments += $ExecutionContext.InvokeCommand.NewScriptBlock("Get-ChildItem *.txt -exclude $filetoexclude")
$scriptsegments|foreach {& $_}

Snow days used to mean stealing dad’s homemade wine and charging down a hill face first on a piece of plastic, metal, or wood.  Now it consists of getting some lingering work done while learning something along the way.  Hmm…  What does that mean?  I think it means I should have had some wine while I was scripting today…. gotta go get some so I can finish up my work!

***UPDATE 2/16

It’s another snowy day so it’s another opportunity to learn something new. I have finally finished my entry into the contest, but I ran into one final snag before I was able to get my script out the door. I noticed when executing my array of script blocks I ran into a major problem.  The context of the variables remains with each individual script block.  This can be seen by running the following:

$scriptblocks = @()
$scriptblocks += {$test = $false}
$scriptblocks += {Write-host $test}
$scriptblocks |foreach {& $_}

While you could perform some variable trickery to ensure that the variable gets set in the current session I thought it was cleaner to handle this problem by rebuilding the scriptblock.  The following is the method I used to recompile my script blocks into a single block, and then execute that new block:

$finalscript = ""
$scriptsegments |foreach {
        $finalscript += $_.ToString() + "`n"                        
}
$finalscript = $ExecutionContext.InvokeCommand.NewScriptBlock($finalscript)
& $finalscript

3 responses to “Dynamic Code in Powershell

  1. Pingback: New-SimpleForm – Holy dynamic GUI Batman! « Tome's Land of IT

  2. SHOWRZENEGGER April 25, 2014 at 3:38 am

    You’re a genius man, thanks alot

  3. Peter Eglintine November 12, 2014 at 3:54 am

    Thanks for that got my script working!!

Leave a comment