Code Generation with PowerShell

by MattG at 2012-08-21 18:21:53

I’m writing a script where I an generating code for another script. The code is static with the exception of a few variables that need to be replaced dynamically. I could easily store the code in a drawn out string or perform a regex on the generated code but these solutions are really ugly in my opinion. Is there any way to accomplish via a scriptblock?

To put some context around my problem, here’s some example, over-simplified code:

[script=powershell]function Generate-WriteHost () {

$str = 'Hello, World!'

$Code = 'function Out-HelloWorld ()
{
# Do some stuff here
Write-Host '
$Code += $str
$Code += ';
}'

Write-Output $Code

}[/script]
Ideally, I’d like something like the following script. The obvious issue, however is that $str will not be resolved to "Hello, World!" Any cleaner, more elegant solutions to the previous script would be greatly appreciated!

[script=powershell]function Generate-WriteHost () {

$str = 'Hello, World!'

$Code = {
function Out-HelloWorld ()
{
# Do some stuff here
Write-Host $str
}
}

Write-Output $Code

}[/script]
by poshoholic at 2012-08-21 19:12:12
I use here strings. They make this easier. Like this:

[script=powershell]$Code = @"
function Out-HelloWorld
{
# Do some stuff here
Write-Host '$str'
}
"@[/script]
The downsides to this approach are obvious: you don’t get syntax highlighting, if you use double-quotes you need to escape things you don’t want expanded in the string, and escaping can get ugly; plus, perhaps most importantly, you can’t step through the script block in a debugger unless that debugger supports opening in-memory script blocks in new files (some debuggers do this).

Another option is to define it as a script block, but use something other than $str to put your string in place. Like this:

[script=powershell]function Generate-WriteHost () {
$str = 'Hello, World!'
$Code = {
function Out-HelloWorld ()
{{
# Do some stuff here
Write-Host '{0}'
}}
}
$Code -f $str
}[/script]

This works nicely, using the -format operator instead. Note you need to be careful of a few things. Curly braces you want as part of your result script must be doubled so that the formatter knows to replace them with a single curly brace. If you use strings, be careful to put them inside of quotes since failing to do so may break scripts. Here I opted not to use Write-Output, because if I used Write-Output I would have to put the $Code -f $str expression in round brackets so that it is evaluated first and then results are returned, and I didn’t like having to use brackets for that. It’s a decent alternative, and I think using duplicate round braces should work everywhere they apply without generating issues except for maybe hash tables. Yup, just tested, hash tables cause problems for this approach, with PowerShell’s parser generating an error. There are probably other things that cause this approach to trip up.

I think what you really want here is to be able to take a script block that is defined with parameters (because you’re really just using parameters here) and have a method on script blocks that allows you to inline those parameters, generating a new script block with the parameters removed and with their values put inline in the script. That would be useful, although since here strings work, it may not be worth it if it is expensive to implement. If you feel this is important enough, you could log a suggestion on connect.microsoft.com.
by MattG at 2012-08-22 03:23:28
I didn’t realize you could use the format operator on script blocks. That solution is perfect for me. Thanks!
by DougFinke at 2012-08-22 05:59:13
Matt

Also, check out the Invoke-Template from chapter 4 in my book http://goo.gl/TgFK9. I modeled it after approaches found in the Python and Ruby community.

Code Generation is definitely and under utilized sweet spot of PowerShell.

Doug
by poshoholic at 2012-08-22 08:00:48
Hi Matt,

The format operator works on script blocks because script blocks implicitly convert to strings. The implicit conversion is something I have never liked about PowerShell (I’m not against the conversion, I just wish it had to be explicit), but this is one case where it is quite convenient (although if I had to put [string] in front of the script block variable, that wouldn’t have been a big deal either).
by MattG at 2012-08-22 18:04:48
Thanks for the tips, gents. And fortunately, Doug, I have a copy of your book handy. :smiley: