Expanding Variable Strings in PowerShell

I was recently working on an automation task that involved opening an XML document, reading the values its contents, and passing them as arguments to install a variety of processes, etc., etc. All rather routine and mundane. Of course, my XML document was littered with environment variables and other special monikers that would be replaced after the file had been loaded. So I reached for my PowerShell editor and started putting together a solution. Then I got to thinking…why don’t I just embed PowerShell variables directly in the file?

Well, why not indeed. The problem was as PowerShell read the file in, it simply ignored my variables and I was stuck with an XML attribute value of something like $Domain\$User. It didn’t help me one whit. Surely there must be some way to convince PowerShell to evaluate that.

As luck would have it, there is! Or I should say, there are! Because it turns out there are multiple ways to do this, none of which are specific to XML (that was just my target data). So, let’s review the options…

1) Invoke-Expression, Invoke-Expression '"$Domain\$User" | Write-Output'

2)  ???

So I initially came up with a rather short list. I’m not proud to admit it but I couldn’t think of anything else at the time. I’ve used the Invoke-Expression methodology before and although it works, it’s kludgy.

Turns out there is a second way. The ExpandString function does exactly what we want. It’s not a cmdlet but rather part of the internal PowerShell engine.

So, you can use $ExecutionContext.InvokeCommand.ExpandString("$Domain\$User") and voila! You’ve expanded your string.

Now before we get too carried away, let’s all acknowledge and understand that both of these approaches suffer from the same drawback: unintended and arbitrary code execution. You can protect against that with restricted runspaces and various other techniques but I’m not going to focus on those today. Suffice to say, it’s been mentioned and you should make sure you trust your input data. Here is a discussion on StackOverflow about restricting runspaces for this exact purpose.

So, is that it? Nope. I tried using this solution only to discover that it removed all quotes from within my string. And since my original data was XML, you can see how that would be bad. I spent a while tinkering with solutions to put quotes back and read the XML but it turns out there is a simpler solution: just escape the quotes before you process the input data.

Here is an example… First we escape double quotes, then we escape single quotes. (This of course can be condensed.. Expanding for your reading pleasure).

$sourceString = $sourceString -replace '"', '`"'
$sourceString = $sourceString -replace '''', '`'''
$ExecutionContext.InvokeCommand.ExpandString($sourceString)

But wait, there’s more! After conferring with a colleague of mine about this, he politely inquired as to why I was bothering with replacing the quotes at all. I showed him the problem I was having with the quotes being removed and he wasn’t able to reproduce the issue. Why not?

Because he was using PowerShell 3.0. Ahhh..,so this is a limitation of PowerShell 2.0? Nope. This is a BUG in PowerShell 2.0. Because the same functionality works fine in PowerShell 1.0. Here is a bug logged on Microsoft Connect to document the issue. Several other people have confirmed this and report that this functionality works fine in other versions. Only the PowerShell 2.0 users (which is still probably most of us) get this special treatment.

As others suffering from this problem have suggested, wrapping this logic in some sort of version-aware PowerShell function is probably the best approach long term since you don’t have to worry about it. I’ve included an example below on a possible way to do this:

if ($PSVersionTable.PSVersion.Major -lt 3)
{
$sourceString = $sourceString -replace '"', '`"'
$sourceString = $sourceString -replace '''', '`'''
}

$ExecutionContext.InvokeCommand.ExpandString($sourceString) 

So, that’s it. Variable expansion within an input text file. By far, the easiest way to accomplish this is using the built-in evaluation engine within PowerShell. You’ll just need to be aware of the pitfalls.

Happy Coding!

About Andrew Bassett

Andrew Bassett is a software engineer with over a dozen years of experience focused primarily in .NET technologies with an extensive background in automation. Andrew spent several years as a system administrator at Marriott International developing techniques for and managing a global deployment of thousands of Windows based servers. Following that, he started doing development on custom Thin Client solutions and BlackBerry administration. At AIS he has been working extensively on developing PowerShell automation for creating SharePoint environments from scratch.