EPS ( Embedded PowerShell ), inspired by ERB, is a templating tool that embeds PowerShell code into a text document. It is conceptually and syntactically similar to ERB for Ruby or Twig for PHP.
EPS can be used to generate any kind of text. The example below illustrates generating plain text, but it could be used to generate HTML as in DS or PowerShell code as in the Forge Module generator.
EPS is available in the PowerShell Gallary. You can install the module with the following command:
Install-Module -Name EPS
EPS allows PowerShell code to be embedded within a pair of <% ... %>
,
<%= ... %>
, or <%# ... %>
as well:
- Code in
<% CODE %>
blocks are executed but no value is inserted.- If started with
<%-
: the preceding indentation is trimmed. - If terminated with
-%>
: the following line break is trimmed.
- If started with
- Code in
<%= EXPRESSION %>
blocks insert the value ofEXPRESSION
.- If terminated with
-%>
: the following line break is trimmed.
- If terminated with
- Text in
<%# ... %>
blocks are treated as comments and are removed from the output.- If terminated with
-%>
: the following line break is trimmed.
- If terminated with
<%%
and%%>
: are replaced respectively by<%
and%>
in the output.
All blocks accept multi-line content as long as it is valid PowerShell.
Invoke-EpsTemplate [-Template <string>] [-Binding <hashtable>] [-Safe] [<CommonParameters>]
Invoke-EpsTemplate [-Path <string>] [-Binding <hashtable>] [-Safe] [<CommonParameters>]
- use
-Template
to render the template in the corresponding string. than a file - use
-Path
to render the template in the corresponding file. -Safe
renders the template in isolated mode (in another thread/powershell instance) to avoid variable pollution (variable that are already in the current scope).- if
-Safe
is provided, you must bind your values using-Binding
option with aHashtable
containing key/value pairs.
A very simple example of EPS would be :
$name = "Dave"
Invoke-EpsTemplate -Template 'Hello <%= $name %>!'
This script produces the following result:
Hello Dave!
In a template file Test.eps
:
Hi <%= $name %>
<%# this is a comment -%>
Please buy me the following items:
<% 1..5 | %{ -%>
- <%= $_ %> pigs ...
<% } -%>
Dave is a <% if($True) { %>boy<% } else { %>girl<% } %>.
Thanks,
Dave
<%= (Get-Date -f yyyy-MM-dd) %>
Then render it in on the command line:
Import-Module EPS
$name = "ABC"
Invoke-EpsTemplate -Path Test.eps
Here it is in non-safe mode (render template with values in current run space)
To use safe mode (render the template in an isolated scope) execute: Invoke-EpsTemplate -Path Test.eps -Safe
with binding values
It will produce:
Hi dave
Please buy me the following items:
- 1 pigs ...
- 2 pigs ...
- 3 pigs ...
- 4 pigs ...
- 5 pigs ...
Dave is a boy.
Thanks,
Dave
2016-12-07
Or you can use safe mode with data bindings:
Invoke-EpsTemplate -Path Test.eps -Safe -binding @{ name = "dave" }
which will generate the same output.
You can use multi-line statements in blocks:
$name = "Dave"
Invoke-EpsTemplate -Template @'
Hello <%= $name %>!
Today is <%= Get-Date -UFormat %x %>.
'@
will produce:
Hello Dave!
Today is 11/12/17.
Sometimes we would like to iterate over a collection, generate some text for each element and finally join the generated blocks together with a separator.
In an expression block we can use the following idiomatic PowerShell snippet:
Invoke-EpsTemplate -Template @'
<%= ("Id", "Name", "Description" | ForEach-Object { "[String]`$$_" }) -Join ",`n" -%>
'@
which would generate the following result:
[String]$Id,
[String]$Name,
[String]$Description
However, due to EPS internal workings, the following code would not work:
Invoke-EpsTemplate -Template @'
<% ("Id", "Name", "Description" | ForEach-Object { -%>
[String]$<%= $_ -%>
<% }) -join ",`n" -%>
'@
The -join
operator is ignored by EPS:
[String]$Id[String]$Name[String]$Description
This is expected behavior because -join
is applied to the result of
ForEach-Object
which is defined inside a CODE block and should not produce
any output.
EPS provides an internal Each
function whose behavior is similar to
ForEach-Object
but achieves the desired result inside a template :
Each [-Process] <scriptblock> [-InputObject <Object[]>] [-Begin <scriptblock>] [-End <scriptblock>] [-Join <string>]
Each
can only be used in PS v3 or above.
This snippet of EPS would generate the desired result (notice that -Join
is a parameter of Each
and is not applied to its result value as would be the case with the -join
operator):
Invoke-EpsTemplate -Template @'
<% "Id", "Name", "Description" | Each { -%>
[String]$<%= $_ -%>
<% } -Join ",`n" -%>
'@
and generate:
[String]$Id,
[String]$Name,
[String]$Description
In some cases it can be useful to also generate a prefix and suffix to the iterated part:
Invoke-EpsTemplate -Template @'
<% "Id", "Name", "Description" | Each { -%>
[String]$<%= $_ -%>
<% } -Begin { %>[NSSession]$Session<% } -End { %>[String]$LogLevel<% } -Join ",`n" -%>
'@
will generate:
[NSSession]$Session,
[String]$Id,
[String]$Name,
[String]$Description,
[String]$LogLevel
Notice that when using -Begin
and/or -End
with -Join
all blocks are joined together.
If you want to prefix and suffix, without joining the prefix and suffix, use the following pattern:
Invoke-EpsTemplate -Template @'
Param(
<% "Id", "Name", "Description" | Each { -%>
[String]$<%= $_ -%>
<% } -Join ",`n" %>
)
'@
Which will generate:
Param(
[String]$Id,
[String]$Name,
[String]$Description
)
In some cases it is useful to iterate over a collection while maintaining an index of the
current item. The Each
function exposes a $index
variable to the script blocks it executes.
The $index
variable starts at 0 for the first element.
Invoke-EpsTemplate -Template @'
<% "Dave", "Bob", "Alice" | Each { -%>
<%= $Index + 1 %>. <%= $_ %>
<% } -%>
'@
would generate the following listing:
1. Dave
2. Bob
3. Alice
Using default values if the provided variable is $Null
or empty (as returned by the [string]::IsNullOrEmpty
function) is a very common pattern which can be done easily with a template like:
$config = [PSCustomObject]@{
Host = "localhost"
#Port = "8080" # this would be an optional configuration value
}
Invoke-EpsTemplate -Template @'
<%= $config.Host
%>:<%=
if ([string]::IsNullOrEmpty($config.Port)) { "80" } else { $config.Port }
%>
'@
EPS provides a Get-OrElse
function that allows for a shorter version:
$config = [PSCustomObject]@{
Host = "localhost"
#Port = "8080" # this would be an optional configuration value
}
Invoke-EpsTemplate -Template @'
<%= $config.Host %>:<%= Get-OrElse $config.Port "80" %>
<%= $config.Host %>:<%= $config.Port | Get-OrElse -Default "80" %>
'@
If there's a more complicated piece of powershell code that should be reused across multiple files, it can be encapsulated in a helper function, i.e.:
$helpers = @{
NumberedList = {
param($arr)
$i = 1
$arr | foreach-object { "$i. $_"; $i++ } | out-string
}
}
$helpers
should be passed to Invoke-Eps
:
Invoke-EpsTemplate -Path Test.eps -helpers $helpers
Now, NumberedList
can be used in the template.
<%= NumberedList "Dave", "Bob", "Alice" %>
would generate the following listing:
1. Dave
2. Bob
3. Alice
- Original version was written by Dave Wu.
- Maintained now and extended by Dominique Broeglin (@dbroeglin), thank you pal 谢谢!
Help find more bugs! Or find more usage of this tool... Author's email: [email protected]