From 96fa56e74aa34e1f661925c2615f329b45ab6e7b Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 30 Mar 2021 01:16:27 +0200 Subject: [PATCH 1/8] minor fixes & updates to template object model --- PSModuleDevelopment/changelog.md | 7 +- PSModuleDevelopment/en-us/strings.psd1 | 3 + .../functions/templating/Get-PSMDTemplate.ps1 | 6 +- .../templating/Invoke-PSMDTemplate.ps1 | 193 +++++++----------- .../internal/configurations/debug.ps1 | 2 +- .../internal/configurations/template.ps1 | 2 +- .../internal/configurations/utility.ps1 | 2 +- .../PSModuleDevelopment.csproj | 8 + .../Template/Parameter/ParameterBase.cs | 27 +++ .../Template/Parameter/ParameterPrompt.cs | 58 ++++++ .../Template/Parameter/ParameterScript.cs | 76 +++++++ .../Template/Parameter/ScriptExecutionTime.cs | 37 ++++ .../PSModuleDevelopment/Template/Template.cs | 6 + .../Template/TemplateInfo.cs | 5 + 14 files changed, 301 insertions(+), 131 deletions(-) create mode 100644 library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterBase.cs create mode 100644 library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterPrompt.cs create mode 100644 library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs create mode 100644 library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ScriptExecutionTime.cs diff --git a/PSModuleDevelopment/changelog.md b/PSModuleDevelopment/changelog.md index da0083f..bff1ac4 100644 --- a/PSModuleDevelopment/changelog.md +++ b/PSModuleDevelopment/changelog.md @@ -1,11 +1,16 @@ # Changelog +## ??? + +- Fix: TemplateStore - default path iss invalid on MAC (#136) +- Fix: Invoke-PSMDTemplate - unreliable string replacement through -replace operator (#113) +- Fix: Publish-PSMDScriptFile - insufficient exclude paths (#138; @Callidus2000) + ## 2.2.9.106 (September 10th, 2020) - New: Convert-PSMDMessage - Converts a file's use of PSFramework messages to strings. - Upd: Export-PSMDString - Adding support for Test-PSFShouldProcess. - Fix: Export-PSMDString - Failed with splatting detection -- Fix: Publish-PSMDScriptFile - insufficient exclude paths (#138; @Callidus2000) ## 2.2.8.104 (July 26th, 2020) diff --git a/PSModuleDevelopment/en-us/strings.psd1 b/PSModuleDevelopment/en-us/strings.psd1 index 65c294e..d950284 100644 --- a/PSModuleDevelopment/en-us/strings.psd1 +++ b/PSModuleDevelopment/en-us/strings.psd1 @@ -4,6 +4,9 @@ 'Get-PSMDFileCommand.SyntaxError' = 'Syntax error in file: {0}' # $pathItem + 'Invoke-PSMDTemplate.Template.NotFound' = 'Unable to find template data for "{0}"' # $TemplateName + 'Invoke-PSMDTemplate.Invoking' = 'Creating file/project from template {0}' # $item + 'MeasurePSMDLinesOfCode.Processing' = 'Processing Path: {0}' # $fileItem 'Publish-PSMDScriptFile.Module.Saving' = 'Saving module {0} from repository {1}' # $moduleName, (Get-PSFConfigValue -FullName 'PSModuleDevelopment.Script.StagingRepository') diff --git a/PSModuleDevelopment/functions/templating/Get-PSMDTemplate.ps1 b/PSModuleDevelopment/functions/templating/Get-PSMDTemplate.ps1 index 54e6168..00df036 100644 --- a/PSModuleDevelopment/functions/templating/Get-PSMDTemplate.ps1 +++ b/PSModuleDevelopment/functions/templating/Get-PSMDTemplate.ps1 @@ -84,8 +84,6 @@ begin { - Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param' - $prospects = @() } process @@ -97,7 +95,7 @@ foreach ($info in $templateInfos) { - $data = Import-Clixml $info.FullName + $data = Import-PSFClixml $info.FullName $data.Path = $info.FullName -replace '\.Info\.xml$','.xml' $prospects += $data } @@ -117,7 +115,7 @@ foreach ($info in $templateInfos) { - $data = Import-Clixml $info.FullName + $data = Import-PSFClixml $info.FullName $data.Path = $info.FullName -replace '-Info\.xml$', '.xml' $data.Store = $item.Name $prospects += $data diff --git a/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 b/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 index fea81e3..970e73e 100644 --- a/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 +++ b/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 @@ -1,5 +1,4 @@ -function Invoke-PSMDTemplate -{ +function Invoke-PSMDTemplate { <# .SYNOPSIS Creates a project/file from a template. @@ -99,6 +98,7 @@ $Path, [Parameter(Position = 2)] + [PSFramework.Validation.PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] [string] $OutPath = (Get-PSFConfigValue -FullName 'PSModuleDevelopment.Template.OutPath' -Fallback "."), @@ -128,52 +128,31 @@ $EnableException ) - begin - { - #region Validate output path - try - { - $resolvedPath = Resolve-Path $OutPath -ErrorAction Stop - if (($resolvedPath | Measure-Object).Count -ne 1) - { - throw "Cannot resolve $OutPath to a single folder" - } - if ($resolvedPath.Provider -notlike "*FileSystem") - { - throw "Path $OutPath was not recognized as a filesystem path" - } - } - catch - { - Stop-PSFFunction -Message "Could not resolve output path to a valid folder: $OutPath" -EnableException $EnableException -ErrorRecord $_ -Tag 'fail', 'path', 'validate' - return - } - #endregion Validate output path - + begin { $templates = @() - switch ($PSCmdlet.ParameterSetName) - { + switch ($PSCmdlet.ParameterSetName) { 'NameStore' { $templates = Get-PSMDTemplate -TemplateName $TemplateName -Store $Store } 'NamePath' { $templates = Get-PSMDTemplate -TemplateName $TemplateName -Path $Path } } + if ($TemplateName -and -not $templates) { + Stop-PSFFunction -String 'Invoke-PSMDTemplate.Template.NotFound' -StringValues $TemplateName -EnableException $EnableException -Cmdlet $PSCmdlet + return + } #region Parameter Processing if (-not $Parameters) { $Parameters = @{ } } if ($Name) { $Parameters["Name"] = $Name } - foreach ($config in (Get-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.ParameterDefault.*')) - { + foreach ($config in (Get-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.ParameterDefault.*')) { $cfgName = $config.Name -replace '^.+\.([^\.]+)$', '$1' - if (-not $Parameters.ContainsKey($cfgName)) - { + if (-not $Parameters.ContainsKey($cfgName)) { $Parameters[$cfgName] = $config.Value } } #endregion Parameter Processing #region Helper function - function Invoke-Template - { + function Invoke-Template { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( @@ -200,16 +179,13 @@ ) Write-PSFMessage -Level Verbose -Message "Processing template $($item)" -Tag 'template', 'invoke' -FunctionName Invoke-PSMDTemplate - $templateData = Import-Clixml -Path $Template.Path -ErrorAction Stop + $templateData = Import-PSFClixml -Path $Template.Path -ErrorAction Stop #region Process Parameters - foreach ($parameter in $templateData.Parameters) - { + foreach ($parameter in $templateData.Parameters) { if (-not $parameter) { continue } - if (-not $Parameters.ContainsKey($parameter)) - { + if (-not $Parameters.ContainsKey($parameter)) { if ($Silent) { throw "Parameter not specified: $parameter" } - try - { + try { $value = Read-Host -Prompt "Enter value for parameter '$parameter'" -ErrorAction Stop $Parameters[$parameter] = $value } @@ -221,17 +197,13 @@ #region Scripts $scriptParameters = @{ } - if (-not $Raw) - { - foreach ($scriptParam in $templateData.Scripts.Values) - { + if (-not $Raw) { + foreach ($scriptParam in $templateData.Scripts.Values) { if (-not $scriptParam) { continue } try { $scriptParameters[$scriptParam.Name] = "$([scriptblock]::Create($scriptParam.StringScript).Invoke())" } - catch - { + catch { if ($Silent) { throw (New-Object System.Exception("Scriptblock $($scriptParam.Name) failed during execution: $_", $_.Exception)) } - else - { + else { Write-PSFMessage -Level Warning -Message "Scriptblock $($scriptParam.Name) failed during execution. Please specify a custom value or use CTRL+C to terminate creation" -ErrorRecord $_ -FunctionName "Invoke-PSMDTemplate" -ModuleName 'PSModuleDevelopment' $scriptParameters[$scriptParam.Name] = Read-Host -Prompt "Value for script $($scriptParam.Name)" } @@ -240,17 +212,14 @@ } #endregion Scripts - switch ($templateData.Type.ToString()) - { + switch ($templateData.Type.ToString()) { #region File "File" { - foreach ($child in $templateData.Children) - { + foreach ($child in $templateData.Children) { Write-TemplateItem -Item $child -Path $OutPath -Encoding $Encoding -ParameterFlat $Parameters -ParameterScript $scriptParameters -Raw $Raw } - if ($Raw -and $templateData.Scripts.Values) - { + if ($Raw -and $templateData.Scripts.Values) { $templateData.Scripts.Values | Export-Clixml -Path (Join-Path $OutPath "_PSMD_ParameterScripts.xml") } } @@ -260,34 +229,28 @@ "Project" { #region Resolve output folder - if (-not $NoFolder) - { - if ($Parameters["Name"]) - { + if (-not $NoFolder) { + if ($Parameters["Name"]) { $projectName = $Parameters["Name"] $projectFullName = Join-Path $OutPath $projectName - if ((Test-Path $projectFullName) -and (-not $Force)) - { + if ((Test-Path $projectFullName) -and (-not $Force)) { throw "Project root folder already exists: $projectFullName" } $newFolder = New-Item -Path $OutPath -Name $Parameters["Name"] -ItemType Directory -ErrorAction Stop -Force } - else - { + else { throw "Parameter Name is needed to create a project without setting the -NoFolder parameter!" } } else { $newFolder = Get-Item $OutPath } #endregion Resolve output folder - foreach ($child in $templateData.Children) - { + foreach ($child in $templateData.Children) { Write-TemplateItem -Item $child -Path $newFolder.FullName -Encoding $Encoding -ParameterFlat $Parameters -ParameterScript $scriptParameters -Raw $Raw } #region Write Config File (Raw) - if ($Raw) - { + if ($Raw) { $guid = [System.Guid]::NewGuid().ToString() $optionsTemplate = @" @{ @@ -299,11 +262,9 @@ þþþPLACEHOLDER-$($guid)þþþ } "@ - if ($params = $templateData.Scripts.Values) - { + if ($params = $templateData.Scripts.Values) { $list = @() - foreach ($param in $params) - { + foreach ($param in $params) { $list += @" $($param.Name) = { $($param.StringScript) @@ -312,9 +273,8 @@ } $optionsTemplate = $optionsTemplate -replace "þþþPLACEHOLDER-$($guid)þþþ", ($list -join "`n`n") } - else - { - $optionsTemplate = $optionsTemplate -replace "þþþPLACEHOLDER-$($guid)þþþ","" + else { + $optionsTemplate = $optionsTemplate -replace "þþþPLACEHOLDER-$($guid)þþþ", "" } $configFile = Join-Path $newFolder.FullName "PSMDTemplate.ps1" @@ -326,8 +286,7 @@ } } - function Write-TemplateItem - { + function Write-TemplateItem { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( @@ -350,46 +309,37 @@ $Raw ) - Write-PSFMessage -Level Verbose -Message "Creating file: $($Item.Name) ($($Item.RelativePath))" -FunctionName Invoke-PSMDTemplate -ModuleName PSModuleDevelopment -Tag 'create','template' + Write-PSFMessage -Level Verbose -Message "Creating file: $($Item.Name) ($($Item.RelativePath))" -FunctionName Invoke-PSMDTemplate -ModuleName PSModuleDevelopment -Tag 'create', 'template' $identifier = $Item.Identifier $isFile = $Item.GetType().Name -eq 'TemplateItemFile' #region File - if ($isFile) - { + if ($isFile) { $fileName = $Item.Name - if (-not $Raw) - { - foreach ($param in $Item.FileSystemParameterFlat) - { - $fileName = $fileName -replace "$($identifier)$([regex]::Escape($param))$($identifier)",$ParameterFlat[$param] + if (-not $Raw) { + foreach ($param in $Item.FileSystemParameterFlat) { + $fileName = $fileName.Replace("$($identifier)$($param)$($identifier)", $ParameterFlat[$param], $true, $null) } - foreach ($param in $Item.FileSystemParameterScript) - { - $fileName = $fileName -replace "$($identifier)!$([regex]::Escape($param))!$($identifier)", $ParameterScript[$param] + foreach ($param in $Item.FileSystemParameterScript) { + $fileName = $fileName.Replace("$($identifier)$($param)$($identifier)", $ParameterScript[$param], $true, $null) } } $destPath = Join-Path $Path $fileName - if ($Item.PlainText) - { + if ($Item.PlainText) { $text = $Item.Value - if (-not $Raw) - { - foreach ($param in $Item.ContentParameterFlat) - { - $text = $text -replace "$($identifier)$([regex]::Escape($param))$($identifier)", $ParameterFlat[$param] + if (-not $Raw) { + foreach ($param in $Item.ContentParameterFlat) { + $text = $text.Replace("$($identifier)$($param)$($identifier)", $ParameterFlat[$param], $true, $null) } - foreach ($param in $Item.ContentParameterScript) - { - $text = $text -replace "$($identifier)!$([regex]::Escape($param))!$($identifier)", $ParameterScript[$param] + foreach ($param in $Item.ContentParameterScript) { + $text = $text.Replace("$($identifier)!$($param)!$($identifier)", $ParameterScript[$param], $true, $null) } } [System.IO.File]::WriteAllText($destPath, $text, $Encoding) } - else - { + else { $bytes = [System.Convert]::FromBase64String($Item.Value) [System.IO.File]::WriteAllBytes($destPath, $bytes) } @@ -397,24 +347,19 @@ #endregion File #region Folder - else - { + else { $folderName = $Item.Name - if (-not $Raw) - { - foreach ($param in $Item.FileSystemParameterFlat) - { + if (-not $Raw) { + foreach ($param in $Item.FileSystemParameterFlat) { $folderName = $folderName -replace "$($identifier)$([regex]::Escape($param))$($identifier)", $ParameterFlat[$param] } - foreach ($param in $Item.FileSystemParameterScript) - { + foreach ($param in $Item.FileSystemParameterScript) { $folderName = $folderName -replace "$($identifier)!$([regex]::Escape($param))!$($identifier)", $ParameterScript[$param] } } $folder = New-Item -Path $Path -Name $folderName -ItemType Directory - foreach ($child in $Item.Children) - { + foreach ($child in $Item.Children) { Write-TemplateItem -Item $child -Path $folder.FullName -Encoding $Encoding -ParameterFlat $ParameterFlat -ParameterScript $ParameterScript -Raw $Raw } } @@ -422,25 +367,27 @@ } #endregion Helper function } - process - { + process { if (Test-PSFFunctionInterrupt) { return } - foreach ($item in $Template) - { - if ($PSCmdlet.ShouldProcess($item, "Invoking template")) - { - try { Invoke-Template -Template $item -OutPath $resolvedPath.ProviderPath -NoFolder $NoFolder -Encoding $Encoding -Parameters $Parameters.Clone() -Raw $Raw -Silent $Silent } - catch { Stop-PSFFunction -Message "Failed to invoke template $($item)" -EnableException $EnableException -ErrorRecord $_ -Target $item -Tag 'fail', 'template', 'invoke' -Continue } - } + $invokeParam = @{ + Parameters = $Parameters.Clone() + OutPath = Resolve-PSFPath -Path $OutPath + NoFolder = $NoFolder + Encoding = $Encoding + Raw = $Raw + Silent = $Silent } - foreach ($item in $templates) - { - if ($PSCmdlet.ShouldProcess($item, "Invoking template")) - { - try { Invoke-Template -Template $item -OutPath $resolvedPath.ProviderPath -NoFolder $NoFolder -Encoding $Encoding -Parameters $Parameters.Clone() -Raw $Raw -Silent $Silent } - catch { Stop-PSFFunction -Message "Failed to invoke template $($item)" -EnableException $EnableException -ErrorRecord $_ -Target $item -Tag 'fail', 'template', 'invoke' -Continue } - } + + foreach ($item in $Template) { + Invoke-PSFProtectedCommand -ActionString 'Invoke-PSMDTemplate.Invoking' -ActionStringValues $item -Target $item -ScriptBlock { + Invoke-Template @invokeParam -Template $item + } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue + } + foreach ($item in $templates) { + Invoke-PSFProtectedCommand -ActionString 'Invoke-PSMDTemplate.Invoking' -ActionStringValues $item -Target $item -ScriptBlock { + Invoke-Template @invokeParam -Template $item + } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue } } } diff --git a/PSModuleDevelopment/internal/configurations/debug.ps1 b/PSModuleDevelopment/internal/configurations/debug.ps1 index 51e68c0..d6999b3 100644 --- a/PSModuleDevelopment/internal/configurations/debug.ps1 +++ b/PSModuleDevelopment/internal/configurations/debug.ps1 @@ -1 +1 @@ -Set-PSFConfig -Module PSModuleDevelopment -Name 'Debug.ConfigPath' -Value "$($path_FileUserShared)\InfernalAssociates\PowerShell\PSModuleDevelopment\config.xml" -Initialize -Validation string -Description 'The path to where the module debugging information is being stored. Used in the *-PSMDModuleDebug commands.' \ No newline at end of file +Set-PSFConfig -Module PSModuleDevelopment -Name 'Debug.ConfigPath' -Value (Join-Path -Path (Get-PSFPath -Name AppData) -ChildPath "InfernalAssociates/PowerShell/PSModuleDevelopment/config.xml") -Initialize -Validation string -Description 'The path to where the module debugging information is being stored. Used in the *-PSMDModuleDebug commands.' \ No newline at end of file diff --git a/PSModuleDevelopment/internal/configurations/template.ps1 b/PSModuleDevelopment/internal/configurations/template.ps1 index f1065ad..79a6981 100644 --- a/PSModuleDevelopment/internal/configurations/template.ps1 +++ b/PSModuleDevelopment/internal/configurations/template.ps1 @@ -9,7 +9,7 @@ Set-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.ParameterDefault.Com Set-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.BinaryExtensions' -Value @('.dll', '.exe', '.pdf', '.doc', '.docx', '.xls', '.xlsx') -Initialize -Description "When creating a template, files with these extensions will be included as raw bytes and not interpreted for parameter insertion." # Define the default store. To add more stores, just add a similar setting with a different last name segment -Set-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.Store.Default' -Value "$path_FileUserShared/WindowsPowerShell/PSModuleDevelopment/Templates" -Initialize -Validation "string" -Description "Path to the default directory where PSModuleDevelopment will store its templates. You can add additional stores by creating the same setting again, only changing the last name segment to a new name and configuring a separate path." +Set-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.Store.Default' -Value (Join-Path -Path (Get-PSFPath -Name AppData) -ChildPath "WindowsPowerShell/PSModuleDevelopment/Templates") -Initialize -Validation "string" -Description "Path to the default directory where PSModuleDevelopment will store its templates. You can add additional stores by creating the same setting again, only changing the last name segment to a new name and configuring a separate path." Set-PSFConfig -Module 'PSModuleDevelopment' -Name 'Template.Store.PSModuleDevelopment' -Value "$script:ModuleRoot/internal/templates" -Initialize -Validation "string" -Description "Path to the templates shipped in PSModuleDevelopment" # Define the default path to create from templates in diff --git a/PSModuleDevelopment/internal/configurations/utility.ps1 b/PSModuleDevelopment/internal/configurations/utility.ps1 index 6bf4866..bddf4d2 100644 --- a/PSModuleDevelopment/internal/configurations/utility.ps1 +++ b/PSModuleDevelopment/internal/configurations/utility.ps1 @@ -1,5 +1,5 @@ Set-PSFConfig -Module PSModuleDevelopment -Name 'Module.Path' -Value "" -Initialize -Validation "string" -Handler { } -Description "The path to the module currently under development. Used as default path by commnds that work within a module directory." -Set-PSFConfig -Module PSModuleDevelopment -Name 'Package.Path' -Value "$env:TEMP" -Initialize -Validation "string" -Description "The default output path when exporting a module into a nuget package." +Set-PSFConfig -Module PSModuleDevelopment -Name 'Package.Path' -Value (Get-PSFPath -Name Temp) -Initialize -Validation "string" -Description "The default output path when exporting a module into a nuget package." Set-PSFConfig -Module PSModuleDevelopment -Name 'Find.DefaultExtensions' -Value '^\.ps1$|^\.psd1$|^\.psm1$|^\.cs$' -Initialize -Validation string -Description 'The pattern to use to select files to scan when using Find-PSMDFileContent.' Set-PSFConfig -Module PSModuleDevelopment -Name "ShowSyntax.ParmsNotFound" -Value "Red" -Initialize -Validation "string" -Handler { } -Description "The color to be used for the parameters that could not be found." diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/PSModuleDevelopment.csproj b/library/PSModuleDevelopment/PSModuleDevelopment/PSModuleDevelopment.csproj index 25f15a9..2673b30 100644 --- a/library/PSModuleDevelopment/PSModuleDevelopment/PSModuleDevelopment.csproj +++ b/library/PSModuleDevelopment/PSModuleDevelopment/PSModuleDevelopment.csproj @@ -31,6 +31,10 @@ 4 + + ..\..\..\..\psframework\PSFramework\bin\PSFramework.dll + False + @@ -54,7 +58,11 @@ + + + + diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterBase.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterBase.cs new file mode 100644 index 0000000..451f9ce --- /dev/null +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterBase.cs @@ -0,0 +1,27 @@ +using System; + +namespace PSModuleDevelopment.Template.Parameter +{ + /// + /// Base class for all kinds of parameters gen 2+ + /// + [Serializable] + public abstract class ParameterBase + { + /// + /// Name of the parameter + /// + public string Name; + + /// + /// Description of the parameter + /// + public string Description; + + /// + /// Get the value associated with this parameter + /// + /// The value to insert into the artifact generated from the template + public abstract string GetValue(); + } +} diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterPrompt.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterPrompt.cs new file mode 100644 index 0000000..c96803e --- /dev/null +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterPrompt.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace PSModuleDevelopment.Template.Parameter +{ + /// + /// A template parameter where the user is prompted for input. + /// + [Serializable] + public class ParameterPrompt : ParameterBase + { + /// + /// The value provided by the user + /// + public string Value; + + /// + /// List of legal values to provide + /// + public List ValidateSet = new List(); + + /// + /// A validation pattern that needs to be met. + /// + public string ValidatePattern; + + /// + /// An error description that will be shown if the user provides invalid input to a parameter with pattern validation. + /// + public string PatternError; + + /// + /// Test whether the input meets the validation rules + /// + /// The value to test + /// Whether the value is valid. + public bool TestValue(string Value) + { + if (ValidateSet.Count > 0 && !ValidateSet.Contains(Value, StringComparer.InvariantCultureIgnoreCase)) + return false; + if (!String.IsNullOrEmpty(ValidatePattern) && !Regex.IsMatch(Value, ValidatePattern)) + return false; + + return true; + } + + /// + /// Return the value specified by the user. + /// + /// The value specified by the user + public override string GetValue() + { + return Value; + } + } +} diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs new file mode 100644 index 0000000..39b8eff --- /dev/null +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +using PSFramework.Utility; + +namespace PSModuleDevelopment.Template.Parameter +{ + /// + /// Parameter type executing + /// + public class ParameterScript : ParameterBase + { + /// + /// The scriptblock to execute. + /// Wrapped as string for serialization purposes. + /// + public string ScriptBlock + { + get + { + if (_ScriptBlock == null) + return ""; + return _ScriptBlock.ToString(); + } + set + { + _ScriptBlock = new PsfScriptBlock(System.Management.Automation.ScriptBlock.Create(value)); + } + } + private PsfScriptBlock _ScriptBlock; + + /// + /// The value of the scriptblock. + /// Populated by the GetValue() method usually called with the "StartUp" timing. + /// + public string Value; + + /// + /// When exactly during the template process should this scriptblock be executed? + /// + public ScriptExecutionTime Timing = ScriptExecutionTime.StartUp; + + /// + /// Setting this to true will cause the Invoke-PSMDTemplate command to omit inserting values for the + /// + public bool SkipInsert; + + /// + /// Returns the string value of the scriptblock by executing it! + /// + /// The string value of the scriptblock by executing it! + public override string GetValue() + { + if (String.IsNullOrEmpty(Value)) + try { Value = (string)LanguagePrimitives.ConvertTo(_ScriptBlock.InvokeEx(true, true, false), typeof(string)); } + catch (Exception e) { Value = $""; } + return Value; + } + + /// + /// Execute the scriptblock "Just-in-time" during either PreItemCreation or PostItemCreation Timing. + /// + /// The file/directory info object of the file recently or about to be created + /// Returns a string value resulting from the scriptblock to insert + public string GetInTimeValue(FileSystemInfo Info) + { + try { return (string)LanguagePrimitives.ConvertTo(_ScriptBlock.InvokeEx(Info, true, true, false), typeof(string)); } + catch (Exception e) { return $""; } + } + } +} diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ScriptExecutionTime.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ScriptExecutionTime.cs new file mode 100644 index 0000000..e8a797e --- /dev/null +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ScriptExecutionTime.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PSModuleDevelopment.Template.Parameter +{ + /// + /// When will a specific scriptblock parameter be executed? + /// + public enum ScriptExecutionTime + { + /// + /// Executed when starting the overall template invocation + /// + StartUp = 1, + + /// + /// Executed before an individual item using it is created. + /// Values will be inserted into the file-content before writing to disk if applicable. + /// + PreItemCreation = 2, + + /// + /// Executed after the individual item using it has been created. + /// Output will be discarded, but scriptblock will receive path of file / folder. + /// + PostItemCreation = 3, + + /// + /// Executed after the entire project has been written. + /// Enables post-processing. + /// + Conclusion = 4 + } +} diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs index 77b7ff8..6682b15 100644 --- a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs @@ -63,6 +63,11 @@ public class Template /// public List Children = new List(); + /// + /// What design generation is the template? + /// + public int Generation = 1; + /// /// Returns the template digest used as index file. /// @@ -78,6 +83,7 @@ public TemplateInfo ToTemplateInfo() info.Tags = Tags; info.Type = Type; info.Version = Version; + info.Generation = Generation; return info; } diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/TemplateInfo.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/TemplateInfo.cs index 586bc40..26f956e 100644 --- a/library/PSModuleDevelopment/PSModuleDevelopment/Template/TemplateInfo.cs +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/TemplateInfo.cs @@ -62,6 +62,11 @@ public class TemplateInfo /// public string Path; + /// + /// What template generation is this file? + /// + public int Generation = 1; + /// /// The version-qualified name of the template /// From d2ff81ef93364168c4ecd08df8098b278b7290ef Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 20 Apr 2021 22:49:13 +0200 Subject: [PATCH 2/8] adding build system --- PSModuleDevelopment/PSModuleDevelopment.psd1 | 111 ++++++++------- PSModuleDevelopment/en-us/strings.psd1 | 2 + .../functions/build/Get-PSMDBuildAction.ps1 | 12 ++ .../functions/build/Get-PSMDBuildArtifact.ps1 | 20 +++ .../functions/build/Get-PSMDBuildProject.ps1 | 33 +++++ .../functions/build/Get-PSMDBuildStep.ps1 | 23 +++ .../build/Invoke-PSMDBuildProject.ps1 | 132 ++++++++++++++++++ .../functions/build/New-PSMDBuildProject.ps1 | 42 ++++++ .../build/Publish-PSMDBuildArtifact.ps1 | 24 ++++ .../build/Register-PSMDBuildAction.ps1 | 26 ++++ .../build/Remove-PSMDBuildArtifact.ps1 | 15 ++ .../build/Select-PSMDBuildProject.ps1 | 20 +++ .../functions/build/Set-PSMDBuildStep.ps1 | 67 +++++++++ .../buildActions/copy-item.action.ps1 | 53 +++++++ .../buildActions/new-pssession.action.ps1 | 43 ++++++ .../buildActions/remove-item.action.ps1 | 56 ++++++++ .../buildActions/remove-pssession.action.ps1 | 47 +++++++ .../internal/configurations/build.ps1 | 1 + .../internal/scripts/postimport.ps1 | 5 + .../internal/scripts/variables.ps1 | 6 +- .../internal/tepp/build.tepp.ps1 | 3 + .../xml/PSModuleDevelopment.Format.ps1xml | 39 ++++++ build/filesAfter.txt | 1 + 23 files changed, 731 insertions(+), 50 deletions(-) create mode 100644 PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 create mode 100644 PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 create mode 100644 PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 create mode 100644 PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 create mode 100644 PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 create mode 100644 PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 create mode 100644 PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 create mode 100644 PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 create mode 100644 PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 create mode 100644 PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 create mode 100644 PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 create mode 100644 PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 create mode 100644 PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 create mode 100644 PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 create mode 100644 PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 create mode 100644 PSModuleDevelopment/internal/configurations/build.ps1 create mode 100644 PSModuleDevelopment/internal/tepp/build.tepp.ps1 diff --git a/PSModuleDevelopment/PSModuleDevelopment.psd1 b/PSModuleDevelopment/PSModuleDevelopment.psd1 index af8c7bb..b9dcd6b 100644 --- a/PSModuleDevelopment/PSModuleDevelopment.psd1 +++ b/PSModuleDevelopment/PSModuleDevelopment.psd1 @@ -27,7 +27,7 @@ # Modules that must be imported into the global environment prior to importing # this module RequiredModules = @( - @{ ModuleName = 'PSFramework'; ModuleVersion = '1.4.149' } + @{ ModuleName = 'PSFramework'; ModuleVersion = '1.6.198' } @{ ModuleName = 'string'; ModuleVersion = '0.6.1' } ) @@ -50,45 +50,57 @@ # Functions to export from this module FunctionsToExport = @( - 'Convert-PSMDMessage', - 'Expand-PSMDTypeName', - 'Export-PSMDString', - 'Find-PSMDFileContent', - 'Find-PSMDType', - 'Format-PSMDParameter', - 'Get-PSMDArgumentCompleter', - 'Get-PSMDAssembly', - 'Get-PSMDConstructor', - 'Get-PSMDFileCommand', - 'Get-PSMDHelp', - 'Get-PSMDMember', - 'Get-PSMDModuleDebug', - 'Get-PSMDTemplate', - 'Import-PSMDModuleDebug', - 'Invoke-PSMDTemplate', - 'Measure-PSMDCommand', - 'Measure-PSMDLinesOfCode', - 'New-PSMDDotNetProject', - 'New-PSMDHeader', - 'New-PSMDFormatTableDefinition', - 'New-PSMDModuleNugetPackage', - 'New-PSMDTemplate', - 'New-PssModuleProject', - 'Publish-PSMDScriptFile', - 'Publish-PSMDStagedModule', - 'Read-PSMDScript', - 'Remove-PSMDModuleDebug', - 'Remove-PSMDTemplate', - 'Rename-PSMDParameter', - 'Restart-PSMDShell', - 'Search-PSMDPropertyValue', - 'Set-PSMDEncoding', - 'Set-PSMDModuleDebug', - 'Set-PSMDCmdletBinding', - 'Set-PSMDModulePath', - 'Set-PSMDParameterHelp', - 'Set-PSMDStagingRepository', - 'Show-PSMDSyntax', + 'Convert-PSMDMessage' + 'Expand-PSMDTypeName' + 'Export-PSMDString' + 'Find-PSMDFileContent' + 'Find-PSMDType' + 'Format-PSMDParameter' + 'Get-PSMDArgumentCompleter' + 'Get-PSMDAssembly' + 'Get-PSMDBuildAction' + 'Get-PSMDBuildArtifact' + 'Get-PSMDBuildProject' + 'Get-PSMDBuildStep' + 'Get-PSMDConstructor' + 'Get-PSMDFileCommand' + 'Get-PSMDHelp' + 'Get-PSMDMember' + 'Get-PSMDModuleDebug' + 'Get-PSMDTemplate' + 'Import-PSMDModuleDebug' + 'Invoke-PSMDBuildProject' + 'Invoke-PSMDTemplate' + 'Measure-PSMDCommand' + 'Measure-PSMDLinesOfCode' + 'New-PSMDBuildProject' + 'New-PSMDDotNetProject' + 'New-PSMDFormatTableDefinition' + 'New-PSMDHeader' + 'New-PSMDModuleNugetPackage' + 'New-PSMDTemplate' + 'New-PssModuleProject' + 'Publish-PSMDBuildArtifact' + 'Publish-PSMDScriptFile' + 'Publish-PSMDStagedModule' + 'Read-PSMDScript' + 'Register-PSMDBuildAction' + 'Remove-PSMDBuildArtifact' + 'Remove-PSMDBuildProject' + 'Remove-PSMDModuleDebug' + 'Remove-PSMDTemplate' + 'Rename-PSMDParameter' + 'Restart-PSMDShell' + 'Search-PSMDPropertyValue' + 'Select-PSMDBuildProject' + 'Set-PSMDBuildStep' + 'Set-PSMDCmdletBinding' + 'Set-PSMDEncoding' + 'Set-PSMDModuleDebug' + 'Set-PSMDModulePath' + 'Set-PSMDParameterHelp' + 'Set-PSMDStagingRepository' + 'Show-PSMDSyntax' 'Split-PSMDScriptFile' ) @@ -99,15 +111,16 @@ # VariablesToExport = '' # Aliases to export from this module - AliasesToExport = @( - 'dotnetnew', - 'find', - 'hex', - 'imt', - 'ipmod', - 'parse', - 'Restart-Shell', - 'rss', + AliasesToExport = @( + 'build' + 'dotnetnew' + 'find' + 'hex' + 'imt' + 'ipmod' + 'parse' + 'Restart-Shell' + 'rss' 'smd' ) diff --git a/PSModuleDevelopment/en-us/strings.psd1 b/PSModuleDevelopment/en-us/strings.psd1 index d950284..e8c66ff 100644 --- a/PSModuleDevelopment/en-us/strings.psd1 +++ b/PSModuleDevelopment/en-us/strings.psd1 @@ -4,6 +4,8 @@ 'Get-PSMDFileCommand.SyntaxError' = 'Syntax error in file: {0}' # $pathItem + 'Invoke-PSMDBuildProject.Step.Executing' = '[{0}] Executing step {1} ({2})' # $count, $step.Name, $step.Action + 'Invoke-PSMDTemplate.Template.NotFound' = 'Unable to find template data for "{0}"' # $TemplateName 'Invoke-PSMDTemplate.Invoking' = 'Creating file/project from template {0}' # $item diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 new file mode 100644 index 0000000..b25458d --- /dev/null +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 @@ -0,0 +1,12 @@ +function Get-PSMDBuildAction { + [CmdletBinding()] + param ( + [PsfArgumentCompleter('PSModuleDevelopment.Build.Action')] + [string] + $Name = '*' + ) + + process { + $script:buildActions.Values | Where-Object Name -Like $Name + } +} diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 new file mode 100644 index 0000000..c4c3513 --- /dev/null +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 @@ -0,0 +1,20 @@ +function Get-PSMDBuildArtifact { + [CmdletBinding()] + param ( + [string] + $Name = '*', + + [string[]] + $Tag + ) + + process { + $script:buildArtifacts.Values | Where-Object Name -Like $Name | Where-Object { + if (-not $Tag) { return $true } + foreach ($tagName in $Tag) { + if ($_.Tags -contains $Tag) { return $true } + } + return $false + } + } +} diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 new file mode 100644 index 0000000..ec5426b --- /dev/null +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 @@ -0,0 +1,33 @@ +function Get-PSMDBuildProject { + [CmdletBinding(DefaultParameterSetName = 'Path')] + param ( + [Parameter(Mandatory = $true, ParameterSetName = 'Path')] + [string] + $Path, + + [Parameter(ParameterSetName = 'Path')] + [string] + $Name, + + [Parameter(Mandatory = $true, ParameterSetName = 'Selected')] + [switch] + $Selected + ) + + process { + #region By Path + if ($Path) { + $importPath = $Path + if ($Name) { $importPath = Join-Path -Path $Path -ChildPath "$Name.build.json" } + + Get-Content -Path $importPath -Encoding UTF8 | ConvertFrom-Json + } + #endregion By Path + + #region Selected + else { + Get-Content -Path (Get-PSFConfigValue -FullName 'PSModuleDevelopment.Build.Project.Selected') -Encoding UTF8 | ConvertFrom-Json + } + #endregion Selected + } +} diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 new file mode 100644 index 0000000..1768866 --- /dev/null +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 @@ -0,0 +1,23 @@ +function Get-PSMDBuildStep { + [CmdletBinding()] + param ( + [string] + $Name = '*', + + [string] + $BuildProject + ) + + begin { + $projectPath = $BuildProject + if (-not $projectPath) { $projectPath = Get-PSFConfigValue -FullName 'PSModuleDevelopment.Build.Project.Selected' } + if (-not $projectPath) { throw "No Project path specified and none selected!" } + if (-not (Test-Path -Path $projectPath)) { + throw "Project file not found: $projectPath" + } + } + process { + $projectObject = Get-PSMDBuildProject -Path $projectPath + $projectObject.Steps | Where-Object Name -Like $Name + } +} diff --git a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 new file mode 100644 index 0000000..2195b3e --- /dev/null +++ b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 @@ -0,0 +1,132 @@ +function Invoke-PSMDBuildProject { + [Alias('build')] + [CmdletBinding()] + param ( + [string] + $Path, + + [switch] + $RetainArtifacts + ) + + begin { + $script:buildArtifacts = @{ } + $buildStatus = @{ } + + $projectPath = $Path + if (-not $projectPath) { $projectPath = Get-PSFConfigValue -FullName 'PSModuleDevelopment.Build.Project.Selected' } + if (-not $projectPath) { throw "No Project path specified and none selected!" } + if (-not (Test-Path -Path $projectPath)) { + throw "Project file not found: $projectPath" + } + + function Write-StepResult { + [CmdletBinding()] + param ( + [int] + $Count, + + [ValidateSet('Success', 'Failed', 'ConditionNotMet', 'DependencyNotMet', 'BadAction')] + [string] + $Status, + + $StepObject, + + $Data, + + [hashtable] + $BuildStatus, + + [string] + $ContinueLabel + ) + + $BuildStatus[$StepObject.Name] = $Status -eq 'Success' + + $paramWritePSFMessage = @{ + Level = 'Warning' + String = "Invoke-PSMDBuildProject.Step.$Status" + } + + switch ($Status) { + Failed { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action -ErrorRecord $Data } + ConditionNotMet { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action, $StepObject.Condition } + DependencyNotMet { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action, $Data } + BadAction { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action } + } + + [PSCustomObject]@{ + PSTypeName = 'PSModuleDevelopment.Build.StepResult' + Count = $Count + Action = $StepObject.Action + Status = $Status + Step = $StepObject.Name + Data = $Data + } + + if ($ContinueLabel) { + continue $ContinueLabel + } + } + } + process { + $projectObject = Get-PSMDBuildProject -Path $projectPath + $steps = $projectObject.Steps | Sort-Object Weight + + $count = 0 + $stepResults = :main foreach ($step in $steps) { + $count++ + $resultDef = @{ + Count = $count + StepObject = $step + BuildStatus = $buildStatus + } + + Write-PSFMessage -Level Host -String 'Invoke-PSMDBuildProject.Step.Executing' -StringValues $count, $step.Name, $step.Action + + #region Validation + $actionObject = $script:buildActions[$step.Action] + if (-not $actionObject) { + Write-StepResult @resultDef -Status BadAction -ContinueLabel main + } + + foreach ($dependency in $step.Dependency) { + if (-not $buildStatus[$dependency]) { + Write-StepResult @resultDef -Status DependencyNotMet -Data $dependency -ContinueLabel main + } + } + + if ($step.Condition -and $step.ConditionSet) { + $cModule, $cSetName = $step.ConditionSet -split " ", 2 + $conditionSet = Get-PSFFilterConditionSet -Module $cModule -Name $cSetName + if (-not $conditionSet) { + Write-StepResult @resultDef -Status ConditionNotMet -ContinueLabel main + } + + $filter = New-PSFFilter -Expression $step.Condition -ConditionSet $conditionSet + if (-not $filter.Evaluate()) { + Write-StepResult @resultDef -Status ConditionNotMet -ContinueLabel main + } + } + #endregion Validation + + #region Execution + $parameters = @{ + RootPath = Split-Path -Path $projectPath + Parameters = $step.Parameters + } + try { $null = & $actionObject.Action $parameters } + catch { + Write-StepResult @resultDef -Status Failed -Data $_ -ContinueLabel main + } + Write-StepResult @resultDef -Status Success + #endregion Execution + } + $stepResults + } + end { + if (-not $RetainArtifacts) { + $script:buildArtifacts = @{ } + } + } +} diff --git a/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 new file mode 100644 index 0000000..2b81751 --- /dev/null +++ b/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 @@ -0,0 +1,42 @@ +function New-PSMDBuildProject +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter(Mandatory = $true)] + [PsfValidateScript('PSFramework.Validate.FSPath.Folder', ErrorString = 'PSFramework.Validate.FSPath.Folder')] + [string] + $Path, + + [string] + $Condition, + + [string] + $ConditionSet = 'PSFramework Environment', + + [switch] + $NoSelect, + + [switch] + $Register + ) + + process + { + $project = [pscustomobject]@{ + Name = $Name + Condition = $Condition + ConditionSet = $ConditionSet + Steps = @() + } + $outPath = Join-Path -Path $Path -ChildPath "$Name.build.Json" + $project | ConvertTo-Json -Depth 10 | Set-Content -Path $outPath -Encoding UTF8 -ErrorAction Stop + if (-not $NoSelect) { + Set-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' -Value $outPath + if ($Register) { Register-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' } + } + } +} diff --git a/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 new file mode 100644 index 0000000..7f7dee4 --- /dev/null +++ b/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 @@ -0,0 +1,24 @@ +function Publish-PSMDBuildArtifact { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] + $Name, + + [Parameter(Mandatory = $true)] + [AllowNull()] + $Value, + + [string[]] + $Tag = @() + ) + + process { + $script:buildArtifacts[$Name] = [pscustomobject]@{ + PSTypeName = 'PSModuleDevelopment.Build.Artifact' + Name = $Name + Value = $Value + Tags = $Tag + } + } +} diff --git a/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 b/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 new file mode 100644 index 0000000..e5e2004 --- /dev/null +++ b/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 @@ -0,0 +1,26 @@ +function Register-PSMDBuildAction { + [CmdletBinding()] + param ( + [string] + $Name, + + [ScriptBlock] + $Action, + + [string] + $Description, + + [hashtable[]] + $Parameters + ) + + process { + $script:buildActions[$Name] = [pscustomobject]@{ + PSTypeName = 'PSModuleDevelopment.Build.Action' + Name = $Name + Action = $Action + Description = $Description + Parameters = $Parameters + } + } +} diff --git a/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 new file mode 100644 index 0000000..969909f --- /dev/null +++ b/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 @@ -0,0 +1,15 @@ +function Remove-PSMDBuildArtifact +{ + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string[]] + $Name + ) + + process{ + foreach ($nameString in $Name) { + $script:buildArtifacts.Remove($nameString) + } + } +} diff --git a/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 new file mode 100644 index 0000000..c0213c4 --- /dev/null +++ b/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 @@ -0,0 +1,20 @@ +function Select-PSMDBuildProject +{ + [CmdletBinding()] + Param ( + + ) + + begin + { + + } + process + { + + } + end + { + + } +} diff --git a/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 b/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 new file mode 100644 index 0000000..d402796 --- /dev/null +++ b/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 @@ -0,0 +1,67 @@ +function Set-PSMDBuildStep { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] + $Name, + + [int] + $Weight, + + [PsfArgumentCompleter('PSModuleDevelopment.Build.Action')] + [string] + $Action, + + [hashtable] + $Parameters, + + [string] + $Condition, + + [string] + $ConditionSet, + + [string[]] + $Dependency, + + [string] + $BuildProject + ) + + begin { + $projectPath = $BuildProject + if (-not $projectPath) { $projectPath = Get-PSFConfigValue -FullName 'PSModuleDevelopment.Build.Project.Selected' } + if (-not $projectPath) { throw "No Project path specified and none selected!" } + if (-not (Test-Path -Path $projectPath)) { + throw "Project file not found: $projectPath" + } + } + process { + $projectObject = Get-PSMDBuildProject -Path $projectPath + $stepObject = $projectObject.Steps | Where-Object Name -EQ $Name + if (-not $stepObject) { + $stepObject = [pscustomobject]@{ + PSTypeName = 'PSModuleDevelopment.Build.Step' + Name = $Name + Weight = 50 + Action = '' + Parameters = @{ } + Condition = '' + ConditionSet = 'PSFramework Environment' + Dependency = @() + } + } + if (Test-PSFParameterBinding -ParameterName Weight) { $stepObject.Weight = $Weight } + if (Test-PSFParameterBinding -ParameterName Action) { $stepObject.Action = $Action } + if (Test-PSFParameterBinding -ParameterName Parameters) { $stepObject.Parameters = $Parameters } + if (Test-PSFParameterBinding -ParameterName Condition) { $stepObject.Condition = $Condition } + if (Test-PSFParameterBinding -ParameterName ConditionSet) { $stepObject.ConditionSet = $ConditionSet } + if (Test-PSFParameterBinding -ParameterName Dependency) { $stepObject.Dependency = $Dependency } + + if (-not $stepObject.Action) { + throw "Failed to save Build Step $Name : No Action defined!" + } + $projectObject.Steps = @($projectObject.Steps | Where-Object Name -ne $Name) + @($stepObject) | Sort-Object -Property Name + $projectObject | ConvertTo-Json -Depth 10 | Set-Content -Path $projectPath -Encoding UTF8 + } +} \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 b/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 new file mode 100644 index 0000000..25a5d52 --- /dev/null +++ b/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 @@ -0,0 +1,53 @@ +$action = { + param ( + $Parameters + ) + + $rootPath = $Parameters.RootPath + $actualParameters = $Parameters.Parameters + + if (-not ($actualParameters.Path -and $actualParameters.Destination)) { + throw "Invalid parameters! Specify both Path and Destination." + } + + $paths = $actualParameters.Path -replace '%ProjectRoot%', $rootPath + $copyParam = @{ + Destination = $actualParameters.Destination -replace '%ProjectRoot%', $rootPath + } + if ($actualParameters.Recurse) { $copyParam.Recurse = $true } + if ($actualParameters.Force) { $copyParam.Force = $true } + if ($actualParameters.FromSession) { + $artifact = Get-PSMDBuildArtifact -Name $actualParameters.FromSession + if (-not $artifact) { + throw "FromSession $($actualParameters.FromSession) not found!" + } + $copyParam.FromSession = $artifact.Value + } + if ($actualParameters.ToSession) { + $artifact = Get-PSMDBuildArtifact -Name $actualParameters.ToSession + if (-not $artifact) { + throw "ToSession $($actualParameters.ToSession) not found!" + } + $copyParam.ToSession = $artifact.Value + } + foreach ($path in $paths) { + try { Copy-Item @copyParam -Path $path -ErrorAction Stop } + catch { throw } + } +} + +$params = @{ + Name = 'copy-item' + Action = $action + Description = 'Copies files & folders from A to B' + Parameters = @{ + Path = '(mandatory) Path(s) to copy. Use "%ProjectRoot%" to reference to the root path containing the build file.' + Destination = '(mandatory) Path to copy to. Use "%ProjectRoot%" to reference to the root path containing the build file.' + FromSession = 'Artifact Name of the PSSession to copy from.' + ToSession = 'Artifact Name of the PSSession to copy to.' + Recurse = 'Whether to copy child items' + Force = 'Whether to use force (Remove destination items)' + } +} + +Register-PSMDBuildAction @params \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 b/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 new file mode 100644 index 0000000..3011bc2 --- /dev/null +++ b/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 @@ -0,0 +1,43 @@ +$action = { + param ( + $Parameters + ) + + $rootPath = $Parameters.RootPath + $actualParameters = $Parameters.Parameters + + if (-not $actualParameters.ArtifactName) { throw "No ArtifactName specified! Unable to publish remoting session for build." } + if (-not ($actualParameters.VMName -or $actualParameters.ComputerName)) { throw "Neither ComputerName nor VMName specified, unable to connect to nothing!" } + if ($actualParameters.VMName -and $actualParameters.ComputerName) { throw "Both ComputerName and VMName specified, unable to connect to both at once!" } + + $credential = $null + if ($actualParameters.CredentialPath) { + $path = $actualParameters.CredentialPath -replace '%ProjectRoot%', $rootPath + try { $credential = Import-PSFClixml -Path $path -ErrorAction Stop } + catch { throw "Error accessing credentials from $path : $_" } + } + + $paramNewPSSession = @{ } + if ($actualParameters.VMName) { $paramNewPSSession.VMName = $actualParameters.VMName } + if ($actualParameters.ComputerName) { $paramNewPSSession.ComputerName = $actualParameters.ComputerName } + if ($credential) { $paramNewPSSession.Credential = $credential } + + try { $session = New-PSSession @paramNewPSSession -ErrorAction Stop } + catch { throw "Error establishing PS Remoting session: $_" } + + Publish-PSMDBuildArtifact -Name $actualParameters.ArtifactName -Value $session -Tag pssession +} + +$params = @{ + Name = 'new-pssession' + Action = $action + Description = 'Establish a PSSession to a target computer and provide it as an artifact' + Parameters = @{ + ComputerName = 'The Computer to connect to' + VMName = 'The virtual machine to which to connect to via the HyperV VM Bus' + CredentialPath = 'The path to the credentials to use for the connection. Use %ProjectRoot% to insert the folder path to where the buildfile is located' + ArtifactName = 'The name under which to publish the session as an artifact' + } +} + +Register-PSMDBuildAction @params \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 b/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 new file mode 100644 index 0000000..2d842eb --- /dev/null +++ b/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 @@ -0,0 +1,56 @@ +$action = { + param ( + $Parameters + ) + + $rootPath = $Parameters.RootPath + $actualParameters = $Parameters.Parameters + + if (-not $actualParameters.Path) { + throw "Invalid parameters! Specify a Path to delete." + } + + $paths = $actualParameters.Path -replace '%ProjectRoot%', $rootPath + $deleteParam = @{ } + if ($actualParameters.Recurse) { $deleteParam.Recurse = $true } + if ($actualParameters.Force) { $deleteParam.Force = $true } + if ($actualParameters.InSession) { + $artifact = Get-PSMDBuildArtifact -Name $actualParameters.InSession + if (-not $artifact) { + throw "InSession $($actualParameters.InSession) not found!" + } + + $failed = Invoke-Command -Session $artifact.Value -ScriptBlock { + param ($DeleteParam, $Paths) + + foreach ($path in $Paths) { + if (-not (Get-Item -Path $path -Force -ErrorAction Ignore)) { continue } + try { Remove-Item @DeleteParam -Path $path -ErrorAction Stop } + catch { return $_ } + } + } -ArgumentList $deleteParam, $paths + if ($failed) { + throw $failed + } + } + + foreach ($path in $paths) { + if (-not (Get-Item -Path $path -Force -ErrorAction Ignore)) { continue } + try { Remove-Item @DeleteParam -Path $path -ErrorAction Stop } + catch { throw } + } +} + +$params = @{ + Name = 'remove-item' + Action = $action + Description = 'Removes files or folders' + Parameters = @{ + Path = '(mandatory) Path(s) to the item(s) to delete. Use "%ProjectRoot%" to reference to the root path containing the build file.' + InSession = 'Artifact Name of the PSSession within which to execute the deletion' + Recurse = 'Whether to delete child items' + Force = 'Whether to use force' + } +} + +Register-PSMDBuildAction @params \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 b/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 new file mode 100644 index 0000000..1b77db3 --- /dev/null +++ b/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 @@ -0,0 +1,47 @@ +$action = { + param ( + $Parameters + ) + + $rootPath = $Parameters.RootPath + $actualParameters = $Parameters.Parameters + + if ($actualParameters.All) { + foreach ($artifact in Get-PSMDBuildArtifact -Tag pssession) { + try { + $artifact.Value | Remove-PSSession -ErrorAction Stop + Remove-PSMDBuildArtifact -Name $artifact.Name + } + catch { + throw "Failed to remove PSSession artifact $($artifact.Name) to $($artifact.Value) | $_" + } + } + } + elseif ($actualParameters.ArtifactName) { + $artifact = Get-PSMDBuildArtifact -Name $actualParameters.ArtifactName + if ($artifact) { + try { + $artifact.Value | Remove-PSSession -ErrorAction Stop + Remove-PSMDBuildArtifact -Name $artifact.Name + } + catch { + throw "Failed to remove PSSession artifact $($artifact.Name) to $($artifact.Value) | $_" + } + } + } + else { + throw "Invalid parameters! Specify either 'All' or 'ArtifactName' in step definition." + } +} + +$params = @{ + Name = 'remove-pssession' + Action = $action + Description = 'Removes a PSSession that was previously established with the new-pssession action' + Parameters = @{ + ArtifactName = 'The name under which to publish the session as an artifact' + All = 'Whether all PSSession artifacts should be removed' + } +} + +Register-PSMDBuildAction @params \ No newline at end of file diff --git a/PSModuleDevelopment/internal/configurations/build.ps1 b/PSModuleDevelopment/internal/configurations/build.ps1 new file mode 100644 index 0000000..291bf24 --- /dev/null +++ b/PSModuleDevelopment/internal/configurations/build.ps1 @@ -0,0 +1 @@ +Set-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' -Value '' -Validation string -Initialize -Description 'Path of the selected build project. Used when running Invoke-PSMDBuildProject without specifying a build file.' \ No newline at end of file diff --git a/PSModuleDevelopment/internal/scripts/postimport.ps1 b/PSModuleDevelopment/internal/scripts/postimport.ps1 index 5b93318..51a1137 100644 --- a/PSModuleDevelopment/internal/scripts/postimport.ps1 +++ b/PSModuleDevelopment/internal/scripts/postimport.ps1 @@ -21,6 +21,11 @@ foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\maintenance\*.p . Import-ModuleFile -Path $file.FullName } +# Load Build Actions +foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\buildActions\*.ps1" -ErrorAction Ignore)) { + . Import-ModuleFile -Path $file.FullName +} + # Load License . Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\license.ps1" diff --git a/PSModuleDevelopment/internal/scripts/variables.ps1 b/PSModuleDevelopment/internal/scripts/variables.ps1 index ef13dc1..30d090d 100644 --- a/PSModuleDevelopment/internal/scripts/variables.ps1 +++ b/PSModuleDevelopment/internal/scripts/variables.ps1 @@ -11,4 +11,8 @@ else # Defaults to $Env:AppData on Windows $path_FileUserShared = Join-Path $Env:AppData "$psVersionName\PSFramework\Config" if (-not $Env:AppData) { $path_FileUserShared = Join-Path ([Environment]::GetFolderPath("ApplicationData")) "$psVersionName\PSFramework\Config" } -} \ No newline at end of file +} + +# Store of registered build actions +$script:buildActions = @{ } +$script:buildArtifacts = @{ } diff --git a/PSModuleDevelopment/internal/tepp/build.tepp.ps1 b/PSModuleDevelopment/internal/tepp/build.tepp.ps1 new file mode 100644 index 0000000..e430665 --- /dev/null +++ b/PSModuleDevelopment/internal/tepp/build.tepp.ps1 @@ -0,0 +1,3 @@ +Register-PSFTeppScriptblock -Name 'PSModuleDevelopment.Build.Action' -ScriptBlock { + (Get-PSMDBuildAction).Name +} \ No newline at end of file diff --git a/PSModuleDevelopment/xml/PSModuleDevelopment.Format.ps1xml b/PSModuleDevelopment/xml/PSModuleDevelopment.Format.ps1xml index 5351685..19a6410 100644 --- a/PSModuleDevelopment/xml/PSModuleDevelopment.Format.ps1xml +++ b/PSModuleDevelopment/xml/PSModuleDevelopment.Format.ps1xml @@ -1,6 +1,45 @@  + + + PSModuleDevelopment.Build.StepResult + + PSModuleDevelopment.Build.StepResult + + + + + + + + + + + + + + + Count + + + Action + + + Status + + + Step + + + Data + + + + + + + PSModuleDevelopment.File.Command diff --git a/build/filesAfter.txt b/build/filesAfter.txt index fbf452a..92c18a9 100644 --- a/build/filesAfter.txt +++ b/build/filesAfter.txt @@ -5,5 +5,6 @@ internal\scriptblocks\*.ps1 internal\tepp\*.tepp.ps1 internal\tepp\assignment.ps1 internal\maintenance\*.ps1 +internal\buildActions\*.ps1 internal\scripts\license.ps1 internal\scripts\moduledebug.ps1 \ No newline at end of file From 5f36bb8fdda98beff7af9bed3c7964940fdd2f3e Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 20 Apr 2021 23:25:54 +0200 Subject: [PATCH 3/8] docs update --- PSModuleDevelopment/changelog.md | 1 + .../functions/build/Get-PSMDBuildAction.ps1 | 17 ++++++++ .../functions/build/Get-PSMDBuildArtifact.ps1 | 32 ++++++++++++++ .../functions/build/Get-PSMDBuildProject.ps1 | 26 ++++++++++++ .../functions/build/Get-PSMDBuildStep.ps1 | 26 ++++++++++++ .../build/Select-PSMDBuildProject.ps1 | 42 ++++++++++++++----- 6 files changed, 133 insertions(+), 11 deletions(-) diff --git a/PSModuleDevelopment/changelog.md b/PSModuleDevelopment/changelog.md index bff1ac4..453f486 100644 --- a/PSModuleDevelopment/changelog.md +++ b/PSModuleDevelopment/changelog.md @@ -2,6 +2,7 @@ ## ??? +- New: Build Component - define build workflows based on pre-defined & extensible action code - Fix: TemplateStore - default path iss invalid on MAC (#136) - Fix: Invoke-PSMDTemplate - unreliable string replacement through -replace operator (#113) - Fix: Publish-PSMDScriptFile - insufficient exclude paths (#138; @Callidus2000) diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 index b25458d..6ff5b48 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildAction.ps1 @@ -1,4 +1,21 @@ function Get-PSMDBuildAction { +<# + .SYNOPSIS + Get a list of registered build actions. + + .DESCRIPTION + Get a list of registered build actions. + Actions are the scriptblocks that are used to execute the build logic when running Invoke-PSMDBuildProject. + + .PARAMETER Name + The name by which to filter the actions returned. + Defaults to '*' + + .EXAMPLE + PS C:\> Get-PSMDBuildAction + + Get a list of all registered build actions. +#> [CmdletBinding()] param ( [PsfArgumentCompleter('PSModuleDevelopment.Build.Action')] diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 index c4c3513..7dc8585 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 @@ -1,4 +1,36 @@ function Get-PSMDBuildArtifact { +<# + .SYNOPSIS + Retrieve an artifact during a build project's execution. + + .DESCRIPTION + Retrieve an artifact during a build project's execution. + These artifacts are usually created during such an execution and discarded once completed. + + .PARAMETER Name + The name by which to search for artifacts. + Defaults to '*' + + .PARAMETER Tag + Search for artifacts by tag. + Artifacts can receive tag for better categorization. + When specifying multiple tags, any artifact containing at least one of them will be returned. + + .EXAMPLE + PS C:\> Get-PSMDBuildArtifact + + List all available artifacts. + + .EXAMPLE + PS C:\> Get-PSMDBuildArtifact -Name ReleasePath + + Returns the artifact named "ReleasePath" + + .EXAMPLE + PS C:\> Get-PSMDBuildArtifact -Tag pssession + + Returns all artifacts with the tag "pssession" +#> [CmdletBinding()] param ( [string] diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 index ec5426b..de88f5b 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 @@ -1,4 +1,30 @@ function Get-PSMDBuildProject { +<# + .SYNOPSIS + Reads & returns a build project. + + .DESCRIPTION + Reads & returns a build project. + A build project is a container including the steps executed during the build. + + .PARAMETER Path + Path to the build project file. + May target the folder, in which case the -Name parameter must be specified. + + .PARAMETER Name + The name of the build project to read. + Use together with the -Path parameter only. + Absolute file path assumed will be: "\.build.json" + + .PARAMETER Selected + Rather than specifying the path to read from, return the currently selected build project. + Use Select-PSMDBuildProject to select a build project as the default ("selected") project. + + .EXAMPLE + PS C:\> Get-PSMDBuildProject -Path 'C:\code\project' -Name project + + Will load the build project stored in the file "C:\code\project\project.build.json" +#> [CmdletBinding(DefaultParameterSetName = 'Path')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 index 1768866..7c9890e 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildStep.ps1 @@ -1,4 +1,30 @@ function Get-PSMDBuildStep { +<# + .SYNOPSIS + Read the steps that are part of the specified build project. + + .DESCRIPTION + Read the steps that are part of the specified build project. + + .PARAMETER Name + The name by which to filter the steps returned. + Defaults to '*' + + .PARAMETER BuildProject + Path to the build project file to read from. + Defaults to the currently selected project if available. + Use Select-PSMDBuildProject to select a default project. + + .EXAMPLE + PS C:\> Get-PSMDBuildStep + + Read all steps that are part of the default build project. + + .EXAMPLE + PS C:\> Get-PSMDBuildStep -Name CreateSession -BuildProject C:\code\Project\Project.build.json + + Return the CreateSession step from the specified project file. +#> [CmdletBinding()] param ( [string] diff --git a/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 index c0213c4..b915741 100644 --- a/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Select-PSMDBuildProject.ps1 @@ -1,20 +1,40 @@ function Select-PSMDBuildProject { - [CmdletBinding()] - Param ( +<# + .SYNOPSIS + Set the specified build project as the default project. - ) + .DESCRIPTION + Set the specified build project as the default project. + This will have most other commands in this Component automatically use the specified project. - begin - { + .PARAMETER Path + Path to the project file to pick. + + .PARAMETER Register + Persist the choice as default build project file across PowerShell sessions. + + .EXAMPLE + PS C:\> Select-PSMDBuildProject -Path 'c:\code\Project\Project.build.json' + + Sets the specified build project as the default project. +#> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] + $Path, - } - process - { + [switch] + $Register + ) - } - end + process { - + Invoke-PSFProtectedCommand -ActionString 'Select-PSMDBuildProject.Testing' -ActionStringValues $Path -ScriptBlock { + $null = Get-PSMDBuildProject -Path $Path -ErrorAction Stop + } -Target $Path -EnableException $true -PSCmdlet $PSCmdlet + Set-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' -Value $Path + if ($Register) { Register-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' } } } From 3719e2fdea6184a6f89d5fc4b053583e2ce8c197 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Thu, 29 Apr 2021 08:25:18 +0200 Subject: [PATCH 4/8] Added build command parameter help --- PSModuleDevelopment/PSModuleDevelopment.psd1 | 1 + .../functions/build/Get-PSMDBuildArtifact.ps1 | 3 +- .../build/Invoke-PSMDBuildProject.ps1 | 41 +++++++++++ .../functions/build/New-PSMDBuildProject.ps1 | 69 ++++++++++++++++--- .../build/Publish-PSMDBuildArtifact.ps1 | 30 ++++++++ .../build/Register-PSMDBuildAction.ps1 | 38 +++++++++- .../build/Remove-PSMDBuildArtifact.ps1 | 23 ++++++- .../build/Resolve-PSMDBuildStepParameter.ps1 | 56 +++++++++++++++ .../functions/build/Set-PSMDBuildStep.ps1 | 60 ++++++++++++++-- .../buildActions/copy-item.action.ps1 | 1 + .../buildActions/new-pssession.action.ps1 | 3 +- .../buildActions/remove-item.action.ps1 | 1 + .../buildActions/remove-pssession.action.ps1 | 1 + .../internal/buildActions/script.action.ps1 | 57 +++++++++++++++ .../build/Export-PsmdBuildProjectFile.ps1 | 48 +++++++++++++ 15 files changed, 415 insertions(+), 17 deletions(-) create mode 100644 PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 create mode 100644 PSModuleDevelopment/internal/buildActions/script.action.ps1 create mode 100644 PSModuleDevelopment/internal/functions/build/Export-PsmdBuildProjectFile.ps1 diff --git a/PSModuleDevelopment/PSModuleDevelopment.psd1 b/PSModuleDevelopment/PSModuleDevelopment.psd1 index b9dcd6b..03e7840 100644 --- a/PSModuleDevelopment/PSModuleDevelopment.psd1 +++ b/PSModuleDevelopment/PSModuleDevelopment.psd1 @@ -90,6 +90,7 @@ 'Remove-PSMDModuleDebug' 'Remove-PSMDTemplate' 'Rename-PSMDParameter' + 'Resolve-PSMDBuildStepParameter' 'Restart-PSMDShell' 'Search-PSMDPropertyValue' 'Select-PSMDBuildProject' diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 index 7dc8585..56807e4 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 @@ -41,12 +41,13 @@ ) process { - $script:buildArtifacts.Values | Where-Object Name -Like $Name | Where-Object { + $artifacts = $script:buildArtifacts.Values | Where-Object Name -Like $Name | Where-Object { if (-not $Tag) { return $true } foreach ($tagName in $Tag) { if ($_.Tags -contains $Tag) { return $true } } return $false } + $($artifacts) } } diff --git a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 index 2195b3e..80c4dbd 100644 --- a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 @@ -1,4 +1,42 @@ function Invoke-PSMDBuildProject { +<# + .SYNOPSIS + Execute a build project. + + .DESCRIPTION + Execute a build project. + A build project is a configured chain of actions that have been configured in json. + They will be processed in their specified order and allow manageable, configurable steps without having to reinvent the same action again and again. + + + Individual action types become available using Register-PSMDBuildAction. + + Create new build projects using New-PSMDBuildProject + + Set up steps taken during a build using Set-PSMDBuildStep + + Select the default build project using Select-PSMDBuildProject + + .PARAMETER Path + The path to the build project file to execute. + Mandatory if no build project has been selected as the default project. + Use the Select-PSMDBuildProject to define a default project (and optionally persist the choice across sessions) + + .PARAMETER RetainArtifacts + Whether, after executing the project, its artifacts should be retained. + By default, any artifacts created during a build project will be discarded upon project completion. + + Artifacts are similar to variables to the pipeline and can be used to pass data throughout the pipeline. + + + Use Publish-PSMDBuildArtifact to create a new artifact. + + Use Get-PSMDBuildArtifact to access existing build artifacts. + + .EXAMPLE + PS C:\> Invoke-PSMDBuildProject -Path .\VMDeployment.build.Json + + Execute the build file "VMDeployment.build.json" from the current folder + + .EXAMPLE + PS C:\> build + + Execute the default build project. +#> [Alias('build')] [CmdletBinding()] param ( @@ -114,7 +152,10 @@ $parameters = @{ RootPath = Split-Path -Path $projectPath Parameters = $step.Parameters + ProjectName = $projectObject.Name + StepName = $step.Name } + if (-not $parameters.Parameters) { $parameters.Parameters = @{ } } try { $null = & $actionObject.Action $parameters } catch { Write-StepResult @resultDef -Status Failed -Data $_ -ContinueLabel main diff --git a/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 index 2b81751..a946bc4 100644 --- a/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 @@ -1,6 +1,56 @@ -function New-PSMDBuildProject -{ - [CmdletBinding()] +function New-PSMDBuildProject { +<# + .SYNOPSIS + Create a new build project file. + + .DESCRIPTION + Create a new build project file. + Build projects are used to configure a repeatable, managed set of steps that make up a workflow. + It is designed with software build processes in mind, but can be used for pretty much anything that works in separate steps. + + See the help on Invoke-PSMDBuildProject for more details. + + NOTE: This is not the tool or component to create new PowerShell _code_ projects / repositories! + To create a new PowerShell module project, instead run: + + Invoke-PSMDTemplate PSFProject + + .PARAMETER Name + The name of the build project. + + .PARAMETER Path + The path to the folder in which the build project file is created. + Final path will be: "\.build.json" + + .PARAMETER Condition + A condition - a filter expression - that must be met in order for the build to proceed. + For more details on filter conditions, see the PSFramework documentation on its feature: + https://psframework.org/documentation/documents/psframework/filters.html + + .PARAMETER ConditionSet + The name of the condition set to use. + This is part of the PSFramework filter system: + https://psframework.org/documentation/documents/psframework/filters.html + + Specify as " " format. + Default Value: PSFramework Environment + + .PARAMETER NoSelect + Do not select the newly created build project as the default project for the current session. + By default, the newly created build project will be set as default project, in order to facilitate adding steps to it. + Use Select-PSMDBuildProject to explicitly set a default project file. + + .PARAMETER Register + Persist the newly created build project as default build project beyond the current session. + By default, the newly created build project will already be set as default project, in order to facilitate adding steps to it. + But ONLY for the current session. This parameter makes it remember in new PowerShell sessions as well. + + .EXAMPLE + PS C:\> New-PSMDBuildProject -Name 'VMDeployment' -Path 'C:\Code\VMDeployment' + + Create a new build project named 'VMDeployment' in the folder 'C:\Code\VMDeployment' +#> + [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true)] [string] @@ -17,23 +67,24 @@ [string] $ConditionSet = 'PSFramework Environment', + [Parameter(ParameterSetName = 'NoSelect')] [switch] $NoSelect, + [Parameter(ParameterSetName = 'Register')] [switch] $Register ) - process - { + process { $project = [pscustomobject]@{ - Name = $Name - Condition = $Condition + Name = $Name + Condition = $Condition ConditionSet = $ConditionSet - Steps = @() + Steps = @() } $outPath = Join-Path -Path $Path -ChildPath "$Name.build.Json" - $project | ConvertTo-Json -Depth 10 | Set-Content -Path $outPath -Encoding UTF8 -ErrorAction Stop + $project | Export-PsmdBuildProjectFile -OutPath $outPath -ErrorAction Stop if (-not $NoSelect) { Set-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' -Value $outPath if ($Register) { Register-PSFConfig -Module PSModuleDevelopment -Name 'Build.Project.Selected' } diff --git a/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 index 7f7dee4..6223327 100644 --- a/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 +++ b/PSModuleDevelopment/functions/build/Publish-PSMDBuildArtifact.ps1 @@ -1,4 +1,34 @@ function Publish-PSMDBuildArtifact { +<# + .SYNOPSIS + Create a new artifact for the current build pipeline. + + .DESCRIPTION + Create a new artifact for the current build pipeline. + Use this create artifacts that are accessible in later steps in the pipeline. + + Usually, artifacts are deleted at the end of a build process. + They are always cleared at the beginning of a new one. + + Artifacts are NOT persisted across PowerShell sessions. + + .PARAMETER Name + Name of the Artifact to create. + Technically there are no limits to which character to chose, but we strongly encourage restricting yourself to letters, numbers, dash, underscore and dot. + + .PARAMETER Value + The value to assign to the artifact. + + .PARAMETER Tag + Any tags to add to an artifact. + Tags can be searched for in order to bulk-operate against all artifacts of that tag. + For example, the "remove-pssession" action can remove all remoting sessions for all artifacts tagged as "pssession". + + .EXAMPLE + PS C:\> Publish-PSMDBuildArtifact -Name 'session' -Value $session -Tag 'pssession' + + Publishes an artifact named "session" containing the content of $session that is tagged as a PowerShell remoting session. +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] diff --git a/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 b/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 index e5e2004..8728f03 100644 --- a/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 +++ b/PSModuleDevelopment/functions/build/Register-PSMDBuildAction.ps1 @@ -1,16 +1,52 @@ function Register-PSMDBuildAction { +<# + .SYNOPSIS + Register a new action usable in build projects. + + .DESCRIPTION + Register a new action usable in build projects. + Actions are the actual implementation logic that turns the configuration in a build project file into ... well, actions. + Anyway, these are basically named scriptblocks with some metadata. + This command is used to provide all the builtin actions and can be used to freely define your own actions. + + Whenever you use a "script" action in your build projects, consider ... would it make a good configurable option valuable for other builds? + If so, that might just mark the birth of the next action! + + .PARAMETER Name + The name of the action. + + .PARAMETER Action + The actual code implementing the action. + Each action scriptblock will receive exactly one . + + .PARAMETER Description + A description explaining what the action is all about. + + .PARAMETER Parameters + The parameters the action accepts. + Provider a hashtable, with the keys being the parameter names and the values being a description of its parameter. + + .EXAMPLE + PS C:\> Register-PSMDBuildAction -Name 'script' -Action $actionCode -Description 'Execute a custom scriptfile as part of your workflow' -Parameters $parameters + + Creates / registers the action "script". +#> [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] [string] $Name, + [Parameter(Mandatory = $true)] [ScriptBlock] $Action, + [Parameter(Mandatory = $true)] [string] $Description, - [hashtable[]] + [Parameter(Mandatory = $true)] + [hashtable] $Parameters ) diff --git a/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 index 969909f..f98213d 100644 --- a/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 +++ b/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 @@ -1,8 +1,29 @@ function Remove-PSMDBuildArtifact { +<# + .SYNOPSIS + Removes an artifact from the build pipeline. + + .DESCRIPTION + Removes an artifact from the build pipeline. + Only interacts with the PSModuleDevelopment build system. + + .PARAMETER Name + Name of the artifact to remove. + + .EXAMPLE + PS C:\> Remove-PSMDBuildArtifact -Name 'session' + + Removes the artifact 'session' from the build pipeline. + + .EXAMPLE + PS C:\> Get-PSMDBuildArtifact -Tag pssession | Remove-PSMDBuildArtifact + + Removes all artifacts with the 'pssession' tag from the build pipeline. +#> [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string[]] $Name ) diff --git a/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 b/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 new file mode 100644 index 0000000..fbf8f3c --- /dev/null +++ b/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 @@ -0,0 +1,56 @@ +function Resolve-PSMDBuildStepParameter { +<# + .SYNOPSIS + Update missing build action parameters from the configuration system. + + .DESCRIPTION + Update missing build action parameters from the configuration system. + This command is for use within the defined code of build actions. + + .PARAMETER Parameters + The hashtable containing the currently specified parameters from the step configuration within the build project file. + Only settings not already defined there are taken from configuration. + + .PARAMETER ProjectName + The name of the project being executed. + Supplementary parameters taken from configuration will pick up settings based on this name: + "PSModuleDevelopment.BuildParam...*" + + .PARAMETER StepName + The name of the step being executed. + Supplementary parameters taken from configuration will pick up settings based on this name: + "PSModuleDevelopment.BuildParam...*" + + .EXAMPLE + PS C:\> Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName VMDeployment -StepName 'Create Session' + + Adds parameters provided through configuration. +#> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [hashtable] + $Parameters, + + [Parameter(Mandatory = $true)] + [string] + $ProjectName, + + [Parameter(Mandatory = $true)] + [string] + $StepName + ) + + process { + $configObject = Select-PSFConfig -FullName "PSModuleDevelopment.BuildParam.$ProjectName.$StepName.*" + if (-not $configObject) { return $Parameters } + + foreach ($property in $configObject.PSObject.Properties) { + if ($property.Name -in '_Name', '_FullName', '_Depth', '_Children') { continue } + if ($Parameters.ContainsKey($property.Name)) { continue } + $Parameters[$property.Name] = $property.Value + } + + $Parameters + } +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 b/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 index d402796..b5037c1 100644 --- a/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 +++ b/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 @@ -1,4 +1,56 @@ function Set-PSMDBuildStep { +<# + .SYNOPSIS + Create or update a step from a build project. + + .DESCRIPTION + Create or update a step from a build project. + + .PARAMETER Name + The name of the step. + All step names must be unique within a single build project. + + .PARAMETER Weight + The weight of the step. + Weight determines processing order, the lower the number the earlier it is executed. + + .PARAMETER Action + The name of the action to execute. + Use Get-PSMDBuildAction to get a list of available actions. + + .PARAMETER Parameters + The parameters this action should take. + See the action object to see a description of parameters, including which must be provided and which can be skipped. + + .PARAMETER Condition + A PSFramework filter condition that must apply for this action to be executed successfully. + Example Conditions: + Elevated + PS7Plus -and OSWindows + More Details: https://psframework.org/documentation/documents/psframework/filters.html + + .PARAMETER ConditionSet + The name of the condition set to use. + This is part of the PSFramework filter system: + https://psframework.org/documentation/documents/psframework/filters.html + + Specify as " " format. + Default Value: PSFramework Environment + + .PARAMETER Dependency + Any other steps that must successfully finished in order for this step to execute. + ALL of the listed steps must have succeeded, skipped steps do not count. + + .PARAMETER BuildProject + The build project file to work against. + Specify the full path to the build project file. + This parameter can be skipped if a default project file has been defined. + + .EXAMPLE + PS C:\> Set-PSMDBuildStep -Name 'Create Session' -Action new-pssession -Parameters @{ VMName = 'labdc1'; CredentialPath = "%ProjectRoot%\creds\labdc1.cred"; } + + Defines a new step named 'Create Session' using the 'new-pssession'-action. +#> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -37,8 +89,8 @@ } } process { - $projectObject = Get-PSMDBuildProject -Path $projectPath - $stepObject = $projectObject.Steps | Where-Object Name -EQ $Name + $projectObject = Get-PSMDBuildProject -Path $projectPath | ConvertTo-PSFHashtable + $stepObject = $projectObject.Steps | Where-Object Name -EQ $Name | ConvertTo-PSFHashtable if (-not $stepObject) { $stepObject = [pscustomobject]@{ PSTypeName = 'PSModuleDevelopment.Build.Step' @@ -61,7 +113,7 @@ if (-not $stepObject.Action) { throw "Failed to save Build Step $Name : No Action defined!" } - $projectObject.Steps = @($projectObject.Steps | Where-Object Name -ne $Name) + @($stepObject) | Sort-Object -Property Name - $projectObject | ConvertTo-Json -Depth 10 | Set-Content -Path $projectPath -Encoding UTF8 + $projectObject.Steps = @($projectObject.Steps | Where-Object Name -ne $Name) + @($stepObject) + $projectObject | Export-PsmdBuildProjectFile -OutPath $projectPath -ErrorAction Stop } } \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 b/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 index 25a5d52..9798055 100644 --- a/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 @@ -5,6 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if (-not ($actualParameters.Path -and $actualParameters.Destination)) { throw "Invalid parameters! Specify both Path and Destination." diff --git a/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 b/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 index 3011bc2..aec1526 100644 --- a/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 @@ -5,6 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if (-not $actualParameters.ArtifactName) { throw "No ArtifactName specified! Unable to publish remoting session for build." } if (-not ($actualParameters.VMName -or $actualParameters.ComputerName)) { throw "Neither ComputerName nor VMName specified, unable to connect to nothing!" } @@ -36,7 +37,7 @@ $params = @{ ComputerName = 'The Computer to connect to' VMName = 'The virtual machine to which to connect to via the HyperV VM Bus' CredentialPath = 'The path to the credentials to use for the connection. Use %ProjectRoot% to insert the folder path to where the buildfile is located' - ArtifactName = 'The name under which to publish the session as an artifact' + ArtifactName = '(mandatory) The name under which to publish the session as an artifact' } } diff --git a/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 b/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 index 2d842eb..9794caf 100644 --- a/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 @@ -5,6 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if (-not $actualParameters.Path) { throw "Invalid parameters! Specify a Path to delete." diff --git a/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 b/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 index 1b77db3..48e5b75 100644 --- a/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 @@ -5,6 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if ($actualParameters.All) { foreach ($artifact in Get-PSMDBuildArtifact -Tag pssession) { diff --git a/PSModuleDevelopment/internal/buildActions/script.action.ps1 b/PSModuleDevelopment/internal/buildActions/script.action.ps1 new file mode 100644 index 0000000..806eb7b --- /dev/null +++ b/PSModuleDevelopment/internal/buildActions/script.action.ps1 @@ -0,0 +1,57 @@ +$action = { + param ( + $Parameters + ) + + $rootPath = $Parameters.RootPath + $actualParameters = $Parameters.Parameters + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + + if (-not $actualParameters.Path) { + throw "Mandatory parameter: Path not specified" + } + + if ($actualParameters.Path -notlike '%!*!%') { + $scriptPath = $actualParameters.Path -replace '%ProjectRoot%', $rootPath + } + else { + $artifactName = $actualParameters.Path -replace '^%!(.+)!%$', '$1' + $artifactObject = Get-PSMDBuildArtifact -Name $artifactName + if (-not $artifactObject) { throw "Artifact not found: $artifactName" } + $scriptPath = $artifactObject.Value + } + + if (-not (Test-Path $scriptPath)) { + throw "Cannot find resolved script path: $scriptPath" + } + + $actualArguments = foreach ($argument in $actualParameters.ArgumentList) { + if ($argument -isnot [string]) { + $argument + continue + } + if ($argument -notlike '%!*!%') { + $argument + continue + } + $artifactName = $argument -replace '^%!(.+)!%$', '$1' + $artifactObject = Get-PSMDBuildArtifact -Name $artifactName + if (-not $artifactObject) { throw "Artifact for arguments not found: $artifactName" } + $artifactObject.Value + } + + try { Invoke-Command -FilePath $scriptPath -ArgumentList $actualArguments -ErrorAction Stop } + catch { throw } +} + +$params = @{ + Name = 'script' + Action = $action + Description = 'Execute a scriptfile' + Parameters = @{ + Path = '(mandatory) Path to the scriptfile to run. Use %ProjectRoot% to reference the same folder the build action file is stored in. To insert an artifact, wrap its name in both percent and exclamation-mark symbols like this: "%!ArtifactName!%"' + ArgumentList = 'Any number of arguments to pass to the scripts. To insert artifacts, specify a string with the special notation "%!ArtifactName!%"' + } +} + +Register-PSMDBuildAction @params \ No newline at end of file diff --git a/PSModuleDevelopment/internal/functions/build/Export-PsmdBuildProjectFile.ps1 b/PSModuleDevelopment/internal/functions/build/Export-PsmdBuildProjectFile.ps1 new file mode 100644 index 0000000..1759ba6 --- /dev/null +++ b/PSModuleDevelopment/internal/functions/build/Export-PsmdBuildProjectFile.ps1 @@ -0,0 +1,48 @@ +function Export-PsmdBuildProjectFile { +<# + .SYNOPSIS + Exports a build project object to file. + + .DESCRIPTION + Exports a build project object to file. + Strips out all superfluous properties on steps to improve readability of output. + + .PARAMETER OutPath + The path to write the file to. + + .PARAMETER ProjectObject + The build project to export. + + .EXAMPLE + PS C:\> $projectObject | Export-PsmdBuildProjectFile -OutPath $outPath + + Exports the specified build project object to file. +#> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] + $OutPath, + + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + $ProjectObject + ) + + process { + $steps = foreach ($step in $ProjectObject.Steps) { + $newStep = $step | ConvertTo-PSFHashtable -Include Name, Weight, Action + if ($step.Dependency) { $newStep.Dependency = $step.Dependency } + if ($step.Parameters) { + $parameters = $step.Parameters | ConvertTo-PSFHashtable + if ($parameters.Count -gt 0) { $newStep.Parameters = $parameters } + } + if ($step.Condition -and $step.ConditionSet) { + $newStep.Condition = $step.Condition + $newStep.ConditionSet = $step.ConditionSet + } + [PSCustomObject]$newStep + } + $ProjectObject.Steps = $steps | Sort-Object Weight + $ProjectObject | ConvertTo-Json -Depth 10 | Set-Content -Path $OutPath -Encoding UTF8 -ErrorAction Stop + } +} \ No newline at end of file From 5b335614c30b2178f3c1e1e1ba0ec50653d2f57b Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Sun, 9 May 2021 02:22:08 +0200 Subject: [PATCH 5/8] fixing parameter processing --- PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 index 80c4dbd..73f2a67 100644 --- a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 @@ -151,7 +151,7 @@ #region Execution $parameters = @{ RootPath = Split-Path -Path $projectPath - Parameters = $step.Parameters + Parameters = $step.Parameters | ConvertTo-PSFHashtable ProjectName = $projectObject.Name StepName = $step.Name } From 0b8eb71fb99e3f1b55b7d061186a60a93e55dd57 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Sun, 16 May 2021 10:20:16 +0200 Subject: [PATCH 6/8] updates --- .../bin/PSModuleDevelopment.dll | Bin 18944 -> 22016 bytes .../bin/PSModuleDevelopment.pdb | Bin 62976 -> 79360 bytes .../bin/PSModuleDevelopment.xml | 150 ++++++++++++++++++ .../build/Invoke-PSMDBuildProject.ps1 | 2 + .../build/Resolve-PSMDBuildStepParameter.ps1 | 18 ++- .../internal/buildActions/command.action.ps1 | 74 +++++++++ .../buildActions/copy-item.action.ps1 | 33 +++- .../buildActions/new-pssession.action.ps1 | 2 +- .../buildActions/remove-item.action.ps1 | 19 ++- .../buildActions/remove-pssession.action.ps1 | 2 +- .../internal/buildActions/script.action.ps1 | 55 ++++--- .../Template/Parameter/ParameterScript.cs | 16 +- .../PSModuleDevelopment/Template/Template.cs | 11 +- 13 files changed, 341 insertions(+), 41 deletions(-) create mode 100644 PSModuleDevelopment/internal/buildActions/command.action.ps1 diff --git a/PSModuleDevelopment/bin/PSModuleDevelopment.dll b/PSModuleDevelopment/bin/PSModuleDevelopment.dll index 4e23017ca51516838ec42540ddbd5d14a579f265..4d4a049f6ce6cae0f5f9cd32386b2a2f5779e132 100644 GIT binary patch literal 22016 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 delta 8180 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 E0hGQ`!2kdN diff --git a/PSModuleDevelopment/bin/PSModuleDevelopment.pdb b/PSModuleDevelopment/bin/PSModuleDevelopment.pdb index e84f1e75cbf9a314d6e419f763ca72bb99572d9b..68f03cbef640bb42d4a42fced01da0c450cbfd70 100644 GIT binary patch delta 15217 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 c8bqdQjp;n5acB* The constructor info object describing the constructor + + + Base class for all kinds of parameters gen 2+ + + + + + Name of the parameter + + + + + Description of the parameter + + + + + Get the value associated with this parameter + + The value to insert into the artifact generated from the template + + + + A template parameter where the user is prompted for input. + + + + + The value provided by the user + + + + + List of legal values to provide + + + + + A validation pattern that needs to be met. + + + + + An error description that will be shown if the user provides invalid input to a parameter with pattern validation. + + + + + Test whether the input meets the validation rules + + The value to test + Whether the value is valid. + + + + Return the value specified by the user. + + The value specified by the user + + + + Parameter type executing + + + + + The scriptblock to execute. + Wrapped as string for serialization purposes. + + + + + The value of the scriptblock. + Populated by the GetValue() method usually called with the "StartUp" timing. + + + + + When exactly during the template process should this scriptblock be executed? + + + + + Setting this to true will cause the Invoke-PSMDTemplate command to omit inserting values for the + + + + + Returns the string value of the scriptblock by executing it! + + The string value of the scriptblock by executing it! + + + + Execute the scriptblock "Just-in-time" during either PreItemCreation or PostItemCreation Timing. + + The file/directory info object of the object recently or about to be created + Returns a string value resulting from the scriptblock to insert + + + + Execute the scriptblock "Just-in-time" during either PreItemCreation or PostItemCreation Timing. + + The path to the file/directory info object of the object recently or about to be created + Whether the object (about to be) created is a file. + Returns a string value resulting from the scriptblock to insert + + + + When will a specific scriptblock parameter be executed? + + + + + Executed when starting the overall template invocation + + + + + Executed before an individual item using it is created. + Values will be inserted into the file-content before writing to disk if applicable. + + + + + Executed after the individual item using it has been created. + Output will be discarded, but scriptblock will receive path of file / folder. + + + + + Executed after the entire project has been written. + Enables post-processing. + + A script used to calculate content to be inserted @@ -395,11 +530,21 @@ List of scripts that will be invoked on initialization + + + List of generation 2 parameters to include + + Items in the root directory of the template (which may contain children themselves). + + + What design generation is the template? + + Returns the template digest used as index file. @@ -461,6 +606,11 @@ The path to the template file + + + What template generation is this file? + + The version-qualified name of the template diff --git a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 index 73f2a67..bc8018c 100644 --- a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 @@ -154,8 +154,10 @@ Parameters = $step.Parameters | ConvertTo-PSFHashtable ProjectName = $projectObject.Name StepName = $step.Name + ParametersFromArtifacts = $step.ParametersFromArtifacts | ConvertTo-PSFHashtable } if (-not $parameters.Parameters) { $parameters.Parameters = @{ } } + if (-not $parameters.ParametersFromArtifacts) { $parameters.ParametersFromArtifacts = @{ } } try { $null = & $actionObject.Action $parameters } catch { Write-StepResult @resultDef -Status Failed -Data $_ -ContinueLabel main diff --git a/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 b/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 index fbf8f3c..5ea62b9 100644 --- a/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 +++ b/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 @@ -11,6 +11,10 @@ The hashtable containing the currently specified parameters from the step configuration within the build project file. Only settings not already defined there are taken from configuration. + .PARAMETER FromArtifacts + The hashtable mapping parameters from artifacts. + This allows dynamically assigning artifacts to parameters. + .PARAMETER ProjectName The name of the project being executed. Supplementary parameters taken from configuration will pick up settings based on this name: @@ -23,7 +27,7 @@ .EXAMPLE PS C:\> Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName VMDeployment -StepName 'Create Session' - + Adds parameters provided through configuration. #> [CmdletBinding()] @@ -32,6 +36,10 @@ [hashtable] $Parameters, + [Parameter(Mandatory = $true)] + [hashtable] + $FromArtifacts, + [Parameter(Mandatory = $true)] [string] $ProjectName, @@ -42,15 +50,19 @@ ) process { + # Process parameters from Configuration $configObject = Select-PSFConfig -FullName "PSModuleDevelopment.BuildParam.$ProjectName.$StepName.*" - if (-not $configObject) { return $Parameters } - foreach ($property in $configObject.PSObject.Properties) { if ($property.Name -in '_Name', '_FullName', '_Depth', '_Children') { continue } if ($Parameters.ContainsKey($property.Name)) { continue } $Parameters[$property.Name] = $property.Value } + # Process parameters from Artifacts + foreach ($pair in $FromArtifacts.GetEnumerator()) { + $Parameters[$pair.Key] = (Get-PSMDBuildArtifact -Name $pair.Value).Value + } + $Parameters } } \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/command.action.ps1 b/PSModuleDevelopment/internal/buildActions/command.action.ps1 new file mode 100644 index 0000000..9a8e034 --- /dev/null +++ b/PSModuleDevelopment/internal/buildActions/command.action.ps1 @@ -0,0 +1,74 @@ +$action = { + param ( + $Parameters + ) + + $rootPath = $Parameters.RootPath + $actualParameters = $Parameters.Parameters + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -FromArtifacts $Parameters.ParametersFromArtifacts -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + + #region Process Parameters + if (-not $actualParameters.Command) { + throw "Mandatory parameter: Command not specified" + } + + if ($actualParameters.Command -is [System.Management.Automation.ScriptBlock]) { + $scriptblock = $actualParameters.Command + } + else { + try { $scriptblock = [scriptblock]::Create($actualParameters.Command) } + catch { + throw "Error parsing command '$($actualParameters.Command)' : $_" + } + } + + $actualArguments = foreach ($argument in $actualParameters.ArgumentList) { + if ($argument -isnot [string]) { + $argument + continue + } + if ($argument -notlike '%!*!%') { + $argument + continue + } + $artifactName = $argument -replace '^%!(.+)!%$', '$1' + $artifactObject = Get-PSMDBuildArtifact -Name $artifactName + if (-not $artifactObject) { throw "Artifact for arguments not found: $artifactName" } + $artifactObject.Value + } + + $inSession = $null + if ($actualParameters.InSession) { + if ($actualParameters.InSession -is [System.Management.Automation.Runspaces.PSSession]) { + $inSession = $actualParameters.InSession + } + $artifactObject = Get-PSMDBuildArtifact -Name $actualParameters.InSession + if (-not $artifactObject) { throw "Artifact for parameter InSession not found: $($actualParameters.InSession)" } + if ($artifactObject.Value -isnot [System.Management.Automation.Runspaces.PSSession]) { throw "Artifact for parameter InSession ($($actualParameters.InSession)) is not a pssession!" } + $inSession = $artifactObject.Value + } + #endregion Process Parameters + + #region Execution + $invokeParam = @{ + ScriptBlock = $scriptblock + ArgumentList = $actualArguments + } + if ($inSession) { $invokeParam.Session = $inSession } + try { Invoke-Command @invokeParam -ErrorAction Stop } + catch { throw } + #endregion Execution +} + +$params = @{ + Name = 'command' + Action = $action + Description = 'Execute a scriptblock' + Parameters = @{ + Command = '(mandatory) Scriptcode to run' + ArgumentList = 'Any number of arguments to pass to the command. To insert artifacts, specify a string with the special notation "%!ArtifactName!%"' + InSession = 'Execute the scriptfile in the target PSSession. Either provide a full session object or an artifact name pointing at one.' + } +} + +Register-PSMDBuildAction @params \ No newline at end of file diff --git a/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 b/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 index 9798055..bb8aa67 100644 --- a/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/copy-item.action.ps1 @@ -5,7 +5,26 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters - $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -FromArtifacts $Parameters.ParametersFromArtifacts -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + + #region Utility Functions + function ConvertTo-PSSession { + [CmdletBinding()] + param ( + [Parameter(ValueFromPipeline = $true)] + $InputObject + ) + process { + if ($InputObject -is [System.Management.Automation.Runspaces.PSSession]) { + return $InputObject + } + $artifactValue = (Get-PSMDBuildArtifact -Name $InputObject).Value + if ($artifactValue -is [System.Management.Automation.Runspaces.PSSession]) { + return $artifactValue + } + } + } + #endregion Utility Functions if (-not ($actualParameters.Path -and $actualParameters.Destination)) { throw "Invalid parameters! Specify both Path and Destination." @@ -18,18 +37,18 @@ if ($actualParameters.Recurse) { $copyParam.Recurse = $true } if ($actualParameters.Force) { $copyParam.Force = $true } if ($actualParameters.FromSession) { - $artifact = Get-PSMDBuildArtifact -Name $actualParameters.FromSession - if (-not $artifact) { + $fromSession = $actualParameters.FromSession | ConvertTo-PSSession + if (-not $fromSession) { throw "FromSession $($actualParameters.FromSession) not found!" } - $copyParam.FromSession = $artifact.Value + $copyParam.FromSession = $fromSession } if ($actualParameters.ToSession) { - $artifact = Get-PSMDBuildArtifact -Name $actualParameters.ToSession - if (-not $artifact) { + $toSession = $actualParameters.ToSession | ConvertTo-PSSession + if (-not $toSession) { throw "ToSession $($actualParameters.ToSession) not found!" } - $copyParam.ToSession = $artifact.Value + $copyParam.ToSession = $toSession } foreach ($path in $paths) { try { Copy-Item @copyParam -Path $path -ErrorAction Stop } diff --git a/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 b/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 index aec1526..6406f4e 100644 --- a/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/new-pssession.action.ps1 @@ -5,7 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters - $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -FromArtifacts $Parameters.ParametersFromArtifacts -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if (-not $actualParameters.ArtifactName) { throw "No ArtifactName specified! Unable to publish remoting session for build." } if (-not ($actualParameters.VMName -or $actualParameters.ComputerName)) { throw "Neither ComputerName nor VMName specified, unable to connect to nothing!" } diff --git a/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 b/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 index 9794caf..624ac1b 100644 --- a/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/remove-item.action.ps1 @@ -5,7 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters - $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -FromArtifacts $Parameters.ParametersFromArtifacts -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if (-not $actualParameters.Path) { throw "Invalid parameters! Specify a Path to delete." @@ -15,13 +15,20 @@ $deleteParam = @{ } if ($actualParameters.Recurse) { $deleteParam.Recurse = $true } if ($actualParameters.Force) { $deleteParam.Force = $true } + + $inSession = $null if ($actualParameters.InSession) { - $artifact = Get-PSMDBuildArtifact -Name $actualParameters.InSession - if (-not $artifact) { - throw "InSession $($actualParameters.InSession) not found!" + if ($actualParameters.InSession -is [System.Management.Automation.Runspaces.PSSession]) { + $inSession = $actualParameters.InSession } - - $failed = Invoke-Command -Session $artifact.Value -ScriptBlock { + $artifactObject = Get-PSMDBuildArtifact -Name $actualParameters.InSession + if (-not $artifactObject) { throw "Artifact for parameter InSession not found: $($actualParameters.InSession)" } + if ($artifactObject.Value -isnot [System.Management.Automation.Runspaces.PSSession]) { throw "Artifact for parameter InSession ($($actualParameters.InSession)) is not a pssession!" } + $inSession = $artifactObject.Value + } + + if ($inSession) { + $failed = Invoke-Command -Session $inSession -ScriptBlock { param ($DeleteParam, $Paths) foreach ($path in $Paths) { diff --git a/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 b/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 index 48e5b75..a6e80e5 100644 --- a/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/remove-pssession.action.ps1 @@ -5,7 +5,7 @@ $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters - $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -FromArtifacts $Parameters.ParametersFromArtifacts -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName if ($actualParameters.All) { foreach ($artifact in Get-PSMDBuildArtifact -Tag pssession) { diff --git a/PSModuleDevelopment/internal/buildActions/script.action.ps1 b/PSModuleDevelopment/internal/buildActions/script.action.ps1 index 806eb7b..6826213 100644 --- a/PSModuleDevelopment/internal/buildActions/script.action.ps1 +++ b/PSModuleDevelopment/internal/buildActions/script.action.ps1 @@ -1,25 +1,18 @@ $action = { - param ( - $Parameters - ) + param ( + $Parameters + ) - $rootPath = $Parameters.RootPath + $rootPath = $Parameters.RootPath $actualParameters = $Parameters.Parameters - $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + $actualParameters = Resolve-PSMDBuildStepParameter -Parameters $actualParameters -FromArtifacts $Parameters.ParametersFromArtifacts -ProjectName $Parameters.ProjectName -StepName $Parameters.StepName + #region Process Parameters if (-not $actualParameters.Path) { throw "Mandatory parameter: Path not specified" } - if ($actualParameters.Path -notlike '%!*!%') { - $scriptPath = $actualParameters.Path -replace '%ProjectRoot%', $rootPath - } - else { - $artifactName = $actualParameters.Path -replace '^%!(.+)!%$', '$1' - $artifactObject = Get-PSMDBuildArtifact -Name $artifactName - if (-not $artifactObject) { throw "Artifact not found: $artifactName" } - $scriptPath = $artifactObject.Value - } + $scriptPath = $actualParameters.Path -replace '%ProjectRoot%', $rootPath if (-not (Test-Path $scriptPath)) { throw "Cannot find resolved script path: $scriptPath" @@ -40,18 +33,38 @@ $artifactObject.Value } - try { Invoke-Command -FilePath $scriptPath -ArgumentList $actualArguments -ErrorAction Stop } + $inSession = $null + if ($actualParameters.InSession) { + if ($actualParameters.InSession -is [System.Management.Automation.Runspaces.PSSession]) { + $inSession = $actualParameters.InSession + } + $artifactObject = Get-PSMDBuildArtifact -Name $actualParameters.InSession + if (-not $artifactObject) { throw "Artifact for parameter InSession not found: $($actualParameters.InSession)" } + if ($artifactObject.Value -isnot [System.Management.Automation.Runspaces.PSSession]) { throw "Artifact for parameter InSession ($($actualParameters.InSession)) is not a pssession!" } + $inSession = $artifactObject.Value + } + #endregion Process Parameters + + #region Execution + $invokeParam = @{ + FilePath = $scriptPath + ArgumentList = $actualArguments + } + if ($inSession) { $invokeParam.Session = $inSession } + try { Invoke-Command @invokeParam -ErrorAction Stop } catch { throw } + #endregion Execution } $params = @{ - Name = 'script' - Action = $action - Description = 'Execute a scriptfile' - Parameters = @{ - Path = '(mandatory) Path to the scriptfile to run. Use %ProjectRoot% to reference the same folder the build action file is stored in. To insert an artifact, wrap its name in both percent and exclamation-mark symbols like this: "%!ArtifactName!%"' + Name = 'script' + Action = $action + Description = 'Execute a scriptfile' + Parameters = @{ + Path = '(mandatory) Path to the scriptfile to run. Use %ProjectRoot% to reference the same folder the build action file is stored in.' ArgumentList = 'Any number of arguments to pass to the scripts. To insert artifacts, specify a string with the special notation "%!ArtifactName!%"' - } + InSession = 'Execute the scriptfile in the target PSSession. Either provide a full session object or an artifact name pointing at one.' + } } Register-PSMDBuildAction @params \ No newline at end of file diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs index 39b8eff..c757440 100644 --- a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Parameter/ParameterScript.cs @@ -13,6 +13,7 @@ namespace PSModuleDevelopment.Template.Parameter /// /// Parameter type executing /// + [Serializable] public class ParameterScript : ParameterBase { /// @@ -65,12 +66,25 @@ public override string GetValue() /// /// Execute the scriptblock "Just-in-time" during either PreItemCreation or PostItemCreation Timing. /// - /// The file/directory info object of the file recently or about to be created + /// The file/directory info object of the object recently or about to be created /// Returns a string value resulting from the scriptblock to insert public string GetInTimeValue(FileSystemInfo Info) { try { return (string)LanguagePrimitives.ConvertTo(_ScriptBlock.InvokeEx(Info, true, true, false), typeof(string)); } catch (Exception e) { return $""; } } + + /// + /// Execute the scriptblock "Just-in-time" during either PreItemCreation or PostItemCreation Timing. + /// + /// The path to the file/directory info object of the object recently or about to be created + /// Whether the object (about to be) created is a file. + /// Returns a string value resulting from the scriptblock to insert + public string GetInTimeValue(string Path, bool IsFile) + { + if (IsFile) + return GetInTimeValue(new FileInfo(Path)); + return GetInTimeValue(new DirectoryInfo(Path)); + } } } diff --git a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs index 6682b15..0fa0d85 100644 --- a/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs +++ b/library/PSModuleDevelopment/PSModuleDevelopment/Template/Template.cs @@ -58,6 +58,11 @@ public class Template /// public Dictionary Scripts = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// List of generation 2 parameters to include + /// + public Dictionary Parameters2 = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// /// Items in the root directory of the template (which may contain children themselves). /// @@ -74,12 +79,16 @@ public class Template /// A TemplateInfo object describing this template. public TemplateInfo ToTemplateInfo() { + List parameters = new List(Parameters); + if (Parameters2.Count > 0) + parameters.AddRange(Parameters2.Values.Where(o => o.GetType().Name == "ParameterPrompt").Select(o => o.Name)); + TemplateInfo info = new TemplateInfo(); info.Author = Author; info.CreatedOn = CreatedOn; info.Description = Description; info.Name = Name; - info.Parameters = Parameters; + info.Parameters = parameters; info.Tags = Tags; info.Type = Type; info.Version = Version; From ac1aa72821c7adea29bb42a2d36a5a63e056eba9 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 20 Jul 2021 18:20:47 +0200 Subject: [PATCH 7/8] alias & template updates --- PSModuleDevelopment/PSModuleDevelopment.psd1 | 2 +- PSModuleDevelopment/changelog.md | 12 +- .../functions/help/Get-PSMDHelp.ps1 | 9 +- .../moduledebug/Import-PSMDModuleDebug.ps1 | 4 +- .../moduledebug/Set-PSMDModuleDebug.ps1 | 4 +- .../functions/refactor/Read-PSMDScript.ps1 | 4 +- .../templating/Invoke-PSMDTemplate.ps1 | 5 +- .../templating/New-PSMDDotNetProject.ps1 | 5 +- .../utility/Find-PSMDFileContent.ps1 | 4 +- .../functions/utility/Restart-PSMDShell.ps1 | 5 +- templates/AzureFunction/PSMDTemplate.ps1 | 17 +-- templates/AzureFunction/build/build.ps1 | 23 +++ .../AzureFunction/{ => function}/host.json | 0 .../{ => function}/modules/readme.md | 0 templates/AzureFunction/function/profile.ps1 | 132 ++++++++++++++++++ .../{ => function}/requirements.psd1 | 0 templates/AzureFunction/profile.ps1 | 21 --- .../\303\276name\303\276/functions/readme.md" | 3 + .../\303\276name\303\276.psd1" | 103 ++++++++++++++ .../\303\276name\303\276.psm1" | 3 + templates/AzureFunctionRest/run.ps1 | 41 ++---- templates/PSFProject/.github/FUNDING.yml | 12 ++ .../PSFProject/.github/workflows/build.yml | 23 +++ .../PSFProject/.github/workflows/validate.yml | 15 ++ templates/PSFProject/PSMDTemplate.ps1 | 3 +- templates/PSFTests/PSMDTemplate.ps1 | 2 +- .../general/PSScriptAnalyzer.Tests.ps1 | 4 +- .../PSFTests/general/strings.Exceptions.ps1 | 17 +++ templates/PSFTests/general/strings.Tests.ps1 | 10 +- 29 files changed, 393 insertions(+), 90 deletions(-) create mode 100644 templates/AzureFunction/build/build.ps1 rename templates/AzureFunction/{ => function}/host.json (100%) rename templates/AzureFunction/{ => function}/modules/readme.md (100%) create mode 100644 templates/AzureFunction/function/profile.ps1 rename templates/AzureFunction/{ => function}/requirements.psd1 (100%) delete mode 100644 templates/AzureFunction/profile.ps1 create mode 100644 "templates/AzureFunction/\303\276name\303\276/functions/readme.md" create mode 100644 "templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psd1" create mode 100644 "templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psm1" create mode 100644 templates/PSFProject/.github/FUNDING.yml create mode 100644 templates/PSFProject/.github/workflows/build.yml create mode 100644 templates/PSFProject/.github/workflows/validate.yml diff --git a/PSModuleDevelopment/PSModuleDevelopment.psd1 b/PSModuleDevelopment/PSModuleDevelopment.psd1 index 03e7840..2a260fc 100644 --- a/PSModuleDevelopment/PSModuleDevelopment.psd1 +++ b/PSModuleDevelopment/PSModuleDevelopment.psd1 @@ -4,7 +4,7 @@ RootModule = 'PSModuleDevelopment.psm1' # Version number of this module. - ModuleVersion = '2.2.9.107' + ModuleVersion = '2.2.10.120' # ID used to uniquely identify this module GUID = '37dd5fce-e7b5-4d57-ac37-832055ce49d6' diff --git a/PSModuleDevelopment/changelog.md b/PSModuleDevelopment/changelog.md index 453f486..c55f4fa 100644 --- a/PSModuleDevelopment/changelog.md +++ b/PSModuleDevelopment/changelog.md @@ -1,8 +1,18 @@ # Changelog -## ??? +## 2.2.10.120 (2021-07-20) - New: Build Component - define build workflows based on pre-defined & extensible action code +- Upd: Template AzureFunction - new layout with better build automation +- Upd: Template AzureFunctionRest - new layout to integrate into new AzureFunction template +- Upd: Template PSFProject - added Github Actions integration +- Upd: Aliases - removed "AllScope" option +- Fix: Template PSFTest - fixed PSScriptAnalyzer test path detection +- Fix: Template PSFTest - fixed string LegalSurplus exception being ignored +- Fix: Template PSFModule - fixed PSScriptAnalyzer test path detection +- Fix: Template PSFModule - fixed string LegalSurplus exception being ignored +- Fix: Template PSFProject - fixed PSScriptAnalyzer test path detection +- Fix: Template PSFProject - fixed string LegalSurplus exception being ignored - Fix: TemplateStore - default path iss invalid on MAC (#136) - Fix: Invoke-PSMDTemplate - unreliable string replacement through -replace operator (#113) - Fix: Publish-PSMDScriptFile - insufficient exclude paths (#138; @Callidus2000) diff --git a/PSModuleDevelopment/functions/help/Get-PSMDHelp.ps1 b/PSModuleDevelopment/functions/help/Get-PSMDHelp.ps1 index 98b7a6f..fa6bc5d 100644 --- a/PSModuleDevelopment/functions/help/Get-PSMDHelp.ps1 +++ b/PSModuleDevelopment/functions/help/Get-PSMDHelp.ps1 @@ -113,12 +113,8 @@ PS C:\> Get-PSMDHelp Get-Help "en-us" -Detailed Gets the detailed help text of Get-Help in English - - .NOTES - Version 1.0.0.0 - Author: Friedrich Weinmann - Created on: August 15th, 2016 #> + [Alias('hex')] [CmdletBinding(DefaultParameterSetName = "AllUsersView")] Param ( [Parameter(ParameterSetName = "Parameters", Mandatory = $true)] @@ -209,5 +205,4 @@ try { $steppablePipeline.End() } catch { throw } } -} -New-Alias -Name hex -Value Get-PSMDHelp -Scope Global -Option AllScope \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 b/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 index 7eca92d..2800849 100644 --- a/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 +++ b/PSModuleDevelopment/functions/moduledebug/Import-PSMDModuleDebug.ps1 @@ -15,6 +15,7 @@ Imports the cPSNetwork module as it was configured to be imported using Set-ModuleDebug. #> + [Alias('ipmod')] [CmdletBinding()] param ( [string] @@ -40,5 +41,4 @@ [System.Management.Automation.ScriptBlock]::Create($____module.PostImportAction).Invoke() } } -} -New-Alias -Name ipmod -Value Import-ModuleDebug -Option AllScope -Scope Global \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/moduledebug/Set-PSMDModuleDebug.ps1 b/PSModuleDevelopment/functions/moduledebug/Set-PSMDModuleDebug.ps1 index a9a8e65..92ea656 100644 --- a/PSModuleDevelopment/functions/moduledebug/Set-PSMDModuleDebug.ps1 +++ b/PSModuleDevelopment/functions/moduledebug/Set-PSMDModuleDebug.ps1 @@ -65,6 +65,7 @@ Note: Using Write-Host is generally - but not always - bad practice Note: Verbose output during module import is generally discouraged (doesn't apply to tests of course) #> + [Alias('smd')] [CmdletBinding(DefaultParameterSetName = "Name", SupportsShouldProcess = $true)] Param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "Name", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] @@ -176,5 +177,4 @@ } #endregion Name } -} -Set-Alias -Name smd -Value Set-PSMDModuleDebug -Option AllScope -Scope Global \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/refactor/Read-PSMDScript.ps1 b/PSModuleDevelopment/functions/refactor/Read-PSMDScript.ps1 index 88a7105..b7f9fde 100644 --- a/PSModuleDevelopment/functions/refactor/Read-PSMDScript.ps1 +++ b/PSModuleDevelopment/functions/refactor/Read-PSMDScript.ps1 @@ -24,6 +24,7 @@ Parses all script files in the current directory #> + [Alias('parse')] [CmdletBinding()] param ( [Parameter(Position = 0, ParameterSetName = 'Script', Mandatory = $true)] @@ -78,5 +79,4 @@ } } } -} -Set-Alias -Name parse -Value Read-PSMDScript \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 b/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 index 970e73e..ce29e0c 100644 --- a/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 +++ b/PSModuleDevelopment/functions/templating/Invoke-PSMDTemplate.ps1 @@ -78,6 +78,7 @@ #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSPossibleIncorrectUsageOfAssignmentOperator", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + [Alias('imt')] [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'NameStore')] @@ -390,6 +391,4 @@ } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue } } -} - -if (-not (Test-Path Alias:\imt)) { Set-Alias -Name imt -Value Invoke-PSMDTemplate } \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 b/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 index 8e2fdd8..a34ca7c 100644 --- a/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 +++ b/PSModuleDevelopment/functions/templating/New-PSMDDotNetProject.ps1 @@ -59,6 +59,7 @@ - It will skip the automatic restore of the project on create #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + [Alias('dotnetnew')] [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = 'Create')] Param ( [Parameter(Position = 0, Mandatory = $true, ParameterSetName = 'Create')] @@ -175,6 +176,4 @@ & dotnet.exe new $dotNetArgs } } -} - -New-Alias -Name dotnetnew -Value New-PSMDDotNetProject -Option AllScope -Scope Global -ErrorAction Ignore \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/utility/Find-PSMDFileContent.ps1 b/PSModuleDevelopment/functions/utility/Find-PSMDFileContent.ps1 index bf78b92..94094b0 100644 --- a/PSModuleDevelopment/functions/utility/Find-PSMDFileContent.ps1 +++ b/PSModuleDevelopment/functions/utility/Find-PSMDFileContent.ps1 @@ -31,6 +31,7 @@ Searches all module files for the string 'Get-Test'. #> + [Alias('find')] [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0)] @@ -61,5 +62,4 @@ Get-ChildItem -Path $Path -Recurse | Where-Object Extension -Match $Extension | Select-String -Pattern $Pattern } -} -New-Alias -Name find -Value Find-PSMDFileContent -Scope Global -Option AllScope \ No newline at end of file +} \ No newline at end of file diff --git a/PSModuleDevelopment/functions/utility/Restart-PSMDShell.ps1 b/PSModuleDevelopment/functions/utility/Restart-PSMDShell.ps1 index 0e34124..67a8a5a 100644 --- a/PSModuleDevelopment/functions/utility/Restart-PSMDShell.ps1 +++ b/PSModuleDevelopment/functions/utility/Restart-PSMDShell.ps1 @@ -39,6 +39,7 @@ Author: Friedrich Weinmann Created on: August 6th, 2016 #> + [Alias('rss', 'Restart-Shell')] [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] Param ( [Switch] @@ -72,6 +73,4 @@ if (-not $NoExit) { exit } } } -} -New-Alias -Name Restart-Shell -Value Restart-PSMDShell -Option AllScope -Scope Global -New-Alias -Name rss -Value Restart-PSMDShell -Option AllScope -Scope Global \ No newline at end of file +} \ No newline at end of file diff --git a/templates/AzureFunction/PSMDTemplate.ps1 b/templates/AzureFunction/PSMDTemplate.ps1 index b77849a..131be22 100644 --- a/templates/AzureFunction/PSMDTemplate.ps1 +++ b/templates/AzureFunction/PSMDTemplate.ps1 @@ -1,10 +1,11 @@ @{ - TemplateName = 'AzureFunction' - Version = "1.0.0.0" - AutoIncrementVersion = $true - Tags = 'azure', 'function' - Author = 'Friedrich Weinmann' - Description = 'Basic Azure Function Template' - Exclusions = @("PSMDInvoke.ps1", ".PSMDDependency") # Contains list of files - relative path to root - to ignore when building the template - Scripts = @{ } + TemplateName = 'AzureFunction' + Version = "2.0.0" + AutoIncrementVersion = $true + Tags = 'azure', 'function' + Author = 'Friedrich Weinmann' + Description = 'Basic Azure Function Template' + Exclusions = @("PSMDInvoke.ps1", ".PSMDDependency") # Contains list of files - relative path to root - to ignore when building the template + Scripts = @{ } + NoFolder = $true # Whether invoking this template should generate a new folder ... or not. } \ No newline at end of file diff --git a/templates/AzureFunction/build/build.ps1 b/templates/AzureFunction/build/build.ps1 new file mode 100644 index 0000000..9a79a68 --- /dev/null +++ b/templates/AzureFunction/build/build.ps1 @@ -0,0 +1,23 @@ +param ( + [string] + $Repository = 'PSGallery' +) +$workingDirectory = Split-Path $PSScriptRoot + +# Prepare output path and copy function folder +Remove-Item -Path "$workingDirectory/publish" -Recurse -Force -ErrorAction Ignore +$buildFolder = New-Item -Path $workingDirectory -Name 'publish' -ItemType Directory -Force -ErrorAction Stop +Copy-Item -Path "$workingDirectory/function/*" -Destination $buildFolder.FullName -Recurse -Force + +# Process Dependencies +$requiredModules = (Import-PowerShellDataFile -Path "$workingDirectory/þnameþ/þnameþ.psd1").RequiredModules +foreach ($module in $requiredModules) { + Save-Module -Name $module -Path "$($buildFolder.FullName)/modules" -Force -Repository $Repository +} + +# Process Function Module +Copy-Item -Path "$workingDirectory/þnameþ" -Destination "$($buildFolder.FullName)/modules" -Force -Recurse + +# Package & Cleanup +Compress-Archive -Path "$($buildFolder.FullName)/*" -DestinationPath "$workingDirectory/Function.zip" +Remove-Item -Path $buildFolder.FullName -Recurse -Force -ErrorAction Ignore \ No newline at end of file diff --git a/templates/AzureFunction/host.json b/templates/AzureFunction/function/host.json similarity index 100% rename from templates/AzureFunction/host.json rename to templates/AzureFunction/function/host.json diff --git a/templates/AzureFunction/modules/readme.md b/templates/AzureFunction/function/modules/readme.md similarity index 100% rename from templates/AzureFunction/modules/readme.md rename to templates/AzureFunction/function/modules/readme.md diff --git a/templates/AzureFunction/function/profile.ps1 b/templates/AzureFunction/function/profile.ps1 new file mode 100644 index 0000000..be04a7d --- /dev/null +++ b/templates/AzureFunction/function/profile.ps1 @@ -0,0 +1,132 @@ +# Azure Functions profile.ps1 +# +# This profile.ps1 will get executed every "cold start" of your Function App. +# "cold start" occurs when: +# +# * A Function App starts up for the very first time +# * A Function App starts up after being de-allocated due to inactivity +# +# You can define helper functions, run commands, or specify environment variables +# NOTE: any variables defined that are not environment variables will get reset after the first execution +# Authenticate with Azure PowerShell using MSI. +# Remove this if you are not planning on using MSI or Azure PowerShell. + +if ($env:MSI_SECRET -and (Get-Module -ListAvailable Az.Accounts)) +{ + Connect-AzAccount -Identity +} + +# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. +# Enable-AzureRmAlias +# You can also define functions or aliases that can be referenced in any of your PowerShell functions. + +function Write-FunctionResult { + <# + .SYNOPSIS + Reports back the output / result of the function app. + + .DESCRIPTION + Reports back the output / result of the function app. + + .PARAMETER Status + Whether the function succeeded or not. + + .PARAMETER Body + Any data to include in the response. + + .EXAMPLE + PS C:\> Write-FunctionResult -Status OK -Body $newUser + + Reports success while returning the content of $newUser as output + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [System.Net.HttpStatusCode] + $Status, + + [AllowNull()] + $Body + ) + + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $Status + Body = $Body + }) +} + +function Get-RestParameterValue { + <# + .SYNOPSIS + Extract the exact value of a parameter provided by the user. + + .DESCRIPTION + Extract the exact value of a parameter provided by the user. + Expects either query or body parameters from the rest call to the http trigger. + + .PARAMETER Request + The request object provided as part of the function call. + + .PARAMETER Name + The name of the parameter to provide. + + .EXAMPLE + PS C:\> Get-RestParameterValue -Request $Request -Name Type + + Returns the value of the parameter "Type", as provided by the caller + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + $Request, + + [Parameter(Mandatory = $true)] + [string] + $Name + ) + + if ($Request.Query.$Name) { + return $Request.Query.$Name + } + $Request.Body.$Name +} + +function Get-RestParameter { + <# + .SYNOPSIS + Parses the rest request parameters for all values matching parameters on the specified command. + + .DESCRIPTION + Parses the rest request parameters for all values matching parameters on the specified command. + Returns a hashtable ready for splatting. + Does NOT assert mandatory parameters are specified, so command invocation may fail. + + .PARAMETER Request + The original rest request object, containing the caller's information such as parameters. + + .PARAMETER Command + The command to which to bind input parameters. + + .EXAMPLE + PS C:\> Get-RestParameter -Request $Request -Command Get-AzUser + + Retrieves all parameters on the incoming request that match a parameter on Get-AzUser + #> + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $true)] + $Request, + + [Parameter(Mandatory = $true)] + [string] + $Command + ) + + $commandInfo = Get-Command -Name $Command + $results = @{ } + foreach ($parameter in $commandInfo.Parameters.Keys) { + $value = Get-RestParameterValue -Request $Request -Name $parameter + if ($null -ne $value) { $results[$parameter] = $value } + } + $results +} \ No newline at end of file diff --git a/templates/AzureFunction/requirements.psd1 b/templates/AzureFunction/function/requirements.psd1 similarity index 100% rename from templates/AzureFunction/requirements.psd1 rename to templates/AzureFunction/function/requirements.psd1 diff --git a/templates/AzureFunction/profile.ps1 b/templates/AzureFunction/profile.ps1 deleted file mode 100644 index 3d09b77..0000000 --- a/templates/AzureFunction/profile.ps1 +++ /dev/null @@ -1,21 +0,0 @@ -# Azure Functions profile.ps1 -# -# This profile.ps1 will get executed every "cold start" of your Function App. -# "cold start" occurs when: -# -# * A Function App starts up for the very first time -# * A Function App starts up after being de-allocated due to inactivity -# -# You can define helper functions, run commands, or specify environment variables -# NOTE: any variables defined that are not environment variables will get reset after the first execution -# Authenticate with Azure PowerShell using MSI. -# Remove this if you are not planning on using MSI or Azure PowerShell. - -if ($env:MSI_SECRET -and (Get-Module -ListAvailable Az.Accounts)) -{ - Connect-AzAccount -Identity -} - -# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell. -# Enable-AzureRmAlias -# You can also define functions or aliases that can be referenced in any of your PowerShell functions. \ No newline at end of file diff --git "a/templates/AzureFunction/\303\276name\303\276/functions/readme.md" "b/templates/AzureFunction/\303\276name\303\276/functions/readme.md" new file mode 100644 index 0000000..81c52a2 --- /dev/null +++ "b/templates/AzureFunction/\303\276name\303\276/functions/readme.md" @@ -0,0 +1,3 @@ +# Functions + +Place all your function code here diff --git "a/templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psd1" "b/templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psd1" new file mode 100644 index 0000000..9c8f039 --- /dev/null +++ "b/templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psd1" @@ -0,0 +1,103 @@ +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'þnameþ.psm1' + + # Version number of this module. + ModuleVersion = '1.0.0' + + # Supported PSEditions + # CompatiblePSEditions = @() + + # ID used to uniquely identify this module + GUID = 'þ{ New-Guid }þ' + + # Author of this module + Author = 'þauthorþ' + + # Company or vendor of this module + CompanyName = 'þcompanyþ' + + # Copyright statement for this module + Copyright = '(c) þauthorþ. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'þdescriptionþ' + + # Minimum version of the PowerShell engine required by this module + # PowerShellVersion = '' + + # Modules that must be imported into the global environment prior to importing this module + # RequiredModules = @() + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @('bin\my.dll') + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + FunctionsToExport = @( + + ) + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + # CmdletsToExport = '*' + + # Variables to export from this module + # VariablesToExport = '*' + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + # AliasesToExport = '*' + + # DSC resources to export from this module + # DscResourcesToExport = @() + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable +} \ No newline at end of file diff --git "a/templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psm1" "b/templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psm1" new file mode 100644 index 0000000..2509b6f --- /dev/null +++ "b/templates/AzureFunction/\303\276name\303\276/\303\276name\303\276.psm1" @@ -0,0 +1,3 @@ +foreach ($file in Get-ChildItem $PSScriptRoot\functions -Recurse -Filter '*.ps1') { + . $file.FullName +} \ No newline at end of file diff --git a/templates/AzureFunctionRest/run.ps1 b/templates/AzureFunctionRest/run.ps1 index 1f5b0ea..6c6cc83 100644 --- a/templates/AzureFunctionRest/run.ps1 +++ b/templates/AzureFunctionRest/run.ps1 @@ -1,33 +1,18 @@ -using namespace System.Net - -# Input bindings are passed in via param block. -param ($Request, +param ( + $Request, - $TriggerMetadata) + $TriggerMetadata +) -# Write to the Azure Functions log stream. -Write-Host "PowerShell HTTP trigger function processed a request." -# Interact with query parameters or the body of the request. -$name = $Request.Query.Name -if (-not $name) -{ - $name = $Request.Body.Name -} +Write-Host "Trigger: þnameþ has been invoked" -if ($name) -{ - $status = [HttpStatusCode]::OK - $body = "Hello $name" -} -else -{ - $status = [HttpStatusCode]::BadRequest - $body = "Please pass a name on the query string or in the request body." -} +$parameters = Get-RestParameter -Request $Request -Command þnameþ -# Associate values to output bindings by calling 'Push-OutputBinding'. -Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = $status - Body = $body - }) +try { $results = þnameþ @parameters -ErrorAction Stop } +catch { + Write-FunctionResult -Status InternalServerError -Body $_ + $_ | Out-Host + return +} +Write-FunctionResult -Status OK -Body $results \ No newline at end of file diff --git a/templates/PSFProject/.github/FUNDING.yml b/templates/PSFProject/.github/FUNDING.yml new file mode 100644 index 0000000..4b16f59 --- /dev/null +++ b/templates/PSFProject/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/templates/PSFProject/.github/workflows/build.yml b/templates/PSFProject/.github/workflows/build.yml new file mode 100644 index 0000000..2bd1de8 --- /dev/null +++ b/templates/PSFProject/.github/workflows/build.yml @@ -0,0 +1,23 @@ +on: + push: + branches: + - master + +jobs: + build: + + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v1 + - name: Install Prerequisites + run: .\build\vsts-prerequisites.ps1 + shell: powershell + - name: Validate + run: .\build\vsts-validate.ps1 + shell: powershell + - name: Build + run: .\build\vsts-build.ps1 -ApiKey $env:APIKEY + shell: powershell + env: + APIKEY: ${{ secrets.ApiKey }} diff --git a/templates/PSFProject/.github/workflows/validate.yml b/templates/PSFProject/.github/workflows/validate.yml new file mode 100644 index 0000000..64387f3 --- /dev/null +++ b/templates/PSFProject/.github/workflows/validate.yml @@ -0,0 +1,15 @@ +on: [pull_request] + +jobs: + validate: + + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v1 + - name: Install Prerequisites + run: .\build\vsts-prerequisites.ps1 + shell: powershell + - name: Validate + run: .\build\vsts-validate.ps1 + shell: powershell diff --git a/templates/PSFProject/PSMDTemplate.ps1 b/templates/PSFProject/PSMDTemplate.ps1 index 05aa08e..37887a9 100644 --- a/templates/PSFProject/PSMDTemplate.ps1 +++ b/templates/PSFProject/PSMDTemplate.ps1 @@ -1,6 +1,6 @@ @{ TemplateName = 'PSFProject' - Version = "1.3.2.0" + Version = "1.3.3" AutoIncrementVersion = $true Tags = 'module','psframework' Author = 'Friedrich Weinmann' @@ -38,4 +38,5 @@ $null = New-Item -Path "$PSScriptRoot\..\.." -Name TestResults -ItemType Directo '$config.TestResult.Enabled = $true' } } + NoFolder = $true } \ No newline at end of file diff --git a/templates/PSFTests/PSMDTemplate.ps1 b/templates/PSFTests/PSMDTemplate.ps1 index 770fb51..7396c33 100644 --- a/templates/PSFTests/PSMDTemplate.ps1 +++ b/templates/PSFTests/PSMDTemplate.ps1 @@ -1,6 +1,6 @@ @{ TemplateName = 'PSFTests' # Insert name of template - Version = "2.0.0.0" # Version to build to + Version = "2.0.1" # Version to build to AutoIncrementVersion = $true # If a newer version than specified is present, instead of the specified version, make it one greater than the existing template Tags = @('Tests', 'PSFramework') # Insert Tags as desired Author = 'Friedrich Weinmann' # The author of the template, not the file / project created from it diff --git a/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 b/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 index 74e5a65..a99b60d 100644 --- a/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 +++ b/templates/PSFTests/general/PSScriptAnalyzer.Tests.ps1 @@ -12,7 +12,9 @@ if ($SkipTest) { return } $global:__pester_data.ScriptAnalyzer = New-Object System.Collections.ArrayList Describe 'Invoking PSScriptAnalyzer against commandbase' { - $commandFiles = Get-ChildItem -Path $CommandPath -Recurse | Where-Object Name -like "*.ps1" + $commandFiles = foreach ($path in $CommandPath) { + Get-ChildItem -Path $path -Recurse | Where-Object Name -like "*.ps1" + } $scriptAnalyzerRules = Get-ScriptAnalyzerRule foreach ($file in $commandFiles) diff --git a/templates/PSFTests/general/strings.Exceptions.ps1 b/templates/PSFTests/general/strings.Exceptions.ps1 index 0a11c98..b4c91f2 100644 --- a/templates/PSFTests/general/strings.Exceptions.ps1 +++ b/templates/PSFTests/general/strings.Exceptions.ps1 @@ -15,5 +15,22 @@ $exceptions['LegalSurplus'] = @( $exceptions['LegalSurplus'] = @( ) +<# +A list of entries that MAY be used without needing to have text defined. +This is intended for modules (re-)using strings provided by another module +#> +$exceptions['NoTextNeeded'] = @( + 'Validate.FSPath' + 'Validate.FSPath.File' + 'Validate.FSPath.FileOrParent' + 'Validate.FSPath.Folder' + 'Validate.Path' + 'Validate.Path.Container' + 'Validate.Path.Leaf' + 'Validate.TimeSpan.Positive' + 'Validate.Uri.Absolute' + 'Validate.Uri.Absolute.File' + 'Validate.Uri.Absolute.Https' +) $exceptions \ No newline at end of file diff --git a/templates/PSFTests/general/strings.Tests.ps1 b/templates/PSFTests/general/strings.Tests.ps1 index 5045dc1..eb83c05 100644 --- a/templates/PSFTests/general/strings.Tests.ps1 +++ b/templates/PSFTests/general/strings.Tests.ps1 @@ -9,17 +9,19 @@ Describe "Testing localization strings" { - $moduleRoot = (Get-Module þnameþ).ModuleBase + $moduleRoot = (Get-Module VHDX).ModuleBase $stringsResults = Export-PSMDString -ModuleRoot $moduleRoot $exceptions = & "$global:testroot\general\strings.Exceptions.ps1" foreach ($stringEntry in $stringsResults) { if ($stringEntry.String -eq "key") { continue } # Skipping the template default entry - It "Should be used & have text: $($stringEntry.String)" -TestCases @{ stringEntry = $stringEntry } { + It "Should be used & have text: $($stringEntry.String)" -TestCases @{ stringEntry = $stringEntry; exceptions = $exceptions } { if ($exceptions.LegalSurplus -notcontains $stringEntry.String) { $stringEntry.Surplus | Should -BeFalse - } - $stringEntry.Text | Should -Not -BeNullOrEmpty + } + if ($exceptions.NoTextNeeded -notcontains $stringEntry.String) { + $stringEntry.Text | Should -Not -BeNullOrEmpty + } } } } \ No newline at end of file From f38096ba57414b1b19f8bed18615d7066c280ae8 Mon Sep 17 00:00:00 2001 From: Friedrich Weinmann Date: Tue, 20 Jul 2021 18:31:19 +0200 Subject: [PATCH 8/8] fixing tests --- PSModuleDevelopment/PSModuleDevelopment.psd1 | 1 - PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 | 1 + PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 | 1 + PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 | 2 +- PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 | 1 + .../functions/build/Remove-PSMDBuildArtifact.ps1 | 1 + .../functions/build/Resolve-PSMDBuildStepParameter.ps1 | 1 + PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 | 1 + 8 files changed, 7 insertions(+), 2 deletions(-) diff --git a/PSModuleDevelopment/PSModuleDevelopment.psd1 b/PSModuleDevelopment/PSModuleDevelopment.psd1 index 2a260fc..630f8e0 100644 --- a/PSModuleDevelopment/PSModuleDevelopment.psd1 +++ b/PSModuleDevelopment/PSModuleDevelopment.psd1 @@ -86,7 +86,6 @@ 'Read-PSMDScript' 'Register-PSMDBuildAction' 'Remove-PSMDBuildArtifact' - 'Remove-PSMDBuildProject' 'Remove-PSMDModuleDebug' 'Remove-PSMDTemplate' 'Rename-PSMDParameter' diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 index 56807e4..011accc 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildArtifact.ps1 @@ -31,6 +31,7 @@ Returns all artifacts with the tag "pssession" #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding()] param ( [string] diff --git a/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 index de88f5b..93a7594 100644 --- a/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Get-PSMDBuildProject.ps1 @@ -25,6 +25,7 @@ Will load the build project stored in the file "C:\code\project\project.build.json" #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [CmdletBinding(DefaultParameterSetName = 'Path')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'Path')] diff --git a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 index bc8018c..ee8567b 100644 --- a/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/Invoke-PSMDBuildProject.ps1 @@ -90,7 +90,7 @@ Failed { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action -ErrorRecord $Data } ConditionNotMet { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action, $StepObject.Condition } DependencyNotMet { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action, $Data } - BadAction { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action } + BadAction { Write-PSFMessage @paramWritePSFMessage -StringValues $StepObject.Name, $StepObject.Action } } [PSCustomObject]@{ diff --git a/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 b/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 index a946bc4..6d44498 100644 --- a/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 +++ b/PSModuleDevelopment/functions/build/New-PSMDBuildProject.ps1 @@ -50,6 +50,7 @@ Create a new build project named 'VMDeployment' in the folder 'C:\Code\VMDeployment' #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding(DefaultParameterSetName = 'default')] param ( [Parameter(Mandatory = $true)] diff --git a/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 b/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 index f98213d..a6c354b 100644 --- a/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 +++ b/PSModuleDevelopment/functions/build/Remove-PSMDBuildArtifact.ps1 @@ -21,6 +21,7 @@ Removes all artifacts with the 'pssession' tag from the build pipeline. #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] diff --git a/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 b/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 index 5ea62b9..82c7ffc 100644 --- a/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 +++ b/PSModuleDevelopment/functions/build/Resolve-PSMDBuildStepParameter.ps1 @@ -30,6 +30,7 @@ Adds parameters provided through configuration. #> + [OutputType([hashtable])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] diff --git a/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 b/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 index b5037c1..07b3722 100644 --- a/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 +++ b/PSModuleDevelopment/functions/build/Set-PSMDBuildStep.ps1 @@ -51,6 +51,7 @@ Defines a new step named 'Create Session' using the 'new-pssession'-action. #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)]