diff --git a/.build/tasks/Update_Bootstrap_Script.build.ps1 b/.build/tasks/Update_Bootstrap_Script.build.ps1 index 182dc8a..981e354 100644 --- a/.build/tasks/Update_Bootstrap_Script.build.ps1 +++ b/.build/tasks/Update_Bootstrap_Script.build.ps1 @@ -39,33 +39,108 @@ param # Synopsis: Updates the bootstrap script before deploy task Update_Bootstrap_Script { - function Get-FunctionDefinition + function Get-FunctionDefinitionAst { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] - $CommandName + $CommandName, + + [Parameter()] + [System.String] + $ModuleContent ) - # Get the script content - $moduleContent = (Get-Command $CommandName).Module.Definition + if (-not $PSBoundParameters.ContainsKey('ModuleContent')) + { + # Get the command script definition. + $moduleContent = (Get-Command $CommandName).Module.Definition + } - # Parse the script into an AST - $ast = [System.Management.Automation.Language.Parser]::ParseInput($moduleContent, [ref]$null, [ref]$null) + # Parse the script into an AST. + $ast = [System.Management.Automation.Language.Parser]::ParseInput($ModuleContent, [ref] $null, [ref] $null) - # Find the Start-PSResourceGetBootstrap function definition - $functionDefinition = $ast.Find({ - param($node) + $astFilter = { + $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] -and + $args[0].Name -eq $CommandName + } - return $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and - $node.Name -in $CommandName - }, $true) + $functionDefinition = $ast.Find($astFilter, $true) return $functionDefinition } + function Get-ParameterAst + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.Language.ParamBlockAst] + $Ast, + + [Parameter(Mandatory = $true)] + [System.String] + $ParameterName + ) + + $astFilter = { + $args[0] -is [System.Management.Automation.Language.ParameterAst] ` + -and $args[0].Name.Extent.Text -eq $ParameterName + } + + $parameterAst = $Ast.Find($astFilter, $false) + + return $parameterAst + } + + function Get-ParameterValidationAst + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.Language.ParameterAst] + $Ast + ) + + $astFilter = { + $args[0] -is [System.Management.Automation.Language.AttributeAst] ` + -and $args[0].TypeName.Name -match 'Validate' + } + + $validateAttributeAst = $Ast.Find($astFilter, $false) + + return $validateAttributeAst + } + + function Remove-AstExtentContent + { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.Language.Ast] + $Ast, + + [Parameter(Mandatory = $true)] + [System.String] + $Script + ) + + $startOffset = $Ast.Extent.StartOffset + $endOffset = $Ast.Extent.EndOffset + + Write-Debug -Message "Start offset: $startOffset, End offset: $endOffset" + + $beforeAst = $Script.Substring(0, $startOffset) + $afterAst = $Script.Substring($endOffset) + + return $beforeAst + $afterAst + } + # Get the vales for task variables, see https://github.com/gaelcolas/Sampler#task-variables. . Set-SamplerTaskVariable @@ -83,10 +158,37 @@ task Update_Bootstrap_Script { $builtBootstrapScript = $builtBootstrapScript.Replace('v#.#.#', $ModuleVersion) $builtBootstrapScript = $builtBootstrapScript.Replace('yyyy-MM-dd', (Get-Date -Format 'yyyy-MM-dd')) - Write-Build -Color 'DarkGray' -Text "`tGet the function definition for the Start-PSResourceGetBootstrap function." - $functionDefinition = Get-FunctionDefinition -CommandName 'Start-PSResourceGetBootstrap' + Write-Build -Color 'DarkGray' -Text "`tParse the parameter block of the Start-PSResourceGetBootstrap command in the built module." + + # Get the function definition of the command Start-PSResourceGetBootstrap command in the built module. + $functionDefinition = Get-FunctionDefinitionAst -CommandName 'Start-PSResourceGetBootstrap' + + # Need to parse the entire module content to get the correct parameter block. + $moduleContent = $functionDefinition.Parent.Parent.Extent.Text + + # Get the parameters for the Start-PSResourceGetBootstrap command. + $parameters = $functionDefinition.Body.ParamBlock.Parameters.Name + + foreach ($parameter in $parameters) + { + Write-Build -Color 'DarkGray' -Text "`t`tGet the validation attributes for parameter $parameter." + $parameterAst = Get-ParameterAst -Ast $functionDefinition.Body.ParamBlock -ParameterName $parameter + $validationAttributeAst = Get-ParameterValidationAst -Ast $parameterAst + + if ($validationAttributeAst -and $validationAttributeAst.TypeName.Name -eq 'ValidateScript') + { + Write-Build -Color 'DarkGray' -Text "`t`t`tRemove the validation script for parameter $parameter." + $moduleContent = Remove-AstExtentContent -Ast $validationAttributeAst -Script $moduleContent - Write-Build -Color 'DarkGray' -Text "`t`tGet the parameter block for the Start-PSResourceGetBootstrap function." + # Parse the $moduleContent result to AST to get the correct parameter block, to continue parsing parameters. + $functionDefinition = Get-FunctionDefinitionAst -CommandName 'Start-PSResourceGetBootstrap' -ModuleContent $moduleContent + } + } + + # Parse the $moduleContent result to AST to get the correct parameter block. + $functionDefinition = Get-FunctionDefinitionAst -CommandName 'Start-PSResourceGetBootstrap' -ModuleContent $moduleContent + + Write-Build -Color 'DarkGray' -Text "`tWrite the parameter block of the bootstrap script." $parameterBlockString = "[CmdletBinding(DefaultParameterSetName = 'Scope')]`n" + $functionDefinition.Body.ParamBlock.Extent.Text Write-Build -Color 'DarkGray' -Text "`tSet parameters in the bootstrap script" @@ -102,6 +204,9 @@ task Update_Bootstrap_Script { Write-Build -Color 'DarkGray' -Text "`tSet localization in the bootstrap script." $builtBootstrapScript = $builtBootstrapScript.Replace('#placeholder localization', "`$script:localizedData = `n$localizationContent") + Write-Build -Color 'DarkGray' -Text "`tGet the function definition of the Start-PSResourceGetBootstrap command in the built module." + $functionDefinition = Get-FunctionDefinitionAst -CommandName 'Start-PSResourceGetBootstrap' + Write-Build -Color 'DarkGray' -Text "`tGet the comment-based help for the Start-PSResourceGetBootstrap function." $commentBasedHelp = ($functionDefinition.GetHelpContent()).GetCommentBlock() $functionDefinitionString = $functionDefinition.Extent.Text @@ -124,7 +229,7 @@ task Update_Bootstrap_Script { { Write-Build -Color 'DarkGray' -Text "`t`tGet definition for command $Command." - $functionDefinition = Get-FunctionDefinition -CommandName $command + $functionDefinition = Get-FunctionDefinitionAst -CommandName $command $functionDefinitionString += $functionDefinition.Extent.Text $functionDefinitionString += "`n" diff --git a/CHANGELOG.md b/CHANGELOG.md index d66ae9d..9ed6e4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Status badges updated. - Update integration tests for the bootstrap script. - Fix missing commands in bootstrap script. +- Fix bootstrap script parameter block. ### Changed diff --git a/source/Public/Start-PSResourceGetBootstrap.ps1 b/source/Public/Start-PSResourceGetBootstrap.ps1 index bba9a72..a3d498e 100644 --- a/source/Public/Start-PSResourceGetBootstrap.ps1 +++ b/source/Public/Start-PSResourceGetBootstrap.ps1 @@ -71,6 +71,7 @@ function Start-PSResourceGetBootstrap ( [Parameter(Mandatory = $true, ParameterSetName = 'Destination')] [ValidateScript({ + Write-Verbose -Message "Destination folder is set to '$($_)'." Test-Path -Path $_ -PathType 'Container' })] [System.String] diff --git a/tests/Integration/Bootstrap.Integration.Tests.ps1 b/tests/Integration/Bootstrap.Integration.Tests.ps1 index 951c195..60204ba 100644 --- a/tests/Integration/Bootstrap.Integration.Tests.ps1 +++ b/tests/Integration/Bootstrap.Integration.Tests.ps1 @@ -86,6 +86,38 @@ Describe 'Bootstrap Script' -Tag 'BootstrapScript' { } } + Context 'When using Invoke-Expression to initiate the bootstrap script (with defaults)' { + BeforeAll { + Remove-Item -Path "$currentUserPath/$moduleName" -Recurse -Force -ErrorAction 'SilentlyContinue' + } + + It 'Should have removed the downloaded module in previous tests' { + Get-Module $moduleName -ListAvailable | Where-Object -FilterScript { + $_.Path -match [System.Text.RegularExpressions.Regex]::Escape($currentUserPath) + } | Should -BeNullOrEmpty + } + + It 'Should bootstrap the module to the default scope CurrentUser' { + # Must create the path first, otherwise the test will fail if it does not exist. + New-Item -Path $currentUserPath -ItemType 'Directory' -Force | Out-Null + + $script = Get-Content -Path './output/bootstrap.ps1' -Raw + + # Set default parameters for the command that the script runs. + $PSDefaultParameterValues['Start-PSResourceGetBootstrap:Force'] = $true + $PSDefaultParameterValues['Start-PSResourceGetBootstrap:Verbose'] = $true + + { $script | Invoke-Expression } | Should -Not -Throw + + $PSDefaultParameterValues.Remove('Start-PSResourceGetBootstrap:Force') + $PSDefaultParameterValues.Remove('Start-PSResourceGetBootstrap:Verbose') + + Get-Module $moduleName -ListAvailable | Where-Object -FilterScript { + $_.Path -match [System.Text.RegularExpressions.Regex]::Escape($currentUserPath) + } | Should -Not -BeNullOrEmpty + } + } + Context 'When using Destination parameter set' { BeforeAll { $testFolder1 = "$TestDrive/Test1" diff --git a/tests/Integration/Start-PSResourceGetBootstrap.Integration.Tests.ps1 b/tests/Integration/Start-PSResourceGetBootstrap.Integration.Tests.ps1 index f43fd45..d9a218e 100644 --- a/tests/Integration/Start-PSResourceGetBootstrap.Integration.Tests.ps1 +++ b/tests/Integration/Start-PSResourceGetBootstrap.Integration.Tests.ps1 @@ -40,13 +40,8 @@ Describe 'Start-PSResourceGetBootstrap' { ---> System.IO.IOException: Permission denied #> It 'Should bootstrap the module to the specified scope AllUsers' -Skip:$IsLinux { - $ErrorView = 'DetailedView' - { Start-PSResourceGetBootstrap -Scope 'AllUsers' -Force -Verbose } | Should -Not -Throw - Write-Verbose -Message ('Error count: {0}' -f $Error.Count) -Verbose - Write-Verbose -Message ($Error | Out-String) -Verbose - $allUsersPath = Get-PSModulePath -Scope 'AllUsers' Get-Module $moduleName -ListAvailable | Where-Object -FilterScript {