Reputation: 57202
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?
Upvotes: 36
Views: 12385
Reputation: 15207
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")]
Upvotes: 0
Reputation: 350
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 :-)
Upvotes: 3
Reputation: 201672
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.
Upvotes: 34
Reputation: 57202
I've found this solution:
$something = "World"
$template = Get-Content template.txt
$expanded = Invoke-Expression "`"$template`""
$expanded
Upvotes: 7