My Custom Plaster Template

Um meine PowerShell Projekte endlich etwas zu vereinheitlichen, stand schon länger das Erstellen eines Custom Plaster Template auf meiner ToDo Liste. Das Plaster Projekt selbst kenne ich schon etwas, habe aber leider bisher nicht die Zeit gefunden mich damit genauer zu beschäftigen. Somit hatte also am Ende der Langstreckenflug nach New Orleans etwas sehr Positives… Nämlich die Einarbeitung in dieses geniale Tool.

Was ist Plaster

Die eigene Projektbeschreibung bedarf keiner weiteren Erläuterung:

Plaster is a template-based file and project generator written in PowerShell. Its purpose is to streamline the creation of PowerShell module projects, Pester tests, DSC configurations, and more. File generation is performed using crafted templates which allow the user to fill in details and choose from options to get their desired output.

Nachdem das Plaster Modul aus der PowerShell Gallery gezogen ist (ab PowerShell 5), kann man auch bereits ohne weitere Konfiguration mit dem Standard-Template sein erstes Projekt erzeugen:

Install-Module -Name Plaster
Invoke-Plaster -TemplatePath (Get-PlasterTemplate).TemplatePath -DestinationPath <String>

Ausführliche Dokumentation zu dem Plaster Cmdlet`s findet sich im GitHub Projekt.

Mit meinem Custom Plaster Template sieht der Aufruf ähnlich aus. Nur der Pfad zu dem eigenen Template muss mitgegeben werden:

Custom Plaster Template - Execution

Das Ergebnis ist dann ein Verzeichnis mit allen nötigen Unterordnern und den gewünschten Files darin, welche auch teilweise inhaltlich durch token replacement auf das neue PowerShell Modul angepasst wurden:

C:\TEMP\MYFIRSTPROJECT
│   myFirstProject.psd1
│   myFirstProject.psm1
│   README.md
│
├───.vscode
│       settings.json
│       tasks.json
│
├───docs
│   │   index.rst
│   │
│   └───features
├───helper
│       myFirstProject.Create-Docs.ps1
│       Update-ModuleManifestData.ps1
│       Update-PowerShellGallery.ps1
│
├───media
└───tests
        myFirstProject.Tests.ps1

Was soll mein Custom Plaster Template beinhalten

Da ich sehr gerne mit VSCode an meinen Skripten arbeite, sollte gleich bei der Projekterstellung eine tiefe Integration erfolgen. Dafür soll in jedem Fall die Nutzung von Pester Tests und Read the Docs Dokumentationen vereinfacht werden.

VSCode Integration

Der einfachste Weg, die Nutzung solcher Tools zu vereinfachen ist die Einbindung als Tasks in VSCode.

Meine Tasks:

  • Pester Test
  • Read The Docs Generierung
  • Module Manifest File aktualisieren
    • Patch
    • Minor
    • Major

Aber auch eine kleine Grundkonfiguration für den VSCode Workspace sollte beinhaltet sein.

VSCode Tasks

Für mein task.json habe ich mich der Vorlage aus dem Standard-Template bedient und dieses um meine zusätzlichen Tasks erweitert.

// Available variables which can be used inside of strings.
// ${workspaceRoot}: the root folder of the team
// ${file}: the current opened file
// ${relativeFile}: the current opened file relative to workspaceRoot
// ${fileBasename}: the current opened file's basename
// ${fileDirname}: the current opened file's dirname
// ${fileExtname}: the current opened file's extension
// ${cwd}: the current working directory of the spawned process
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
	"version": "0.1.0",

	// Start PowerShell
    "windows": {
        "command": "${env.windir}\\sysnative\\windowspowershell\\v1.0\\PowerShell.exe"
    },
    "linux": {
        "command": "/usr/bin/powershell"
    },
    "osx": {
        "command": "/usr/local/bin/powershell"
    },

	// The command is a shell script
	"isShellCommand": true,

	// Show the output window always
	"showOutput": "always",

    "args": [
        "-NoProfile", "-ExecutionPolicy", "Bypass"
    ],

    // Associate with test task runner
    "tasks": [
        {
            "taskName": "CreateDocs",
            "suppressTaskName": true,
            "isTestCommand": true,
            "showOutput": "always",
            "args": [
                "Write-Host 'Invoking CreateDocs'; ./helper/*.Create-Docs.ps1;",
                "Invoke-Command { Write-Host 'Completed CreateDocs task in task runner.' }"
            ]
        },
        {
            "taskName": "UpdateManifestData-Patch",
            "suppressTaskName": true,
            "isTestCommand": true,
            "showOutput": "always",
            "args": [
                "Write-Host 'Invoking Patch UpdateManifestData'; Import-Module ./helper/Update-ModuleManifestData.ps1 -Force;",
                "Update-ModuleManifestData -Path ./*.psd1 -Patch;",
                "Invoke-Command { Write-Host 'Completed Patch UpdateManifestData task in task runner.' }"
            ]
        },
        {
            "taskName": "UpdateManifestData-Minor",
            "suppressTaskName": true,
            "isTestCommand": true,
            "showOutput": "always",
            "args": [
                "Write-Host 'Invoking Minor UpdateManifestData'; Import-Module ./helper/Update-ModuleManifestData.ps1 -Force;",
                "Update-ModuleManifestData -Path ./*.psd1 -Minor;",
                "Invoke-Command { Write-Host 'Completed Minor UpdateManifestData task in task runner.' }"
            ]
        },
        {
            "taskName": "UpdateManifestData-Major",
            "suppressTaskName": true,
            "isTestCommand": true,
            "showOutput": "always",
            "args": [
                "Write-Host 'Invoking Major UpdateManifestData'; Import-Module ./helper/Update-ModuleManifestData.ps1 -Force;",
                "Update-ModuleManifestData -Path ./*.psd1 -Major;",
                "Invoke-Command { Write-Host 'Completed Major UpdateManifestData task in task runner.' }"
            ]
        },
        {
            "taskName": "Test",
            "suppressTaskName": true,
            "isTestCommand": true,
            "showOutput": "always",
            "args": [
                "Write-Host 'Invoking Pester'; Invoke-Pester -PesterOption @{IncludeVSCodeMarker=$true};",
                "Invoke-Command { Write-Host 'Completed Test task in task runner.' }"
            ],
            "problemMatcher": [
                {
                    "owner": "powershell",
                    "fileLocation": ["absolute"],
                    "severity": "error",
                    "pattern": [
                        {
                            "regexp": "^\\s*(\\[-\\]\\s*.*?)(\\d+)ms\\s*$",
                            "message": 1
                        },
                        {
                            "regexp": "^\\s+at\\s+[^,]+,\\s*(.*?):\\s+line\\s+(\\d+)$",
                            "file": 1,
                            "line": 2
                        }
                    ]
                }
            ]
        }
	]
}

Ist das neue Projekt in VSCode geöffnet, lassen sich die Tasks über die Befehlsleiste aufrufen:

Custom Plaster Template - VSCode Tasks

Für die UpdateManifestData Tasks wird ein Skript (Update-ModuleManifestData.ps1) im Ordner helper verwendet. Dieser Ordner wird ebenfalls automatisch am Ziel erstellt.
Durch das Update-ModuleManifestData.ps1 Script wird aber nicht nur die Versionsnummer laut den Vorgaben hochgezählt, sondern auch alle Funktionen aus den psm1-Files des Projekts exportiert.

Ich verwende üblicherweise pro Funktion ein eigenes psm1 File, welches analog zur Funktion benannt ist. Darauf ist auch das Update-ModuleManifestData.ps1 Skript ausgelegt.

Die Ausgabe des Taks UpdateManifestData-Patch sieht dann mit der einen Funktion „myFirstProject“ so aus:

Custom Plaster Template - VSCode Task UpdateManifestData-Patch Output

VSCode Settings

Da es sich bei den Files in den erzeugten Projekten hauptsächlich um PowerShell Skripte handeln wird, bietet es sich an dies in den Workspace Settings als Standard festzulegen.

{
    "files.trimTrailingWhitespace": true,
    "files.defaultLanguage": "powershell"
}

Pester

Wie bereits in einem vorherigen Abschnitt ausgeführt, ist das wichtigste für mich die Integration über die VSCode Tasks. Somit kann jederzeit schnell auf die Tests zugegriffen werden.

Um das zu ermöglichen, wird in meinem Custom Plaster Template ein Basis-Test für das neue Modul erstellt.  Der Task zum Aufruf der Pester Tests ist ja bereits über das tasks.json erzeugt.

Für einen Basis-Test habe ich mich dem genialen Skript von Kevin Marquette’s blog bedient. Die spezifischen Pester Tests können dann später einfach hinzugefügt werden.

Ausgabe des Test Tasks mit Hilfe von Pester sieht dann so aus:

Custom Plaster Template - VSCode Task Test Output

Read the Docs

Auf Read the Docs bin ich ursprünglich durch den Artikel Switching to reStructuredText and ReadTheDocs for GitHub Project Documentation von Chris Wahl gekommen. Ich finde das eine tolle Möglichkeit, schnell und unkompliziert eine Basis-Doku zu erstellen und aktuell zu halten.
Zur Generierung der Files für die Dokumentation verwende ich zum einen eine etwas angewandelte Version des Skripts von Chris Wahl und zum anderen eine index.rst die als Template für ein token replacement dient.

Welcome to my <%=$PLASTER_PARAM_ModuleName%> Module
========================

This the basic documentation of the '<%=$PLASTER_PARAM_ModuleName%>' PowerShell Module.
The main documentation for the module is organized into a couple sections:

* :ref:`feature-docs`

.. _feature-docs:

.. toctree::
   :maxdepth: 2
   :glob:
   :caption: Feature Documentation

   features/*

Custom Plaster Template Manifest

Um mein Custom Plater Template zu erstellen bin ich wie folgt vorgegangen:

  • Basis-Template erzeugen
$manifestProperties = @{
    Path = $env:USERPROFILE + '\Documents\GitHub\PlasterConfig\Templates\plasterManifest.xml'
    Title = 'My Custom Plaster Template'
    TemplateName = 'MyCustomPlasterTemplate'
    TemplateVersion = '0.0.1'
    Author = 'Markus Kraus'
}

New-PlasterManifest @manifestProperties
  • Benötigte Schritte aus dem Default Template kopieren und für eigene Template adaptieren (eine weitere gute Quelle sind die zusätzlichen Beispiele im GitHub Projekt)
C:\Program Files\WindowsPowerShell\Modules\Plaster\1.0.1\Templates\NewPowerShellManifestModule\plasterManifest.xml
  • Benötigte Files in das Projekt kopieren

Es ist erforderlich, dass jedes Template in einem eigenen Order abgelegt wird.

Das fertige Schema der Projekts wird in der plasterManifest.xml festgehalten:

<?xml version="1.0" encoding="utf-8"?>
<plasterManifest
      schemaVersion="1.0" xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
      <metadata>
            <name>MyCustomPlasterTemplate</name>
            <id>8ccc688a-efd6-4729-91e7-8332291b7ad4</id>
            <version>0.0.1</version>
            <title>My Custom Plaster Template</title>
            <description></description>
            <author>Markus Kraus</author>
            <tags></tags>
      </metadata>
      <parameters>
            <parameter name='ModuleName'
                  type='text'
                  prompt='Enter the name of the module'/>

            <parameter name='Version'
                  type='text'
                  prompt='Enter the version number of the module'
                  default='0.1.0'/>

            <parameter name='AddTest'
                  type='choice'
                  prompt='Create test dir and add Pester test for module manifest validation:'
                  default='0'
                  store='text'>
                  <choice label='&amp;No'
                        help="Choose not to add Pester test support."
                        value="No"/>
                  <choice label='&amp;Yes'
                        help="Choose to add Pester test support."
                        value="Yes"/>
            </parameter>

            <parameter name='AddHelperDoc'
                  type='choice'
                  prompt='Add  helper Script and Index to create ReadTheDocs Files:'
                  default='0'
                  store='text'>
                  <choice label='&amp;No'
                        help="Choose not to add helper Files"
                        value="No"/>
                  <choice label='&amp;Yes'
                        help="Chooseot to add helper Files."
                        value="Yes"/>
            </parameter>

            <parameter name='Editor'
                  type='choice'
                  prompt='Select a editor for editor integration (or None):'
                  default='0'
                  store='text' >
                  <choice label='&amp;None'
                        help="No editor specified."
                        value="None"/>
                  <choice label='Visual Studio &amp;Code'
                        help="Your editor is Visual Studio Code."
                        value="VSCode"/>
            </parameter>
      </parameters>

      <content>
            <message>&#10;&#10;Scaffolding your PowerShell Module...&#10;&#10;&#10;</message>

            <newModuleManifest destination='${PLASTER_PARAM_ModuleName}.psd1'
                  moduleVersion='$PLASTER_PARAM_Version'
                  rootModule='${PLASTER_PARAM_ModuleName}.psm1'
                  encoding='UTF8-NoBOM'/>

            <templateFile source='MyCustomPlasterTemplate-Template.psm1'
                  destination='${PLASTER_PARAM_ModuleName}.psm1'/>

            <templateFile source='README.md'
                  destination='README.md'/>

            <file source=''
                  destination='media/'/>
            
            <file source=''
                  destination='helper/'/>

            <file source=''
                  destination='docs/'/>
            
            <file source='helper/Update-*'
                  destination='helper/'/>

            <file condition="$PLASTER_PARAM_AddHelperDoc -eq 'Yes'"
                  source=''
                  destination='docs/features'/>

            <templateFile condition="$PLASTER_PARAM_AddHelperDoc -eq 'Yes'"
                  source='helper\Create-Docs.ps1'
                  destination='helper\${PLASTER_PARAM_ModuleName}.Create-Docs.ps1'/>

            <templateFile condition="$PLASTER_PARAM_AddHelperDoc -eq 'Yes'"
                  source='docs\index.rst'
                  destination='docs\index.rst'/>                   

            <templateFile condition="$PLASTER_PARAM_AddTest -eq 'Yes'"
                  source='tests\MyCustomPlasterTemplate-BasicTest.ps1'
                  destination='tests\${PLASTER_PARAM_ModuleName}.Tests.ps1' />

            <file condition="($PLASTER_PARAM_Editor -eq 'VSCode')"
                  source='editor\VSCode\settings.json'
                  destination='.vscode\settings.json' />

            <file condition="(($PLASTER_PARAM_Editor -eq 'VSCode') -and ($PLASTER_PARAM_AddTest -eq 'Yes'))"
                  source='editor\VSCode\tasks.json'
                  destination='.vscode\tasks.json' />

            <requireModule name="Pester" condition="$PLASTER_PARAM_AddTest -eq 'Yes'" minimumVersion="3.4.0"
                  message="Without Pester, you will not be able to run the provided Pester test to validate your module manifest file.`nWithout version 3.4.0, VS Code will not display Pester warnings and errors in the Problems panel."/>

            <message>

Your new PowerShell module project '$PLASTER_PARAM_ModuleName' has been created.

            </message>

            <message condition="$PLASTER_PARAM_AddTest -eq 'Yes'">
A Pester test has been created to validate the module's manifest file.  Add additional tests to the test directory.
You can run the Pester tests in your project by executing the 'test' task.  Press Ctrl+P, then type 'task test'.

            </message>

            <message condition="$PLASTER_PARAM_AddHelperDoc -eq 'Yes'">
A Script to help you to create the Files for ReadTheDocs is added to the helper Folder.
You can run the Docs creation in your project by executing the 'CreateDocs' task.  Press Ctrl+P, then type 'task CreateDocs'.

            </message>
      </content>
</plasterManifest>

Meine Plaster  Konfiguration auf GitHub

Alle von mir verwendeten Skripte und Konfigurationen sind auch in meinem GitHub Projekt zu diesem Beitrag zu finden.

My PlasterConfig Project

 

 

Weitere Infos

Leave a Reply