Вступление
Не так давно передо мной возникла задача рендерить из PowerShell скрипта различные HTML отчеты для дальнейшей отсылки по e-mail. Поиск готовых решений дал не очень много. Кто-то подключает Razor, кто-то свои самописные сложноватые
Скромный список требований был такой:
- Код вьюх должен быть в отдельных файлах.
- Внутри вьюх должна быть поддержка вложенности, и вставок кода на PowerShell.
- Должен работать на любых хостах с PowerShell 2.0 без дополнительных настроек.
Так как ничего подобного не удалось найти, то был реализован простой (и одновременно мощный) движок рендеринга вьюх в стиле классического Asp.

Подробности реализации
Изучая вопрос (как и сам PowerShell) я обратил внимание на синтаксис вычисления PowerShell выражений внутри строк. Например выражение
" $($env:COMPUTERNAME)" будет во время выполнения интерпретировано и на выходе мы получим что-то вроде MYCOMPUTER.Это уже собственно и есть темплейтинг в простейшем виде. Он позволяет рендерить достаточно сложные вьюхи:
$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
Как видим, парсер PowerShell позволяет использовать вложенные вставки кода заключенного в $() в строки, это очень удобно для реализации ветвлений и циклов.
Данный метод уже можно использовать для небольших задач, хотя есть и недостатки:
- Код вьюхи содержится в коде скрипта, а не в отдельном файле.
- Нет возможности использовать вложенные вьюхи.
- Синтаксис немного не совсем нагляден, и часто из-за пропущенной скобки или кавычки приходится напряженно все проверять.
- Необходимо в текстовых вставках кодировать двойную кавычку
"как"".
Первые два недостатка решаются довольно просто – темплейт перемещается в отдельный файл в подпапку Views, и пишется функция для рендера модели:
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 + '"') }
После чего ее можно вызывать так:
RenderViewNativePowerShell 'Test_ps.html' $Model
При этом поддерживаются вложенные вьюхи. Вот так выглядит код test_ps.html:
$( RenderViewNativePowerShell 'header_ps.html' $Model ) <div class=""test""> <ul> $( foreach($client in $Model.Clients) {" <li> $( $client ) </li> "}) </ul> </div>
Кому-то этого может показаться достаточно, но я решил побороть оставшиеся недостатки – перейти на использование скобок ASP <%...%>, так как этот синтаксис поддерживается во многих текстовых редакторах, и верстка страницы выглядит намного читабельнее.
Итак, основная идея реализации довольно проста: взять и заменить все скобки <%...%> на их PowerShell эквиваленты $(…). Некоторая сложность состояла в том, что замена должна быть неоднозначной, чтобы учитывать вложенные вьюхи, так как они должны быть в “…” блоках.
После некоторых мучений возникла такая функция:
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 + '"') }
Кроме требуемой замены <%%> на их PowerShell эквиваленты также выполняется замена “ на “” в текстовых блоках.
В итоге наша вьюха выглядит довольно неплохо в Visual Studio:

В заключение остается отметить, что исходный код с некоторыми тестами и примерами выложен на GitHub.
