powershellorg / plaster Goto Github PK
View Code? Open in Web Editor NEWPlaster is a template-based file and project generator written in PowerShell.
License: MIT License
Plaster is a template-based file and project generator written in PowerShell.
License: MIT License
Hi!
Not all organizations care about licenses, but some do. For the sake of folks like Justin, it might be worth leaning towards a simple, permissive license like the MIT license as a default, rather than None.
I can see arguments in both directions, but if one is specified by default, it will make it harder for folks to just ignore it, or be scared off without realizing there's a decent boiled down overview.
Cheers!
I need to set up an AppVeyor build for Plaster using the PowerShell organization's account. Not a lot of work required, just need to take some time to do it.
First of all, great idea!
I believe there are things that you won't be able to provide a (safe) plaster interface for, and there will always be some repetitive execution that we'll want to automate.
It's good to not have execution embedded in manifest files (XML, JSON, PSD1) but IMO it should still provide the flexibility to 'run arbitrary code', just not from the manifest, from a ps1 file, to make reviewing easier.
One way to implement this could be that the manifest only provides 'reference' to psake tasks. So if you want to run any arbitrary task, it's in a psake file, making reviewing the module easier and quicker.
One example is that I often add one of my repo, as a git submodule on my projects, so that it lives under moduleName/lib/subLibName.
To do so, I want to have an option when I create a new module (similar to what Plaster offers), but that option will do something like:
cd ./lib/
git submodule add [email protected]:Project.git
I don't think Plaster could support 'safe' templating with those functionality for all eventuality (git submodule, other SCM, Creating Github/bitbucket repo through API/Module, opening JIRA project...), so what are we trying to keep 'safe'?
I understand that you'd want the templates to be as safe as possible to avoid malicious injection, but how's that different from the modules in the PowerShell Gallery? Installing someone else's module and using it is not safe. Using 'yet another nuget repository' will not help, and although it might be nice to have separation of 'duty', I don't think it's very scalable (you won't have a different nuget repo for every type of packaging, or you'd have a different one for DSC resources, and that would require a different security model?), and the security of a Module template should probably be treated the same as the one from a Module (which can be malicious as well).
The creation of a specialist package for this sounds like a nice idea, but I'd probably wrap it inside a PowerShell module. What's the gallery need would be more METADATA to support this new 'Type'.
The alternative to this, obviously, is that you keep it safe, and someone (or many people, independently) will leverage it and wrap around to add the 'arbitrary execution'.
Hope that's clear, feel free to ask for clarifications.
It would be a nice convenience feature, e.g. if no previous value is stored (#9) to try and guess a good default value.
for example for an Author/Name field:
git config user.name
or worst case:
$env:USERNAME
The same for email etc.
This may require custom parameter types (i.e. author-name, instead of string), so that it's available to use in template choices.
Into this file "Plaster/examples/README.md" it is say :
"If you run the Invoke-Plaster command a second time, you see Plaster's file conflict handling.
You can use the -Force parameter to automatically overwrite existing files."
But today the -Force parameter displaying 'Conflict' state and not 'Create' state.
And the use of the 'newModuleManifest' element should not displayed 'Conflict' but 'Create'( or 'Overwrite' ?) :
Moreover, after the call to Invoke-Plaster, one can not know what it is happened.
Hi,
The Set-Content in PowerShell use UTF-8 with BOM, which fails when consumed in tools such as Test-Kitchen (might be a ruby thing?).
Could you add support in Plaster for an encoding UTF8-NOBOM?
I found this workaround:
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
[System.IO.File]::WriteAllLine($filePath, $PLASTER_FileContent, $Utf8NoBomEncoding)
Thanks,
Gael
There are multiple parts to this issue.
If Plaster processes a file like an exe, it will corrupt it even if it doesn't modify anything.
function ProcessTemplate([string]$Path, $encoding) {
if ($PSCmdlet.ShouldProcess($Path, $LocalizedData.ShouldProcessTemplateFile)) {
$content = Get-Content $Path -Raw
$pattern = '(<%=)(.*?)(%>)'
$newContent = [regex]::Replace($content, $pattern, {
param($match)
$expr = $match.groups[2].value
Write-Verbose "Replacing template expr $expr in '$Path'"
ExpandString $expr
}, @('IgnoreCase', 'SingleLine', 'MultiLine'))
if ($Content -ne $newContent)
{
Set-Content -Path $Path -Value $newContent -Encoding $encoding
}
}
}
Every file Plaster processes it uses Set-Content with $Encoding
even if there were no changes made to the file. Something like an exe might not have had a utf8
encoding so this will corrupt the exe.
Where this becomes problematic is when using Recurse on a directory. If you have files in those directories, one might add template = 'true'
so the template files in that directory are processed.
A fix that so far seems to work for me is to add a simple check
if ($Content -ne $newContent)
{
Set-Content -Path $Path -Value $newContent -Encoding $encoding
}
Instead of setting the content every single time, which creates unneeded room for error, I just added a check to see if $Content was actually altered via the Regex replace.
However, now that there is Recurse type functionality, is this fix enough? Here are a few things to consider when dealing with Recurse.
I am pretty sure you could do the recurse without template = 'true'
and then add other File Nodes with template = 'true'
for files inside of the Recursed folder that need to be edited. The issue here is we will likely have users who do not know to do it this way and might use template = 'true'
in the folder Recurse.
I am thinking I may go ahead and push the patch for the Set-Content, but wanted to discuss what I mentioned above as well.
In this file, it is says :
"In a future release, we may allow a template to execute arbitrary script but that will be inside a special directive perhaps called < script>."
How do you consider a tied variable inside the plaster context ? Is it safe or not ?
#Tied Variable
class GuidVariable : System.Management.Automation.PSVariable {
GuidVariable():base("Guid", 0, "ReadOnly,AllScope") {}
[object] get_Value(){ return [System.Guid]::NewGuid() }
}
#create $Guid Variable
$ExecutionContext.SessionState.PSVariable.Set([GuidVariable]::new())
Invoke-Plaster...
into the xml template :
<message>`n1 : $Guid </message>
<message>`n2 : $Guid </message>
This use could it be forbidden ?
This is now false :
.OUTPUTS
System.Boolean
Returns "True" when the plaster manifest file is valid and "False" when
it isn't valid.
Could you please add an example about the use of the Verbose parameter (ValidationEventHandler event) ?
Hello,
I've been using some of the ideas from the Build.ps1 script in my psake build script and I added a few tasks to enable script signing that I use on some other projects.
I can create a PR to add/merge some of the work that I've done on the build script i use. It could probably use a bit of work but I wanted to know whether this was something that you thought plaster could provide functionality for.
Thanks,
Dave Green
The template functions could they be public (Invoke-Template) or they are intended to be private ?
The localization is not yet supported :
cd 'C:\Users\Laurent\Downloads\Plaster-master\Examples\NewModuleTemplate'
Import-Module ..\..\Plaster.psd1
# Import-LocalizedData : Cannot find the Windows PowerShell data file 'PlasterResources.psd1' in directory
# 'C:\Users\Laurent\Downloads\Plaster-master\fr-FR\', or in any parent culture directories.
# At C:\Users\Laurent\Downloads\Plaster-master\Plaster.psm1:32 char:1
# + Import-LocalizedData LocalizedData -FileName PlasterResources
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : ObjectNotFound: (C:\Users\Lauren...rResources.psd1:String) [Import-LocalizedData], PSInv
# alidOperationException
# + FullyQualifiedErrorId : ImportLocalizedData,Microsoft.PowerShell.Commands.ImportLocalizedData
Get-Culture
# LCID Name DisplayName
# ---- ---- -----------
#1036 fr-FR Français (France)
$PSVersionTable
# Name Value
# ---- -----
# PSVersion 5.0.10586.117
# PSCompatibleVersions {1.0, 2.0, 3.0, 4.0, 5.0.10586.117}
# BuildVersion 10.0.10586.117
# CLRVersion 4.0.30319.42000
# WSManStackVersion 3.0
# PSRemotingProtocolVersion 2.3
# SerializationVersion 1.1.0.1
The xsd documentation says :
<xs:simpleType name="EncodingType">
<xs:annotation>
<xs:documentation>
The encoding to use for writing to a file.
</xs:documentation>
</xs:annotation>
What about for the encoding to use for reading a file ?
All 'parameter' nodes with "type='input'" are [string] type :
I can declare this :
cd 'C:\Users\Laurent\Downloads\Plaster-master\Examples\NewModuleTemplate'
Import-Module ..\..\Plaster.psd1
$PlasterParams = @{
TemplatePath = $PWD
Destination = '..\Out'
ModuleName = ''
FullName = 'John Q. Doe'
Version = '-1'
Options = 'PSake','Pester','Git','None'
Editor = 'VSCode'
License = 'MIT'
}
Invoke-Plaster @PlasterParams
How to control these cases ?
How to known the valid values for a 'Powershell Gallery' template ?
Following the latest commit, Invoke-Plaster throw an exception :
[STA] C:\Users\Laurent\Downloads\Plaster-master\Examples\NewModuleTemplate> Invoke-Plaster @PlasterParams -Force
Invoke-Plaster : A parameter cannot be found that matches parameter name 'FullName'.
in xml :
<parameter name='FullName' type='author' store='encrypted' prompt='Enter your fullname'/>
in InvokePlaster.ps1 the code in dynamicparam do not manage the 'author' type.
When the template path exist, but not the 'plasterManifest.xml' file, this error is trapped :
Import-Module Plaster
$VerbosePreference='Continue'
cd c:\temp
Invoke-Plaster -DestinationPath 'C:\temp'
# ...Plaster...
#The Plaster manifest file 'C:\temp\plasterManifest.xml' was not found.
Invoke-Plaster -TemplatePath 'C:\temp' -DestinationPath 'C:\temp'
# ...Plaster...
#The Plaster manifest file 'C:\temp\plasterManifest.xml' was not found.
But when the template path do not exist, the behavior is different :
Invoke-Plaster -TemplatePath 'C:\temp\Notexist' -DestinationPath 'C:\temp'
# Get-Item : Cannot find path 'C:\temp\Notexist' because it does not exist.
# At C:\Users\Laurent\Documents\WindowsPowerShell\Modules\Plaster\Plaster.psm1:47 char:13
# + $item = Get-Item -LiteralPath $TemplatePath
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : ObjectNotFound: (C:\temp\Notexist:String) [Get-Item], ItemNotFoundException
# + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand
#
# VERBOSE: Préparation de la décompression...
# VERBOSE: Error processing dynamic parameters: Le chemin d'accès C:\temp\Notexist n'existe pas ou n'est pas un chemin
# d'accès au système de fichiers valide.
#
# ...Plaster...
#
# Get-Item : Cannot find path 'C:\temp\Notexist' because it does not exist.
# At C:\Users\Laurent\Documents\WindowsPowerShell\Modules\Plaster\Plaster.psm1:47 char:13
# + $item = Get-Item -LiteralPath $TemplatePath
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : ObjectNotFound: (C:\temp\Notexist:String) [Get-Item], ItemNotFoundException
# + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand
#
# VERBOSE: Préparation de la décompression...
# Microsoft.PowerShell.Archive\Expand-Archive : Le chemin d'accès C:\temp\Notexist n'existe pas ou n'est pas un chemin
# d'accès au système de fichiers valide.
# At C:\Users\Laurent\Documents\WindowsPowerShell\Modules\Plaster\Plaster.psm1:58 char:16
# + ... [void](Microsoft.PowerShell.Archive\Expand-Archive -LiteralPath ...
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (C:\temp\Notexist:String) [Expand-Archive], InvalidOperationException
# + FullyQualifiedErrorId : ArchiveCmdletPathNotFound,Expand-Archive
#
# The Plaster manifest file 'C:\Users\Laurent\AppData\Local\Temp\3zxu2ded.kxa\plasterManifest.xml' was not found.
# At C:\Users\Laurent\Documents\WindowsPowerShell\Modules\Plaster\InvokePlaster.ps1:196 char:17
# + ... throw ($LocalizedData.ManifestFileMissing_F1 -f $manifest ...
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : OperationStopped: (The Plaster man... was not found.:String) [], RuntimeException
# + FullyQualifiedErrorId : The Plaster manifest file 'C:\Users\Laurent\AppData\Local\Temp\3zxu2ded.kxa\plasterManifest.xml' was not found.
An another case :
$PlasterParams = @{
TemplatePath = 'C:\temp\Notexist'
DestinationPath = 'C:\temp'
ProjectName ='Project'
}
Invoke-Plaster @PlasterParams -Force
# Get-Item : Cannot find path 'C:\temp\Notexist' because it does not exist.
# At C:\Users\Laurent\Documents\WindowsPowerShell\Modules\Plaster\Plaster.psm1:47 char:13
# + $item = Get-Item -LiteralPath $TemplatePath
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : ObjectNotFound: (C:\temp\Notexist:String) [Get-Item], ItemNotFoundException
# + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand
#
# VERBOSE: Préparation de la décompression...
# VERBOSE: Error processing dynamic parameters: Le chemin d'accès C:\temp\Notexist n'existe pas ou n'est pas un chemin
# d'accès au système de fichiers valide.
#
# Invoke-Plaster : A parameter cannot be found that matches parameter name 'ProjectName'.
# At line:1 char:16
# + Invoke-Plaster @PlasterParams -Force
# + ~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (:) [Invoke-Plaster], ParameterBindingException
# + FullyQualifiedErrorId : NamedParameterNotFound,Invoke-Plaster
When the xml file is not well-formed :
<file source='Module.psm1''
destination='${PLASTER_PARAM_ModuleName}.psm1'/>
the real reason is not displayed :
$PlasterParams = @{
TemplatePath = 'G:\PS\Plaster\ProjectProfile'
DestinationPath = 'G:\PS\Plaster\Out\ProjectProfile'
ProjectName ='Project'
}
Invoke-Plaster @PlasterParams -Force
# VERBOSE: Error processing dynamic parameters: The Plaster manifest 'G:\PS\Plaster\ProjectProfile\plasterManifest.xml'
# is not a well-formed XML file. Cannot convert value "System.Object[]" to type "System.Xml.XmlDocument". Error: "Le
# caractère ''', valeur hexadécimale 0x27, ne peut pas commencer un nom. Ligne 23, position 58."
#
# Invoke-Plaster : A parameter cannot be found that matches parameter name 'ProjectName'.
# At line:1 char:16
# + Invoke-Plaster @PlasterParams -Force
# + ~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (:) [Invoke-Plaster], ParameterBindingException
# + FullyQualifiedErrorId : NamedParameterNotFound,Invoke-Plaster
For this cases, is it possible to have adequate messages : 'the file do not exist' or 'the xml file is not well-formed' or 'the xml file is invalid' ?
Could you to localize these following lines ?
# InvokePlaster.ps1
Write-Verbose "Creating destination dir for module manifest: $manifestDir"
throw "Expected parameter DstPath value to be an absolute path, got '$DstPath'"
throw "$Path must contain $fullDestPath"
I try to create an xml file outside the path destination :
<templateFile source='Templates\ShelLibrary-ms.T.xml'
destination='${Env:AppData}\Microsoft\Windows\Libraries\${PLASTER_PARAM_ProjectName}.library-ms'
encoding='UTF8'/>
I know this it not supported, but the file name displayed is not the used file name :
With -verbose :
For this operation i must create a post installation script.
I'm setting up a new module to publish to the gallery.
I've got a process that works. Probably can be improved and I figured maybe a good fit for plaster.
I usually start with a ps1 to try out an idea. Once it is reasonably stable, I move to make it a module, prep it to be published and versioned in a public git repo. This means creating a properly named psd1, psm1 in a new git directory. I have an InstallModule.ps1
and PublishToGallery.ps1
. InstallModule.ps1
simulates Install-Module
so I can smoke test it before publishing. PublishToGallery.ps1
does just that.
It be great if I could plaster this entire thing. Varying the name and scaffolding the rest.
Is this a fit for Plaster? Can it be easily templated today?
Thanks
I wanted to start a thread here where folks can see our initial list of use cases. Propose others for debate and eventually wind up with a set of supported use cases for the initial release. Here is the set of use cases I have been working off of for the preview.
I know some folks have wanted arbitrary code execution. This is something we're currently going out of our way to prevent. We want users who are expecting "scaffolding" i.e. the creation of files & folders, to be able to trust that the template isn't do something else. In fact, I haven't implemented it yet but I'd like for a user to be able to trust that the template will not modify anything outside of the DestinationPath
they've specified. That said, if there are important use cases we're giving up, I'd like to know.
When running Invoke-Plaster I get the following error:
Create FooUtils.psd1
Microsoft.PowerShell.Management\Get-ChildItem : Cannot find path 'C:\Users\Stefan\RecurseTest'
because it does not exist.
At C:\users\Stefan\Documents\GitHub\Plaster\InvokePlaster.ps1:473 char:22
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I've been going through PowerShell, psake etc creating Build, Test and Deploy steps. This can include ARM templates and inserting JSON snippets into the overall provisioning steps. I know 80% can be done with convention over configuration. I'm wondering what the effort would be to leverage Plaster to do this?
I know these are just an example, but it was a bit confusing to me when trying to create a custom manifest. I think you have a typo here in the 'CreateRootFolder', but also these are missing the required='true'
<parameters>
<parameter name='ModuleName' required='true' prompt='Enter the name of the module'/>
<parameter name='Version' default='1.0.0' store='true' prompt='Enter the version number for the module'/>
<parameter name='CreateRootFoler' default='Yes' prompt='Do you want to create the root folder for the project'/>
</parameters>
Also, I am looking through the invokeplaster.ps1 and I am guessing the CreateRootFolder is no longer a valid parameter? What I would like to be able to do, is provide a Destination Path, and then it create a new Directory in the Destination Path with my ModuleName or other Template variable. Is this currently possible?
I have these files :
..\ProcessTemplate\Template\TestExpand.T.ps1
..\ProcessTemplate\Template\TestExpand2.T.ps1
I can use this manifest :
<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="0.2" xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
<id>5dfd8d94-b2ea-419d-8a80-23a87024b359</id>
<title>Process Template</title>
<description>Expand a template file.</description>
<version>0.2.0</version>
<tags>TemplateFile</tags>
</metadata>
<parameters>
<parameter name='FileName' type='input' prompt='Enter the name of the file to expand (%Name%.T.ps1)'/>
</parameters>
<content>
<message>Expand the template ${PLASTER_PARAM_FileName}</message>
<file source='${PLASTER_PARAM_FileName}.T.ps1'
destination='${PLASTER_PARAM_FileName}.ps1'
template='true'
encoding='UTF8'/>
</content>
</plasterManifest>
$PlasterParams = @{
TemplatePath = "$Pwd"
Destination = "..\Out\ProcessTemplate"
FileName="Template\TestExpand"
}
Invoke-Plaster @PlasterParams
The result is :
..\ProcessTemplate\Out\ProcessTemplate\TestExpand.ps1
But when I use this manifest :
<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="0.2" xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
<id>5dfd8d94-b2ea-419d-8a80-23a87024b359</id>
<title>Process Template</title>
<description>Expand a template file.</description>
<version>0.2.0</version>
<tags>TemplateFile</tags>
</metadata>
<content>
<message> Expand alls template '${PLASTER_PARAM_Templatepath}\Template' </message>
<file source='Template\**'
destination='Code'
template='true'
encoding='UTF8'/>
</content>
</plasterManifest>
$PlasterParams = @{
TemplatePath = "$Pwd"
Destination = "..\Out\ProcessTemplate"
}
Invoke-Plaster @PlasterParams
The result is :
..\ProcessTemplate\Out\ProcessTemplate\Code\TestExpand.T.ps1
..\ProcessTemplate\Out\ProcessTemplate\Code\TestExpand2.T.ps1
I can not manage the files extension.
The source code must it evolve to manage this case or the namming convention is unnecessary ?
If I am dealing with a project that has a very large number of files that are static and don't need to be edited or controlled by a condition, it makes using Plaster very cumbersome. This is especially true if you have a template directory that changes often or has new files added often.
I think a better way to address this might be to have a directory inside of the Root of the zip that Represents your directory structure and any static files that are always going into your project. Then have any Template and Conditional files in another location that are added based on the criteria.
Another option would be to automatically add everything in the zip, and then edit the template files and remove any items that don't meet a condition. Instead of adding the ones that meet conditions, we would be doing the opposite. I like this sort of option best.
Another would be to at least allow adding folders with wildcards for all items under that folder. Maybe something like the example below.
<file source='Root\Dir1\*'
destination='Root\Dir1\*'/>
<file source='Root\Dir2\*'
destination='Root\Dir2\*'/>
My goal is to create a plaster manifest template.
I try to prevent the substitution of a pattern variable in a template file :
$VcsPathRepository="<!%=${PLASTER_DestinationPath}%>"
[Environment]::SetEnvironmentVariable("<%=${PLASTER_PARAM_ProjectName}%>Profile",$VcsPathRepository, "User") }
the result :
$VcsPathRepository="<%=${PLASTER_DestinationPath}%>"
[Environment]::SetEnvironmentVariable("PlasterTestProfile",$VcsPathRepository, "User") }
I combine a templatefile element and a modify element :
<templateFile source='Templates\Project_Clone.T.Ps1'
destination='Project_Clone.ps1'
encoding='UTF8'/>
<modify path='Project_Clone.ps1'>
<replace >
<original><![CDATA[(?s)(<!%=)(.*?)(%>)]]></original>
<substitute><![CDATA[<%=$2$3]]></substitute>
</replace>
</modify>
This works.
But, if I want to edit multiple files I must duplicate the element Modify
Is it possible to add multiple processing for the Modify element and/or add the delayed variable substitution ?
Today it is impossible to use a hashtable into a template :
Import-LocalizedData -BindingVariable PLASTER_MyTemplate -Filename MyTemplate.psd1
Invoke-Plaster ...
#Xml template
<message>$($PLASTER_MyTemplate.VcsTask)</message>
<message>$($PLASTER_MyTemplate.$PLASTER_PARAM_License)</message>
#Module.T.ps1
$ModuleManifestName = '<%=$PLASTER_PARAM_ModuleName%>.psd1'
# <%=${PLASTER_GUID1}%> - testing use of PLASTER predefined variables.
# <%=$PLASTER_MyTemplate.ManifestComment%> -
Adding this feature would be appreciated.
If I open the InvokePester.ps1 file in VS Code I get an error in the PowerShell Output. Anytime I swap between another file and InvokePester.ps1 this error occurs.
Suppression Message Attribute error at line 16 in InvokePlaster.ps1 : Cannot find any Targets ModifyContent that match the Scope Function to apply the SuppressMessageAttribute.
It might be useful to allow the parameters to be requested based on conditions, similar to what you do in the content section.
That way, you could have context specific parameters such as:
if ! PLASTER_PARAM_useDHCP
- ask for vm_ip
- ask for subnet mask.
This function checks for a condition using $FileNode.condition
but I believe that is going to always return false since it is a newModuleManifest
instead of a FileNode
$condition = $FileNode.condition
if ($condition) {
if (!(EvaluateCondition $condition)) {
Write-Verbose "Skipping module manifest generation for '$dstPath', condition evaluated to false."
return
}
}
I would think you would need the following, but I wasn't sure if I was missing something.
$condition = ExpandString $NewModuleManifestNode.condition
Then you could add a Manifest condition like this:
<newModuleManifest destination='${PLASTER_PARAM_ModuleName}.psd1'
moduleVersion='$PLASTER_PARAM_Version'
rootModule='${PLASTER_PARAM_ModuleName}.psm1'
author='$PLASTER_PARAM_FullName'
condition="$PLASTER_PARAM_Manifest -eq 'Yes'"/>
<parameter name='Manifest' type='choice' default='0' prompt='Create a Module Manifest?'>
<choice label='&Yes'
help="Adds Manifest."
value="Yes"/>
<choice label='&No'
help="Skips Manifest."
value="No"/>
</parameter>
Currently, if you set a condition like the example above, it ignores the condition and makes the manifest regardless.
When invoking Plaster from a directory outside of the template directory, it is resolving the relative path against the working directory.
Instead of:
$PSCmdlet.GetUnresolvedProviderPathFromPSPath('C:\path\to\template\foldertorecurse)
It is resolving
$PSCmdlet.GetUnresolvedProviderPathFromPSPath('foldertorecurse)
Which resolves to the current working directory + the folder to recurse.
Reading through the project and I stumbled on this:
After having run the Invoke-Plaster command for a few days, I'mm already pining for a Yeoman like store feature where I can add parameter attribute to say that the parameter can be stored and then its value recalled as the default value. So after I've entered my name or copyright notice once, it can be stored and used as the default value instead of the template provided default value.
I was just wondering if there you could leverage the built in powershell default parameters for this?
I would like to see a variable that inserts creation date or creation date and time when plaster is invoked. Either give separate variables for the different formats or give a way to specify format for the variable.
Have you plan to implement nested template ?
<parameter name='Options' type='multichoice' default='0,1,2' store='text' prompt='Select desired options'>
<choice label='P&Sake build script'
help="Adds a PSake build script that generates the module directory for publishing to the PSGallery."
value="PSake"/>
...
</parameter>
<!-- Invoke a Plaster template (PSakeDirectoryTemplate\plasterManifest.xml) -->
<NestedTemplate condition="$PLASTER_PARAM_Options -contains 'PSake'" Value='PSakeDirectoryTemplate'>
I have posted some examples here: https://gist.github.com/gerane/b40a4755fc249ed420c49ae8fef947f9
When specifying [Int[]] for Default value in order to take Multiple selections, it breaks the GUI portion of the Prompts and forces it to console.
In ISE you can still use it, but it might be confusing to users to have some prompts in a GUI and some in console.
The bigger issue is in an Editor like VSCode. If the user doesn't have their output open, they won't even see their options. They are in the console, but being prompted by a blank prompt that only has a ":". I think this will really confuse most users.
Here is a gif showing what it looks like.
Hi!
This might seem superficial at first glance, but given that end users may end up using examples / typical module templates the most, it might be worth some extra thought on their organization.
Two initial thoughts:
Those are the biggest that stand out - love the idea of the tool, thanks for putting this together!
Cheers!
I think it is worth starting a serious discussion on how these templates are going to be distributed and how Plaster could potentially detect installed templates. I have been seeing several people getting interested about Plaster, and the problem of distribution and just finding Plaster templates is becoming evident.
I think the best distribution method would likely be the PSGallery and add a flag or something for plaster. However, I feel there is an issue with this that conflicts with the prevention of Code Execution in the Plaster Templates. If you install a module from the PSGallery, you just bypassed any attempt at preventing Code execution. If you are wanting to be malicious just have the code run at module installation and/or module import. The same goes with any other module, if you want to be malicious, just have it execute at module installation.
If Templates are distributed as modules, I think it would be beneficial for Plaster to be able to detect those templates in the module directories in some way. Maybe you have a standard folder name or other signal that Plaster can find. Instead of supplying a template path, I think it would be beneficial to either have a new command that prompts the user of template choices, or add a new method to invoke plaster that searches for installed templates and prompts the user.
I just wanted to get a dedicated discussion going on this and see what peoples thoughts were.
How to use the default values stored ?
cd 'C:\Users\Laurent\Downloads\Plaster-master\Examples\NewModuleTemplate'
Import-Module ..\..\Plaster.psd1
$PlasterParams = @{
TemplatePath = $PWD
Destination = '..\Out'
ModuleName = 'FooUtils'
FullName = 'John Q. Doe'
Version = '1.2.0'
Options = 'Git','PSake','Pester'
Editor = 'VSCode'
License = 'MIT'
}
Invoke-Plaster @PlasterParams -Force
#Ok.
Invoke-Plaster -TemplatePath . -Destination ..\Out
# DEBUG: Loading default value store from
# 'C:\Users\Laurent\AppData\Local\Plaster\NewModuleTemplate-0.1.0-38bd80d9-3a47-4916-9220-bed383d90876'.
#No default Value
# from DesignNotes.md : "You could invoke the template like so and get prompted for each parameter:"
#Ok
$PlasterParams = @{
TemplatePath = $PWD
Destination = '..\Out'
ModuleName = 'FooUtils'
FullName = 'John Q. Doe'
Version = '1.2.0'
Options = 'Git','PSake','Pester'
#Editor = 'VSCode' Default value ?
License = 'MIT'
}
Invoke-Plaster -TemplatePath . -Force @PlasterParams
#No default Value
# Which editor do you use
# [I] ISE [C] Visual Studio Code [N] None [?] Help (default is "I"):
The store feature is under development ?
**********************
Windows PowerShell transcript start
Start time: 20160517085134
Machine: (Microsoft Windows NT 10.0.10240.0)
Host Application: C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe
Process ID: 7284
PSVersion: 5.0.10240.16384
WSManStackVersion: 3.0
SerializationVersion: 1.1.0.1
CLRVersion: 4.0.30319.42000
BuildVersion: 10.0.10240.16384
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0.10240.16384
PSRemotingProtocolVersion: 2.3
**********************
Transcript started, output file is tryPlaster.txt
PS C:\Users\dfinke\Documents\GitHub\Plaster\Examples\NewModuleTemplate> Invoke-Plaster -TemplatePath . -Destination ..\Out
PS C:\Users\dfinke\Documents\GitHub\Plaster\Examples\NewModuleTemplate> Invoke-Plaster -TemplatePath . -Destination ..\O
ut
Enter the name of the module (): mymod
Enter the version number for the module (1.0.0):
Enter your fullname (): Doug
Select desired options
[P] Pester test support
[S] PSake build script
[G] Git
[?] Help
(default choices are P,S,G)
Choice[0]: g
Choice[1]:
Which editor do you use
[I] ISE [V] Visual Studio Code [N] None [?] Help (default is "N"): v
Select a license for your module
[A] Apache [M] MIT [N] None [?] Help (default is "N"): m
PS C:\Users\dfinke\Documents\GitHub\Plaster\Examples\NewModuleTemplate> TerminatingError(New-ModuleManifest): "Could not find a part of the path 'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1'."
New-ModuleManifest : Could not find a part of the path
'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1'.
At C:\Users\dfinke\Documents\GitHub\Plaster\InvokePlaster.ps1:239 char:17
+ ... New-ModuleManifest -Path $dstPath -ModuleVersion $moduleV ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [New-ModuleManifest], DirectoryNotFoundException
+ FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.NewModuleManifestCommand
New-ModuleManifest : Could not find a part of the path
'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1'.
At C:\Users\dfinke\Documents\GitHub\Plaster\InvokePlaster.ps1:239 char:17
+ ... New-ModuleManifest -Path $dstPath -ModuleVersion $moduleV ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [New-ModuleManifest], DirectoryNotFoundException
+ FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.NewModuleManifestCommand
Get-Content : Cannot find path 'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1' because it does not
exist.
At C:\Users\dfinke\Documents\GitHub\Plaster\InvokePlaster.ps1:240 char:28
+ $content = Get-Content -LiteralPath $dstPath -Raw
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\dfinke...\Out\mymod.psd1:String) [Get-Content],
ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Get-Content : Cannot find path 'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1' because it does not
exist.
At C:\Users\dfinke\Documents\GitHub\Plaster\InvokePlaster.ps1:240 char:28
+ $content = Get-Content -LiteralPath $dstPath -Raw
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\dfinke...\Out\mymod.psd1:String) [Get-Content], ItemNotFoundEx
ception
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Set-Content : Could not find a part of the path 'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1'.
At C:\Users\dfinke\Documents\GitHub\Plaster\InvokePlaster.ps1:241 char:17
+ ... Set-Content -LiteralPath $dstPath -Value $content -Encodi ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\dfinke...\Out\mymod.psd1:String) [Set-Content],
DirectoryNotFoundException
+ FullyQualifiedErrorId : GetContentWriterDirectoryNotFoundError,Microsoft.PowerShell.Commands.SetContentCommand
Set-Content : Could not find a part of the path 'C:\Users\dfinke\Documents\GitHub\Plaster\Examples\Out\mymod.psd1'.
At C:\Users\dfinke\Documents\GitHub\Plaster\InvokePlaster.ps1:241 char:17
+ ... Set-Content -LiteralPath $dstPath -Value $content -Encodi ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\dfinke...\Out\mymod.psd1:String) [Set-Content], DirectoryNotFo
undException
+ FullyQualifiedErrorId : GetContentWriterDirectoryNotFoundError,Microsoft.PowerShell.Commands.SetContentCommand
Create
mymod.psm1
Create
.gitignore
Create
LICENSE.txt
Expand
LICENSE.txt
Create
en-US\about_mymod.help.txt
Expand
en-US\about_mymod.help.txt
Create
.vscode\tasks.json
PS C:\Users\dfinke\Documents\GitHub\Plaster\Examples\NewModuleTemplate> Stop-Transcript
**********************
Windows PowerShell transcript end
End time: 20160517085153
**********************
I am localizing the PlasterRessources.psd1 file and I have some remarks about it.
PlasterResources.psd1
The key 'ManifestNotValid_F1' is for a valid Xml file.
the key 'ManifestNotValidXml_F1' is for a not well-formed Xml file.
Key 'UnrecognizedAttribute_F2' -> Unrecognized manifest attribute {0} on element {1}.
Unrecognized manifest attribute is managed by the XSD.
May be rename it to 'UnrecognizedContentTypeAttribute_F2'
Likewise for 'UnrecognizedParameterType_F2' -> Unrecognized parameter type '{0}' on parameter name '{1}'.
The key 'UnrecognizedContentElement_F1','UnrecognizedParametersElement_F1' are managed by the XSD ?
May be rename it to 'NotImplementedElement' ?
InvokePlaster.ps1
I know is temporary, but 'Create' is not localized :
# TODO: Temporary - remove this when this function makes use of ProcessFile
WriteOperationStatus 'Create' (ConvertToDestinationRelativePath $dstPath)
TestPlasterManifest.ps1
Is it possible to save the original exception ?
Write-Error ($LocalizedData.ManifestNotValidXml_F1 -f $Path)
#Something like this
$Ex= (new-object System.ApplicationException(($LocalizedData.ManifestNotValidXml_F1 -f $Path),$_.Exception))
$Er=New-Object System.Management.Automation.ErrorRecord(
$Ex,
"InvalidFormat",
"InvalidData",
("[{0}]" -f $Path)
)
Write-Error -ErrorRecord $Er
PlasterManifest-v1.xsd
The following element is not yet documented (xs:documentation) :
<xs:attribute name="store" type="ptd:StoreFormatType"/>
The following xml is valid for Test-PlasterManifest
<?xml version="1.0" encoding="utf-8"?>
<plasterManifest>
<metadata>
<id>string</id>
<version>string</version>
<description>string</description>
<tags>string</tags>
</metadata>
<content/>
</plasterManifest>
Invoke-Plaster does nothing :-)
$PlasterParams = @{
TemplatePath = "$PWD"
DestinationPath='..\out'
}
Invoke-Plaster @PlasterParams
There are parts of the manifest that I haven't been able to figure out yet. I was hoping to get more information on certain portions. I was just noting everything as I was going through and trying to get a good understanding of everything. Figured I might get some feedback and maybe this could be added to the documentation once refined.
I tried to leave questions I had in bold. I think it would be beneficial to have a list of the Nodes and their potential values etc. It would also be nice to have a place for proposed or potential ones as well. I did see one potential issue that I am going to file a bug report for with the Module Manifest.
id
- Unique ID to for the Packagetitle
- Name of Package to Show in UIsdescription
- Description message for UIsversion
- Package versiontags
- I am assuming these will be searchable if there is eventually a gallery type store or when searching through your own templates?Define
or similar and they could so something like the following:<parameter name='CustomDateFormat' type='define' default="$(([DateTime]::Now).ToString('yyyyMMdd'))" store='true'/>
<parameter name='StaticVariable' type='define' default="NoPromptStaticVariableValue" store='true'/>
I am not exactly sure what you had decided on with Code Execution and how that would impact this. If a user needed access to something like a Custom DateTime format, they could create their own variable like above. You could also just define variables without prompts. These could also be defined in a config file, but do you plan to give the ability to define variables in a config file without them needing a prompt
types
and/or planned types
input
- Prompts user for inputchoice
- Prompts user to select 1 choicemulitchoice
- Prompts user to select 1 or multiple choicesrequired
- The Parameter is requiredstore
- I'm not quite sure yet, I am guessing this has something to do with stored variables in a config file that is not fully implemented yet. I didn't see anything in invokeplaster.ps1 at first glance.prompt
- What is prompted to the user. Is this Required? Can it be omitted? If omitted will the user still be prompted with a blank prompt?choice
or multichoice
Type. These are used with a $host.ui.PromptforChoice
dialogue that should work in editors as well.label
- Value Shown in Prompt to Userhelp
- Small Help Messahe explaining Choicevalue
- The Actual Valueid
- Potential Dependency IDs based on package metadata IDsfile
source
- Source File that will be copied.destination
- Destination where the file will be copiedcondition
- Condition that must be met for this to be invoked.encoding
- File encodingtemplate
- Templates will have Plaster Variables that will be replaced.newModuleManifest
- Creates a new Module Manifest for the Project.
destination
- Module psd1moduleVersion
- Package versionrootModule
- Module psm1author
- Author of the Modulecondition
- A little confused on the condition for newModuleManifest. $FileNode.condition
is referenced to define the condition, but newModuleManifest
is its own node and not a FileNode. If you add a condition to the Manifest, it will not respect it. I would need to test if creating a File Node for a Manifest and setting the condition to false would carry over to the newModuleManifest
Nodemodify
- Modifies Content with options for Conditions.
path
- Path to file being modifiedencoding
- Encoding of the File (Should this be validated?)condition
- Condition that must be met for this to be invoked.
Replacement
- Replaces content via pattern.This code does not work as expected :
<!-- Recursively copy all *files* into the corresponding directory structure under dest dir Recurse -->
<file source='RecurseTest\**'
destination='Recurse'
template='true'/>
Scaffold a PowerShell Module with the
Create FooUtils.psd1
Expand Recurse\README.md ********
Create Recurse\README.md ********
Create FooUtils.psm1
Create .gitignore
Expand LICENSE.txt
Create LICENSE.txt
See
c2d171b#diff-deaa637a88a56ef9e9a0eecb4a57f438
[edit]
May be change this line :
#function ExpandFileSourceSpec(
$srcPath = Join-Path $TemplatePath $parent
to
$srcPath = Join-Path $TemplatePath $srcRelPath
I try to create a profile for my project.
This file contain miscellaneous variables declaration, this variables are used inside a PSake script.
Today create a parameter/variable $Plaster requires a keyboard input.
<parameter name='ProjectName' type='input' store='text' prompt='Enter the project name'/>
<parameter name='Root' type='input' store='text' prompt='Enter the root of the project name'/>
<!-- assert : the xml parsing is sequential -->
<Parameter name='ProjectRoot' type='NoInput' Value='${PLASTER_PARAM_Root}\${PLASTER_PARAM_ProjectName}'/>
<parameter name='Setup' type='NoInput' Value='${PLASTER_PARAM_ProjectRoot}\Setup'/>
...
<file source='Profile\Profile.T.ps1'
destination='Profile\${PLASTER_PARAM_ProjectName}_profile.ps1'
template='true'/>
With Profile\Profile.T.ps1 :
#Profile for the project <%=${PLASTER_PARAM_ProjectName}%>'
param (
[Parameter(Mandatory=$false)]
[int]$Scope=1
)#param
New-Variable -Name "<%=${PLASTER_PARAM_ProjectName}%>Setup" -Value '<%=${PLASTER_PARAM_Setup}%>' -Option Constant -Scope $Scopee
...
Why does this have XML configuration? What is this, the 1990s? 😉
Have you plan to manage this cases ?
HKLM:\> Invoke-Plaster -TemplatePath . -Destination ..\Out -verbose
#Error : PathNotFound,Expand-Archive
..Plaster-master\Examples\NewModuleTemplate > Invoke-Plaster -TemplatePath . -Destination ..\Out -verbose
#With PLASTER_PARAM_ModuleName = Aux
#Error : FileOpenFailure,Microsoft.PowerShell.Commands.NewModuleManifestCommand
..Plaster-master\Examples\NewModuleTemplate > Invoke-Plaster -TemplatePath . -Destination ..\Aux -verbose
#Error : CreateDirectoryArgumentError,Microsoft.PowerShell.Commands.NewItemCommand
Is the 'File' element can manage this case ?
Import-Module Plaster
cd G:\PS\Plaster
md FabrikamNewModule
$PlasterParams = @{
TemplatePath = "$PWD\FabrikamNewModule"
Destination = "$PWD\Out\FabrikamNewModule"
ModuleName = 'MapPSCode'
FullName = 'John Q. Doe'
Version = '1'
Options = 'PSake','Pester','Git',
Editor = 'VSCode'
License = 'MIT'
}
$Directories=@(
'Demos',
'en-US',
'fr-FR',
'Help',
'Tests',
'Tools',
'Bin',
'FormatData',
'Modules',
'Setup',
'TypeData'
)
md $PlasterParams.Destination
Push-Location $PlasterParams.Destination
$Directories|
Foreach {
md $_ -Verbose -ea SilentlyContinue > $null
}
Pop-Location
#Invoke-Plaster @PlasterParams ...
Hi guys,
I just saw the news about the Plaster project. Very cool concept! I haven't tried it yet, but had some ideas immediately come to mind that I wanted to share, so typing frantically while the mental iron is hot! :)
First, I personally feel that there are many not-so-obvious module extension points in PowerShell that people overlook that are worth considering. One of my personal favorite extension points is to extend functionality within PowerShell automatically via inclusion of a file structure underneath a particular module. Modules are already the vehicle for broad distribution of PowerShell artifacts, and we have PackageManagement plus the Gallery to facilitate that. Why not then have a tool like Plaster automatically discover Plaster templates that are located in any module in a Plaster folder that is under the root folder of a module? The same could be done for Pester, with tests being automatically discovered and invocable by name if they are in a Tests folder (great for reuse of tests across modules). Or DSC even, where DSC resources/configurations could be defined as an extension to a module (makes sense when both use resources in the same module). But those ships have sailed, and this ship is brand new. I personally love the idea of new PowerShell extensions that bring some new file structure or DSL to the mix being located in modules themselves, because I get all of the module goodness I'm already used to: versioning, discoverability, auto-load, PowerShellGet support, etc.
For example, I could create a module (SnippetPx -- already exists even) that includes a Plaster subfolder under the root that defines templates that can be used in Plaster. Then if that module was installed but not loaded, I could invoke Get-PlasterTemplate and get a list of all Plaster templates that are located in modules on my system. Templates can be overridden (sometimes a user may want to override a specific template) by leveraging their WindowsPowerShell\Plaster path. I already set this up for SnippetPx so that I could have a discovery mechanism that just lights up new functionality when a module containing Snippet definitions is on the system with no additional work to manage the registration/location/path/etc., and I've been meaning to share that code as part of a broader PowerShell extension library.
Going further (again, I haven't tried Plaster yet), maybe under my Plaster folder in my module I have two subfolders: templates and extensions. In templates I can offer Plaster templates that are ready to use. In extensions can I have extensions to the Plaster engine, allowing me to define Snippets as a feature that can be used in Plaster and providing whatever metadata/commands are necessary to make that happen, and my templates can automatically use the extensions in my module. If PowerShell commands are involved in those extensions, I can define them within my module and everything will just work because the entire package is just a module.
Thinking beyond that, DSLs come to mind as a nice way to consume templates. Hashtables/splatting works, but it might be nice to see a mini-DSL that can be used in place of Invoke-Plaster or that perhaps offers features that go further than Invoke-Plaster that provides interesting options that users may want. DSLs are great for structured output (generating files is a great example), and there may be some interesting ideas here that can be best realized by using a DSL.
I'm happy to discuss these at length here or in the PowerShell Slack channel, or demonstrate what I'm talking about via a Skype session anytime. Just say the word and I'll make time for it in my schedule.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.