I recently started working with Plaster and I really like this module. I covered my first template in my Adventures in Plaster blog post last week. I have been pulling together ideas for more Plaster templates and I thought up a fun one to work on.

I am going to build a Plaster template that builds a Plaster template. I am calling this new template GetPlastered.

This will be a good example demonstrating the TemplateFile features of Plaster.

Index

Project plan

My primary goal is to have a Plaster template that will turn an existing folder/project into a Plaster template. Our Plaster template will generate a PlasterTemplate.xml for that folder.

This can be confusing because we are also creating a PlasterTemplate.xml to make this template that generates a PlasterTemplate.xml for a new template. It is like we are writing code that writes the same code that we are writing.

Starting a new template

I already have a repository for my Plaster templates, so all I need to do is create the initial template manifest.

$templateName = 'GetPlastered'
$manifestProperties = @{
    Path = ".\$templateName\PlasterManifest.xml"
    Title = "Generate Plaster Maifest"
    TemplateName = $templateName
    TemplateVersion = '0.0.1'
    Author = 'Kevin Marquette'
}

New-Item -Path $templateName -ItemType Directory
New-PlasterManifest @manifestProperties

Planning the questions

Because my intent is that this template will be used instead of the New-PlasterManifest Cmdlet, we need to capture that functionality.

  • Template Name
  • Template Title
  • Template Author

That should sum up the information we need to collect.

Creating parameters

Now we can turn those planned questions into parameters. These questions are straightforward parameters to create.

<parameter name="TemplateName" 
           type="text" 
           prompt="Template Name" 
           default="${PLASTER_DestinationName}" />

<parameter name="TemplateTitle" 
           type="text" 
           prompt="Template Title" 
           default="${PLASTER_PARAM_TemplateName" />

<parameter name="TemplateAuthor" 
           type="user-fullname" 
           prompt="Author" />

I added these parameters to the parameters section of the PlasterManifest.xml file.

For the TemplateName default value, I use the name of the destination folder that is specified when Invoke-Plaster is invoked.

For the TemplateAuthor, I used user-fullname for the type. That is a special type that pulls the value from the user’s .gitconfig as a default.

TemplateFile

Now we need to create a TemplateFile to generate the PlasterTemplate.xml file. The first half of the TemplateFile will be basic value substitution.

<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="1.0" 
xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
    <name><%= $PLASTER_PARAM_TemplateName %></name>
    <id><%= $PLASTER_GUID1 %></id>
    <version>0.0.1</version>
    <title><%= $PLASTER_PARAM_TemplateTitle %></title>
    <description></description>
    <author><%= $PLASTER_PARAM_TemplateAuthor %></author>
    <tags></tags>
</metadata>
<parameters>
</parameters>
<content>
...

All the magic happens in the second half of this TemplateFile. We walk the destination folder for both folders and files to create the content section.

...
  <content>
<%
$path = $PLASTER_DestinationPath

$folders = Get-ChildItem -Path $path -Directory -Recurse
$files = Get-ChildItem -Path $path -File -Recurse

$path += '\'  

foreach($node in $folders.fullname)
{
    $destination = $node.replace($path,'')
"    <file source='' destination='$destination'/>" 
}

foreach($node in $files.fullname)
{
    $source = $node.replace($path,'')
"    <file source='$source' destination=''/>" 
}

%>
  </content>
</plasterManifest>

Then we save this into a template file called PlasterTemplate.aps1 and add a templateFile entry to our original PlasterTemplate.xml content section.

<content>
    <templateFile source="PlasterTemplate.aps1" 
                  destination="PlasterManifest.xml" />
</content>

I can call that template file anything I want and could easily have left the file extension as xml. I am currently using .aps1 as that designation.

GetPlastered in action

Now we can take any folder and turn that into a Plaster template. The idea is that I would build out the folder first with all the files that should be included. Then instead of running New-PlasterManifest, I would use Invoke-Plaster with this template.

Example

Here is a quick example to see this working. Lets say I have two folders of common tests that I drop into modules. I first move all of these to a new folder called MyTests.

MyTests
├───spec
│       module.feature
│       module.Steps.ps1
│
└───tests
        Export-PSGraph.Tests.ps1
        Feature.Tests.ps1
        Help.Tests.ps1
        Project.Tests.ps1
        Regression.Tests.ps1
        Unit.Tests.ps1

Now we run the GetPlastered template.

PS:> Invoke-Plaster -DestinationPath .\MyTests -TemplatePath .\GetPlastered
____  _           _
|  _ \| | __ _ ___| |_ ___ _ __
| |_) | |/ _` / __| __/ _ \ '__|
|  __/| | (_| \__ \ ||  __/ |
|_|   |_|\__,_|___/\__\___|_|
                                            v1.0.1
==================================================
Template  Name (MyTests):
Template Title (MyTests):
Author (KevinMarquette):
Destination path: .\MyTests
Create PlasterManifest.xml

I just accepted all the defaults and my .\MyTests\PlasterManifest.xml was created. Here is the contents of that file showing every file in the content section.

<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="1.0" 
xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
    <name>MyTests</name>
    <id>3c3480d8-c2fa-4777-bb0c-a2453c8147c3</id>
    <version>0.0.1</version>
    <title></title>
    <description></description>
    <author>KevinMarquette</author>
    <tags>GetPlastered</tags>
</metadata>
<parameters>
</parameters>
<content>
    <file source='' destination='spec'/>
    <file source='' destination='tests'/>
    <file source='spec\module.feature' destination=''/>
    <file source='spec\module.Steps.ps1' destination=''/>
    <file source='tests\Feature.Tests.ps1' destination=''/>
    <file source='tests\Help.Tests.ps1' destination=''/>
    <file source='tests\Project.Tests.ps1' destination=''/>
    <file source='tests\Regression.Tests.ps1' destination=''/>
    <file source='tests\Unit.Tests.ps1' destination=''/>
</content>
</plasterManifest>

We can now use this new template to deploy our tests.

PS:> Invoke-Plaster -DestinationPath .\TestFolder -TemplatePath .\MyTests
____  _           _
|  _ \| | __ _ ___| |_ ___ _ __
| |_) | |/ _` / __| __/ _ \ '__|
|  __/| | (_| \__ \ ||  __/ |
|_|   |_|\__,_|___/\__\___|_|
                                            v1.0.1
==================================================
Destination path: .\TestFolder
Create spec\
Create tests\
Create spec\module.feature
Create spec\module.Steps.ps1
Create tests\Feature.Tests.ps1
Create tests\Help.Tests.ps1
Create tests\Project.Tests.ps1
Create tests\Regression.Tests.ps1
Create tests\Unit.Tests.ps1

PS:> cd .\TestFolder
PS:> tree /f

TestFolder
├───spec
│       module.feature
│       module.Steps.ps1
│
└───tests
        Feature.Tests.ps1
        Help.Tests.ps1
        Project.Tests.ps1
        Regression.Tests.ps1
        Unit.Tests.ps1

Wrapping it up

Other than the layers of inception going on, this really was an easy template to create. This was a fun project and I have it published with my other Plaster templates.

I am working on setting this up to be published to the PSGallery. I’ll update this post when that happens. I hope you enjoy it.