37

I have a file template.txt which contains the following:

Hello ${something}

I would like to create a PowerShell script that reads the file and expands the variables in the template, i.e.

$something = "World"
$template = Get-Content template.txt
# replace $something in template file with current value
# of variable in script -> get Hello World

How could I do this?

3
  • 2
    Great question! I just needed to figure out how to do the same thing. Commented Jul 18, 2011 at 22:08
  • 2
    Just to reiterate a comment on an answer below, I do not know of a way to do this (variable expansion) that does not allow arbitrary expressions to be executed. I gave up on powershell variables and used environment variables with [System.Environment]::ExpandEnvironmentVariables() instead. Any safe solution using powershell variables would be very interesting! Commented Dec 19, 2011 at 8:22
  • I have a tool that uses ExpandString on a templpate. But before invoking ExpandString, it defines powershell variables using actual values pulled from a csv file. Commented Feb 26, 2022 at 12:10

4 Answers 4

34

Another option is to use ExpandString() e.g.:

$expanded = $ExecutionContext.InvokeCommand.ExpandString($template)

Invoke-Expression will also work. However be careful. Both of these options are capable of executing arbitrary code e.g.:

# Contents of file template.txt
"EvilString";$(remove-item -whatif c:\ -r -force -confirm:$false -ea 0)

$template = gc template.txt
iex $template # could result in a bad day

If you want to have a "safe" string eval without the potential to accidentally run code then you can combine PowerShell jobs and restricted runspaces to do just that e.g.:

PS> $InitSB = {$ExecutionContext.SessionState.Applications.Clear(); $ExecutionContext.SessionState.Scripts.Clear(); Get-Command | %{$_.Visibility = 'Private'}}
PS> $SafeStringEvalSB = {param($str) $str}
PS> $job = Start-Job -Init $InitSB -ScriptBlock $SafeStringEvalSB -ArgumentList '$foo (Notepad.exe) bar'
PS> Wait-Job $job > $null
PS> Receive-Job $job
$foo (Notepad.exe) bar

Now if you attempt to use an expression in the string that uses a cmdlet, this will not execute the command:

PS> $job = Start-Job -Init $InitSB -ScriptBlock $SafeStringEvalSB -ArgumentList '$foo $(Start-Process Notepad.exe) bar'
PS> Wait-Job $job > $null
PS> Receive-Job $job
$foo $(Start-Process Notepad.exe) bar

If you would like to see a failure if a command is attempted, then use $ExecutionContext.InvokeCommand.ExpandString to expand the $str parameter.

Sign up to request clarification or add additional context in comments.

4 Comments

Sorry if I'm misunderstanding something, but as far as I can tell ExpandString() is not safer at all... It still does expression expansion, which means in your example, specifically, everything in C:\ still gets deleted! Did you mean to indicate that even with ExpandString() arbitrary code will also be executed?
(in the example above, $ExecutionContext.InvokeCommand.ExpandString($template) results in an equally bad day!)
@Tao, yep you're write on the lack of safety with ExpandString. See the updated answer for a better approach.
cool, I have to play with this, looks great! Thanks for the update!
7

I've found this solution:

$something = "World"
$template = Get-Content template.txt
$expanded = Invoke-Expression "`"$template`""
$expanded

Comments

4

Since I really don't like the idea of One More Thing To Remember - in this case, remembering that PS will evaluate variables and run any commands included in the template - I found another way to do this.

Instead of variables in template file, make up your own tokens - if you're not processing HTML, you can use e.g. <variable>, like so:

Hello <something>

Basically use any token that will be unique.

Then in your PS script, use:

$something = "World"
$template = Get-Content template.txt -Raw
# replace <something> in template file with current value
# of variable in script -> get Hello World    
$template=$template.Replace("<something>",$something)

It's more cumbersome than straight-up InvokeCommand, but it's clearer than setting up limited execution environment just to avoid a security risk when processing simple template. YMMV depending on requirements :-)

Comments

0

Here it is my take on the task. It makes use Invoke-Expression, like @PaoloTedesco's answer, but uses string interpolation in multi-line strings and also don't trim newlines in the output. The script assumes Version.cs.in is the template file, that should be expanded to Version.cs:

$VERSION="1.0.0"
$versioncs = Get-Content -Path "Version.cs.in"
$versioncs = $versioncs | %{ Invoke-Expression "`@`"`n$_`n`"`@" }
Set-Content -Path "Version.cs" -value $versioncs

I fed it with the following template:

using System.Reflection;
[assembly: AssemblyVersion("$($VERSION)")]

And I correctly get:

using System.Reflection;
[assembly: AssemblyVersion("1.0.0")]

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.