Introduction
Not long ago, I faced the task of rendering various HTML reports from the PowerShell script for sending via e-mail. The search for ready-made solutions did not give much. Someone connects Razor, someone has his own handwritten complicated
bicycles engines.
The modest list of requirements was:
- The view code must be in separate files.
- Inside the view should be support for nesting, and inserts code on PowerShell.
- It should work on any hosts with PowerShell 2.0 without additional settings.
Since nothing like this could be found, a simple (and at the same time powerful) view rendering engine in the classic Asp style was implemented.

Implementation details
Studying the question (like PowerShell itself), I noticed the syntax for evaluating PowerShell expressions within strings. For example, the expression
" $($env:COMPUTERNAME)"
will be interpreted at runtime and we will get something like
MYCOMPUTER
at the output.
')
This is actually the simplest form of templating. It allows you to render quite complex views:
$Model = @{} $Model.Title = 'Hello, this is a test' $Model.Clients = @('Ivan', 'Sergiy', 'John') $html = "<h1> $($Model.Title) </h1> <div class=""test""> <ul> $( foreach($client in $Model.Clients) {" <li> $( $client ) </li> "}) </ul> </div>" $html
As you can see, the PowerShell parser allows you to use nested inserts of the code enclosed in $ () into lines, which is very convenient for implementing branches and loops.
This method can already be used for small tasks, although there are drawbacks:
- The view code is contained in the script code, not in a separate file.
- There is no possibility to use nested views.
- The syntax is a bit not very clear, and often because of a missing parenthesis or quotation mark, you have to check everything hard.
- It is necessary in the text boxes to encode the double quote
"
as ""
.
The first two shortcomings are solved quite simply - the template is moved to a separate file in the Views subfolder, and a function is written for the model render:
function RenderViewNativePowerShell( [Parameter(Mandatory=$true)][string] $viewName, [Parameter(Mandatory=$true)][Object] $model ) { $viewFileName = Resolve-Path ('Views\' + $viewName) $templateContent = Get-Content $viewFileName | Out-String return $ExecutionContext.InvokeCommand.ExpandString('"' + $templateContent + '"') }
After which it can be called like this:
RenderViewNativePowerShell 'Test_ps.html' $Model
At the same time, nested views are supported. This is what the test_ps.html code looks like:
$( RenderViewNativePowerShell 'header_ps.html' $Model ) <div class=""test""> <ul> $( foreach($client in $Model.Clients) {" <li> $( $client ) </li> "}) </ul> </div>
This may seem to some people enough, but I decided to overcome the remaining shortcomings - switch to using ASP brackets <% ...%>, since this syntax is supported in many text editors, and the page layout looks much more readable.
So, the basic idea of the implementation is quite simple: take and replace all the brackets <% ...%> with their PowerShell equivalents $ (...). Some difficulty was that the replacement must be ambiguous in order to take into account the nested views, as they must be in “...” blocks.
After some torment, such a function arose:
function RenderView( [Parameter(Mandatory=$true)][string] $viewName, [Parameter(Mandatory=$true)][Object] $model ) { $viewFileName = Resolve-Path ("Views\" + $viewName) $templateContent = Get-Content $viewFileName | Out-String $rx = New-Object System.Text.RegularExpressions.Regex('(<%.*?%>)', [System.Text.RegularExpressions.RegexOptions]::Singleline) $res = @() $splitted = $rx.split($templateContent); foreach($part in $splitted) { if ($part.StartsWith('<%') -and $part.EndsWith('%>')) #transform <%...%> blocks { $expr = $part.Substring(2, $part.Length-4) #remove <%%> quotes $normExpr = $expr.Replace('`n','').Replace('`r','').Trim(); $startClosure = '$(' $endClosure = ')' if ($normExpr.endswith('{')) { $endClosure = '"' } if ($normExpr.startsWith('}')) { $startClosure = '"' } $res += @($startClosure + $expr + $endClosure) } else #encode text blocks { $expr = $part.Replace('"', '""'); $res += @($expr) } } $viewExpr = $res -join '' return $ExecutionContext.InvokeCommand.ExpandString('"' + $viewExpr + '"') }
In addition to the required replacement of <%%> with their PowerShell equivalents, they also replace “with“ ”in text blocks.
As a result, our view looks pretty good in Visual Studio:

In conclusion, it remains to note that the source code with some tests and examples is posted on
GitHub .