From 3fb8811f854012fab93449485b127b4d91b76119 Mon Sep 17 00:00:00 2001 From: Nate Ferrell Date: Sun, 25 Aug 2019 00:05:49 -0500 Subject: [PATCH] ## 0.1.6 - 2019-08-24 * Added `Copy-DynamicParameters` to clone DynamicParams from another file or function. * Updated `Start-BuildScript` in `PSProfile.PowerTools` with better argument completers to enable listing available Tasks and other parameters directly from the build script you are executing. * Added `Dockerfile` to enable testing in an Ubuntu container. --- CHANGELOG.md | 7 + Dockerfile | 10 ++ PSProfile/PSProfile.psd1 | 2 +- PSProfile/Plugins/PSProfile.PowerTools.ps1 | 151 ++++++++++-------- .../Public/Helpers/Copy-DynamicParameters.ps1 | 134 ++++++++++++++++ .../en-US/about_PSProfile_Helpers.help.txt | 5 + 6 files changed, 238 insertions(+), 71 deletions(-) create mode 100644 Dockerfile create mode 100644 PSProfile/Public/Helpers/Copy-DynamicParameters.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 29128b7..b8cc4de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ * [PSProfile - ChangeLog](#psprofile---changelog) + * [0.1.6 - 2019-08-24](#016---2019-08-24) * [0.1.5 - 2019-08-22](#015---2019-08-22) * [0.1.4 - 2019-08-22](#014---2019-08-22) * [0.1.3 - 2019-08-20](#013---2019-08-20) @@ -10,6 +11,12 @@ # PSProfile - ChangeLog +## 0.1.6 - 2019-08-24 + +* Added `Copy-DynamicParameters` to clone DynamicParams from another file or function. +* Updated `Start-BuildScript` in `PSProfile.PowerTools` with better argument completers to enable listing available Tasks and other parameters directly from the build script you are executing. +* Added `Dockerfile` to enable testing in an Ubuntu container. + ## 0.1.5 - 2019-08-22 * Added `Export-PSProfileConfiguration` to export your configuration to a portable file. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e9434fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM mcr.microsoft.com/powershell:ubuntu-16.04 as base +RUN apt-get update && apt-get install -y git + +FROM base as src +LABEL maintainer="nferrell" +LABEL description="PSProfile container for Ubuntu 16.04" +LABEL vendor="scrthq" +COPY [".", "/tmp/PSProfile/"] +WORKDIR /tmp/PSProfile +CMD ["pwsh","-c","./build.ps1","-Task Build,Test"] diff --git a/PSProfile/PSProfile.psd1 b/PSProfile/PSProfile.psd1 index 7dd553f..3381313 100644 --- a/PSProfile/PSProfile.psd1 +++ b/PSProfile/PSProfile.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSProfile.psm1' # Version number of this module. -ModuleVersion = '0.1.5' +ModuleVersion = '0.1.6' # Supported PSEditions CompatiblePSEditions = @('Desktop','Core') diff --git a/PSProfile/Plugins/PSProfile.PowerTools.ps1 b/PSProfile/Plugins/PSProfile.PowerTools.ps1 index c5e7db5..44de93a 100644 --- a/PSProfile/Plugins/PSProfile.PowerTools.ps1 +++ b/PSProfile/Plugins/PSProfile.PowerTools.ps1 @@ -234,11 +234,9 @@ function Open-Code { } } -if ($null -ne (Get-Command Get-PSProfileArguments*)) { - Register-ArgumentCompleter -CommandName 'Open-Code' -ParameterName 'Path' -ScriptBlock { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly - } +Register-ArgumentCompleter -CommandName 'Open-Code' -ParameterName 'Path' -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly } Register-ArgumentCompleter -CommandName 'Open-Code' -ParameterName 'Language' -ScriptBlock { @@ -391,11 +389,10 @@ function Open-Item { } } -if ($null -ne (Get-Command Get-PSProfileArguments*)) { - Register-ArgumentCompleter -CommandName 'Open-Item' -ParameterName 'Path' -ScriptBlock { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly - } + +Register-ArgumentCompleter -CommandName 'Open-Item' -ParameterName 'Path' -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly } New-Alias -Name open -Value 'Open-Item' -Scope Global -Option AllScope -Force @@ -488,11 +485,9 @@ function Pop-Path { New-Alias -Name pop -Value Pop-Path -Option AllScope -Scope Global -Force -if ($null -ne (Get-Command Get-PSProfileArguments*)) { - Register-ArgumentCompleter -CommandName 'Push-Path' -ParameterName 'Path' -ScriptBlock { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly - } +Register-ArgumentCompleter -CommandName 'Push-Path' -ParameterName 'Path' -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly } function Get-Gist { @@ -718,59 +713,44 @@ function Start-BuildScript { #> [CmdletBinding(PositionalBinding = $false)] Param ( + [Parameter(Position = 0)] + [Alias('p')] + [String] + $Project, + [Parameter(Position = 1)] + [Alias('t')] + [String] + $Task, + [Parameter(Position = 2)] + [Alias('e')] + [String] + $Engine, [parameter()] - [Alias('ne')] + [Alias('ne','noe')] [Switch] $NoExit, [parameter()] - [Alias('nr')] + [Alias('nr','nor')] [Switch] $NoRestore ) DynamicParam { - $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary - $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute] - $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute - $ParamAttrib.Mandatory = $false - $ParamAttrib.Position = 0 - $AttribColl.Add($ParamAttrib) - $set = @() - $set += $global:PSProfile.PSBuildPathMap.Keys - $set += '.' - $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($set))) - $AttribColl.Add((New-Object System.Management.Automation.AliasAttribute('p'))) - $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Project', [string], $AttribColl) - $RuntimeParamDic.Add('Project', $RuntimeParam) - - $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute] - $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute - $ParamAttrib.Mandatory = $false - $ParamAttrib.Position = 1 - $AttribColl.Add($ParamAttrib) - $bldFile = Join-Path $PWD.Path "build.ps1" - $set = if (Test-Path $bldFile) { - ((([System.Management.Automation.Language.Parser]::ParseFile($bldFile, [ref]$null, [ref]$null)).ParamBlock.Parameters | Where-Object { $_.Name.VariablePath.UserPath -eq 'Task' }).Attributes | Where-Object { $_.TypeName.Name -eq 'ValidateSet' }).PositionalArguments.Value + $bldFolder = if ([String]::IsNullOrEmpty($PSBoundParameters['Project']) -or $PSBoundParameters['Project'] -eq '.') { + $PWD.Path + } + elseif ($Global:PSProfile.PSBuildPathMap.ContainsKey($PSBoundParameters['Project'])) { + Get-LongPath -Path $PSBoundParameters['Project'] } else { - @('Clean','Build','Import','Test','Deploy') + (Resolve-Path $PSBoundParameters['Project']).Path } - $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($set))) - $AttribColl.Add((New-Object System.Management.Automation.AliasAttribute('t'))) - $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Task', [string[]], $AttribColl) - $RuntimeParamDic.Add('Task', $RuntimeParam) - - $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute] - $ParamAttrib = New-Object System.Management.Automation.ParameterAttribute - $ParamAttrib.Mandatory = $false - $ParamAttrib.Position = 2 - $AttribColl.Add($ParamAttrib) - $set = @('powershell','pwsh','pwsh-preview') - $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($set))) - $AttribColl.Add((New-Object System.Management.Automation.AliasAttribute('e'))) - $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter('Engine', [string], $AttribColl) - $RuntimeParamDic.Add('Engine', $RuntimeParam) - - return $RuntimeParamDic + $bldFile = if ($bldFolder -like '*.ps1') { + $bldFolder + } + else { + Join-Path $bldFolder "build.ps1" + } + Copy-DynamicParameters -File $bldFile -ExcludeParameter Project,Task,Engine,NoExit,NoRestore } Process { if (-not $PSBoundParameters.ContainsKey('Project')) { @@ -807,8 +787,13 @@ function Start-BuildScript { $command += "```$false;" } $command += "Set-Location '$parent'; . .\build.ps1" - if ($PSBoundParameters.ContainsKey('Task')) { - $command += " -Task '$($PSBoundParameters['Task'] -join "','")'" + $PSBoundParameters.Keys | Where-Object {$_ -notin @('Project','Engine','NoExit','NoRestore','Debug','ErrorAction','ErrorVariable','InformationAction','InformationVariable','OutBuffer','OutVariable','PipelineVariable','WarningAction','WarningVariable','Verbose','Confirm','WhatIf')} | ForEach-Object { + if ($PSBoundParameters[$_].ToString() -in @('True','False')) { + $command += " -$($_):```$$($PSBoundParameters[$_].ToString())" + } + else { + $command += " -$($_) '$($PSBoundParameters[$_] -join "','")'" + } } $command += '"' Write-Verbose "Invoking expression: $command" @@ -821,10 +806,38 @@ function Start-BuildScript { } } -if ($null -ne (Get-Command Get-PSProfileArguments*)) { - Register-ArgumentCompleter -CommandName 'Start-BuildScript' -ParameterName 'Project' -ScriptBlock { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - Get-PSProfileArguments -WordToComplete "PSBuildPathMap.$wordToComplete" -FinalKeyOnly +Register-ArgumentCompleter -CommandName Start-BuildScript -ParameterName Project -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + Get-PSProfileArguments -WordToComplete "PSBuildPathMap.$wordToComplete" -FinalKeyOnly +} + +Register-ArgumentCompleter -CommandName Start-BuildScript -ParameterName Task -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + $bldFolder = if ([String]::IsNullOrEmpty($fakeBoundParameter.Project) -or $fakeBoundParameter.Project -eq '.') { + $PWD.Path + } + elseif ($Global:PSProfile.PSBuildPathMap.ContainsKey($fakeBoundParameter.Project)) { + Get-LongPath -Path $fakeBoundParameter.Project + } + else { + (Resolve-Path $fakeBoundParameter.Project).Path + } + $bldFile = if ($bldFolder -like '*.ps1') { + $bldFolder + } + else { + Join-Path $bldFolder "build.ps1" + } + $set = if (Test-Path $bldFile) { + ((([System.Management.Automation.Language.Parser]::ParseFile( + $bldFile, [ref]$null, [ref]$null + )).ParamBlock.Parameters | Where-Object { $_.Name.VariablePath.UserPath -eq 'Task' }).Attributes | Where-Object { $_.TypeName.Name -eq 'ValidateSet' }).PositionalArguments.Value + } + else { + @('Clean','Build','Import','Test','Deploy') + } + $set | Where-Object {$_ -like "$wordToComplete*"} | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } @@ -935,11 +948,11 @@ function Get-LongPath { $global:PSProfile.GitPathMap[$PSBoundParameters['Path']] } else { - $PSBoundParameters['Path'] + (Resolve-Path $PSBoundParameters['Path']).Path } } else { - $PSBoundParameters['Path'] + (Resolve-Path $PSBoundParameters['Path']).Path } } Cookbook { @@ -955,11 +968,9 @@ function Get-LongPath { } } -if ($null -ne (Get-Command Get-PSProfileArguments*)) { - Register-ArgumentCompleter -CommandName 'Get-LongPath' -ParameterName 'Path' -ScriptBlock { - param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) - Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly - } +Register-ArgumentCompleter -CommandName 'Get-LongPath' -ParameterName 'Path' -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + Get-PSProfileArguments -WordToComplete "GitPathMap.$wordToComplete" -FinalKeyOnly } New-Alias -Name path -Value 'Get-LongPath' -Scope Global -Option AllScope -Force diff --git a/PSProfile/Public/Helpers/Copy-DynamicParameters.ps1 b/PSProfile/Public/Helpers/Copy-DynamicParameters.ps1 new file mode 100644 index 0000000..d5f812c --- /dev/null +++ b/PSProfile/Public/Helpers/Copy-DynamicParameters.ps1 @@ -0,0 +1,134 @@ +function Copy-DynamicParameters { + <# + .SYNOPSIS + Copies parameters from a file or function and returns a RuntimeDefinedParameterDictionary with the parameters replicated. Used in DynamicParam blocks. + + .DESCRIPTION + Copies parameters from a file or function and returns a RuntimeDefinedParameterDictionary with the parameters replicated. Used in DynamicParam blocks. + + .PARAMETER File + The file to replicate parameters from. + + .PARAMETER Function + The function to replicate parameters from. + + .PARAMETER ExcludeParameter + The parameter or list of parameters to exclude from replicating into the returned Dictionary. + + .EXAMPLE + function Start-Build { + [CmdletBinding()] + Param () + DynamicParam { + Copy-DynamicParameters -File ".\build.ps1" + } + Process { + #Function logic + } + } + + Replicates the parameters from the build.ps1 script into the Start-Build function. + #> + [OutputType('System.Management.Automation.RuntimeDefinedParameterDictionary')] + [CmdletBinding(DefaultParameterSetName = "File")] + Param ( + [Parameter(Mandatory,Position = 0,ParameterSetName = "File")] + [String] + $File, + [Parameter(Mandatory,ParameterSetName = "Function")] + [String] + $Function, + [Parameter()] + [String[]] + $ExcludeParameter = @() + ) + Begin { + $RuntimeParamDic = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + $ast = switch ($PSCmdlet.ParameterSetName) { + File { + [System.Management.Automation.Language.Parser]::ParseFile($PSBoundParameters['File'],[ref]$null,[ref]$null) + } + Function { + [System.Management.Automation.Language.Parser]::ParseInput((Get-Command $PSBoundParameters['Function']).Definition,[ref]$null,[ref]$null) + } + } + } + Process { + foreach ($parameter in $ast.ParamBlock.Parameters | Where-Object {$_.Name.VariablePath.UserPath -notin $ExcludeParameter}) { + $AttribColl = New-Object System.Collections.ObjectModel.Collection[System.Attribute] + foreach ($paramAtt in $parameter.Attributes) { + switch ($paramAtt.TypeName.FullName) { + Parameter { + $attribute = New-Object System.Management.Automation.ParameterAttribute + foreach ($boolParam in @('Mandatory','DontShow','ValueFromPipeline','ValueFromPipelineByPropertyName','ValueFromRemainingArguments')) { + $attribute.$boolParam = ($null -ne ($paramAtt.NamedArguments | Where-Object {$_.ArgumentName -eq $boolParam -and $_.Argument.Value -eq $true})) + } + foreach ($otherParam in @('Position','HelpMessage','HelpMessageBaseName','HelpMessageResourceId','ParameterSetName')) { + if ($item = $paramAtt.NamedArguments | Where-Object {$_.ArgumentName -eq $otherParam}) { + $attribute.$otherParam = $item.Argument.Value + } + } + $AttribColl.Add($attribute) + } + Alias { + $AttribColl.Add((New-Object System.Management.Automation.AliasAttribute($paramAtt.PositionalArguments.SafeGetValue()))) + } + ValidateScript { + $AttribColl.Add((New-Object System.Management.Automation.ValidateScriptAttribute($paramAtt.PositionalArguments.ScriptBlock.GetScriptBlock()))) + } + ValidateRange { + $AttribColl.Add((New-Object System.Management.Automation.ValidateRangeAttribute($paramAtt.PositionalArguments.SafeGetValue()))) + } + ValidateCount { + $AttribColl.Add((New-Object System.Management.Automation.ValidateCountAttribute($paramAtt.PositionalArguments.SafeGetValue()))) + } + ValidatePattern { + $AttribColl.Add((New-Object System.Management.Automation.ValidatePatternAttribute($paramAtt.PositionalArguments.SafeGetValue()))) + } + ValidateSet { + $AttribColl.Add((New-Object System.Management.Automation.ValidateSetAttribute($paramAtt.PositionalArguments.SafeGetValue()))) + } + ValidateLength { + $AttribColl.Add((New-Object System.Management.Automation.ValidateLengthAttribute($paramAtt.PositionalArguments.SafeGetValue()))) + } + } + } + $RuntimeParam = New-Object System.Management.Automation.RuntimeDefinedParameter( + $parameter.Name.VariablePath.UserPath, + $parameter.StaticType, + $AttribColl + ) + $RuntimeParamDic.Add($parameter.Name.VariablePath.UserPath,$RuntimeParam) + } + } + End { + return $RuntimeParamDic + } +} + +Register-ArgumentCompleter -CommandName Copy-DynamicParameters -ParameterName ExcludeParameter -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + $set = if (-not [String]::IsNullOrEmpty($fakeBoundParameter.File)) { + ([System.Management.Automation.Language.Parser]::ParseFile( + $fakeBoundParameter.File, [ref]$null, [ref]$null + )).ParamBlock.Parameters.Name.VariablePath.UserPath + } + elseif (-not [String]::IsNullOrEmpty($fakeBoundParameter.Function)) { + ([System.Management.Automation.Language.Parser]::ParseInput( + (Get-Command $fakeBoundParameter.Function).Definition, [ref]$null, [ref]$null + )).ParamBlock.Parameters.Name.VariablePath.UserPath + } + else { + @() + } + $set | Where-Object {$_ -like "$wordToComplete*"} | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} + +Register-ArgumentCompleter -CommandName Copy-DynamicParameters -ParameterName Function -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) + (Get-Command "$wordToComplete*").Name | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} diff --git a/PSProfile/en-US/about_PSProfile_Helpers.help.txt b/PSProfile/en-US/about_PSProfile_Helpers.help.txt index 55fccf1..c1d8b10 100644 --- a/PSProfile/en-US/about_PSProfile_Helpers.help.txt +++ b/PSProfile/en-US/about_PSProfile_Helpers.help.txt @@ -15,6 +15,11 @@ LONG DESCRIPTION `Get-PSProfileArguments`. COMMANDS + * `Copy-DynamicParameters` + Copies parameters from a file or function and returns a + RuntimeDefinedParameterDictionary with the parameters + replicated. Used in DynamicParam blocks. + * `Get-LastCommandDuration` Gets the duration last command as a timestamp string for use in prompts.