Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand string without Invoke-Expression

Imagine the following code:

# Script Start
$WelcomeMessage = "Hello $UserName, today is $($Date.DayOfWeek)"

..
..
# 100 lines of other functions and what not...
..

function Greet-User
{
    $Username = Get-UserNameFromSomewhereFancy
    $Date = Get-DateFromSomewhereFancy

    $WelcomeMessage
}

This is a very basic example, but what it tries to show is a script where there is a $WelcomeMessage that the person running the script can set at the top of the script and controls how/what the message displayed is.

First thing's first: why do something like this? Well, if you're passing your script around to multiple people, they might want different messages. Maybe they don't like $($Date.DayOfWeek) and want to get the full date. Maybe they don't want to show the username, whatever.

Second, why put it at the top of the script? Simplicity. If you have 1000 lines in your script and messages like these spread all over the script, it makes it a nightmare for people to find and change these messages. We already do that for static messages, in the form of localized strings and stuff, so this is nothing new, except for the variable parts in it.

So, now to the issue. If you run that code and invoke Greet-User (assuming the functions/cmdlets for retrieving username and date actually exist and return something proper...) Greet-User will always return Hello , today is.

This is because the string is expanded when you declare it, at the top of the script, when neither $UserName nor $Date objects have a value.

A potential workaround would be to create the strings with single quotes, and use Invoke-Expression to expand them. But because of the spaces, that gets a bit messy. I.e.:

$WelcomeMessage = 'Hello $env:USERNAME'
Invoke-Expression $WelcomeMessage

This throws an error because of the space, to get it to work properly it would have to be declared as such:

$WelcomeMessage = 'Hello $env:USERNAME'
$InvokeExpression = "`"$WelcomeMessage`""

Messy...

Also, there's another problem in the form of code injection. Since we're allowing the user to write their own welcome message with no bounds specified, what's to prevent them from putting in something like...

$WelcomeMessage 'Hello $([void] (Remove-Item C:\Windows -Force -Recurse))'

(Yes, I know this will not delete everything but it is an example)

Granted this is a script and if they can modify that string they can also modify everything else on the script, but whereas the example I gave was someone maliciously taking advantage of the nature of the script, it can also happen that someone accidentally puts something in the string that ends up having unwanted consequences.

So... there's got to be a better way without the use of Invoke-Expression, I just can't quite thing of one so help would be appreciated :)

like image 364
JohnUbuntu Avatar asked Jan 07 '23 23:01

JohnUbuntu


2 Answers

Embedding variables into strings is not the only way to create dynamic text, the way I would do it is like this:

$WelcomeMessage = 'Hello {0}, today is {1}'

# 100 lines of other functions and what not...

function Greet-User
{
    $Username = Get-UserNameFromSomewhereFancy
    $Date = Get-DateFromSomewhereFancy

    $WelcomeMessage -f $Username, $Date
}
like image 63
Dave Sexton Avatar answered Jan 10 '23 11:01

Dave Sexton


The canonical way to delay evaluation of expressions/variables in strings is to define them as single-quoted strings and use $ExecutionContext.InvokeCommand.ExpandString() later on.

Demonstration:

PS C:\> $s = '$env:COMPUTERNAME'
PS C:\> $s
$env:COMPUTERNAME
PS C:\> $ExecutionContext.InvokeCommand.ExpandString($s)
FOO

Applied to your sample code:

$WelcomeMessage = 'Hello $UserName, today is $($Date.DayOfWeek)'

...
...
...

function Greet-User {
  $Username = Get-UserNameFromSomewhereFancy
  $Date = Get-DateFromSomewhereFancy

  $ExecutionContext.InvokeCommand.ExpandString($WelcomeMessage)
}
like image 35
Ansgar Wiechers Avatar answered Jan 10 '23 13:01

Ansgar Wiechers