From 5b49ac5989526e257dc70885bce7c6271e6afb7f Mon Sep 17 00:00:00 2001 From: DexterPOSH Date: Mon, 19 Jun 2017 15:53:28 +0530 Subject: [PATCH 1/6] [WIP] Add function to sanitize splat hash passed to Pester on remote nodes --- PSRemotely/private/ConfigurationData.ps1 | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/PSRemotely/private/ConfigurationData.ps1 b/PSRemotely/private/ConfigurationData.ps1 index 2218d29..abb804b 100644 --- a/PSRemotely/private/ConfigurationData.ps1 +++ b/PSRemotely/private/ConfigurationData.ps1 @@ -129,3 +129,27 @@ function ConvertPSObjectToHashtable } } } + + +Function Sanitize-PesterSplatHash { + [CmdletBinding()] + param( + # Pass the Pester splat hash to be modified + [Parameter(Mandatory=$true)] + [ValidateNotNullOrEmpty()] + [HashTable]$SplatHash + ) + + # Filter our the conflicting keys from the PesterSplathash + # Keys to remove - Script, PassThru, Quiet, OutputFormat, OutPutFile + $ExcludeKeys = @('Script','PassThru','Quiet','OutputFormat','OutputFile') + $CloneHash = $SplatHash.Clone() + Foreach ($Key in $CloneHash.Keys) { + # process each key present in the splat hash + if ($ExcludeKeys -contains $Key) { + # remove this key and value + Write-Warning -Message "Key $Key found in the SplatHash. Removing it" + $SplatHash.Remove($Key) + } + } +} From cf0c99b818426ad46338e7a00434e8a3fcb2b108 Mon Sep 17 00:00:00 2001 From: DexterPOSH Date: Mon, 19 Jun 2017 15:54:25 +0530 Subject: [PATCH 2/6] [WIP] Adds unit tests for the Sanitize-PesterSplathash func --- Tests/Unit/ConfigurationData.Tests.ps1 | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Tests/Unit/ConfigurationData.Tests.ps1 b/Tests/Unit/ConfigurationData.Tests.ps1 index 78d8039..487d3b9 100644 --- a/Tests/Unit/ConfigurationData.Tests.ps1 +++ b/Tests/Unit/ConfigurationData.Tests.ps1 @@ -188,4 +188,47 @@ InModuleScope -ModuleName $ENV:BHProjectName { } } } + + Describe "Sanitize-PesterSplatHash" -Tag UnitTest { + + Context "Sanitizing the Splat hash" { + # Arrange + $SplatHash = @{ + Script=".\test.ps1"; + Tag = @('Dev','WebServers'); + PassThru=$False; + Quiet=$True; + OutputFormat="XML"; + OutputFile="dummy.xml"; + TestName='Dummy test'; + } + $OrigSplatHash = $SplatHash.Clone() + $ExcludedKeys = @('Script','PassThru','Quiet','OutputFormat','OutputFile') + + # Act + $Result = Sanitize-PesterSplatHash -SplatHash $SplatHash + + # Assert + + foreach ($key in $OrigSplatHash.keys) { + + if ($ExcludedKeys -contains $key) { + It "Should NOT have the reserved key -> $reservedKey in the Splat hash" { + $SplatHash.ContainsKey($reservedKey) | Should Be $False + } + } + else { + It "Should retain the non-reserved key -> $key in the Splat hash" { + $SplatHash.ContainsKey($reservedKey) | Should Be $True + } + } + + } + + It "Should not return anything" { + $Result | Should Be $Null + } + + } + } } \ No newline at end of file From ef4db359fe72680eefa5ce4491bd90eed4053fca Mon Sep 17 00:00:00 2001 From: DexterPOSH Date: Mon, 19 Jun 2017 15:56:15 +0530 Subject: [PATCH 3/6] [WIP] Adds parameter -PesterSplatHash - work done for JSON parameterset - TODO the bootstrap parameterset --- PSRemotely/public/Invoke-PSRemotely.ps1 | 48 +++++++++++++++++++++---- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/PSRemotely/public/Invoke-PSRemotely.ps1 b/PSRemotely/public/Invoke-PSRemotely.ps1 index df7f7b2..624c8e9 100644 --- a/PSRemotely/public/Invoke-PSRemotely.ps1 +++ b/PSRemotely/public/Invoke-PSRemotely.ps1 @@ -55,7 +55,19 @@ Function Invoke-PSRemotely { ] } - + .PARAMETER PesterSplatHash + Pass a hash table which is splatted to Invoke-Pester's execution on the PSRemotely node. + This let's you pass arguments to Invoke-Pester such as -Tag, -ExcludeTag, -Strict etc. + Note - PSRemotely automatically supplies the below arguments to Invoke-Pester. So if these are + specified then it will be ignored. + + Script = + PassThru = $True; + Quiet = $True; + OutputFormat = 'NunitXML'; + OutputFile = .xml; + + .EXAMPLE PS> Invoke-PSRemotely @@ -101,7 +113,7 @@ Function Invoke-PSRemotely { #> [CmdletBinding(DefaultParameterSetName='BootStrap',SupportsShouldProcess=$True)] param( - [Parameter(Position=-0, + [Parameter(Position=0, Mandatory=$False, ParameterSetName='BootStrap', ValueFromPipeline=$true)] @@ -110,7 +122,12 @@ Function Invoke-PSRemotely { [Parameter(Position=0, Mandatory=$true, ParameterSetName='JSON')] - [String]$JSONInput + [String]$JSONInput, + + [Parameter(Position=1, + Mandatory=$False)] + [Alias('SplatHash')] + [HashTable]$PesterSplatHash ) BEGIN { @@ -137,14 +154,19 @@ Function Invoke-PSRemotely { Select-Object -ExpandProperty Value | Select-Object -ExpandProperty Session + # Check if the Pester splat hash was passed + if ($PesterSplatHash) { + Sanitize-PesterSplatHash -SplatHash $PesterSplatHash + } # build the splat hashtable $invokeTestParams = @{ Session = $session; - ArgumentList = $JSONInput, $Object.NodeName #@(,$Object.Tests.Name); + ArgumentList = $JSONInput, $Object.NodeName, $PesterSplatHash #@(,$Object.Tests.Name); ScriptBlock = { param( [String]$JSONString, - [String]$NodeName + [String]$NodeName, + [HashTable]$PesterSplatHash ) $Object = ConvertFrom-Json -InputObject $JSONString foreach ($test in @($Object.Tests.Name)) { @@ -166,11 +188,22 @@ Function Invoke-PSRemotely { $testFile = "$($Global:PSRemotely.PSRemotelyNodePath)\$testFileName" $outPutFile = "{0}\{1}.{2}.xml" -f $PSRemotely.PSRemotelyNodePath, $nodeName, $test + $invokePesterParams = @{ + PassThru = $True; + Quiet = $True; + OutputFormat = 'NunitXML'; + OutputFile = $OutputFile + } + + if ($PesterSplatHash) { + $invokePesterParams += $PesterSplatHash + } + if ($Node) { - Invoke-Pester -Script @{Path=$($TestFile); Parameters=@{Node=$Node}} -PassThru -Quiet -OutputFormat NUnitXML -OutputFile $outPutFile + Invoke-Pester -Script @{Path=$($TestFile); Parameters=@{Node=$Node}} @invokePesterParams } else { - Invoke-Pester -Script $testFile -PassThru -Quiet -OutputFormat NUnitXML -OutputFile $outPutFile + Invoke-Pester -Script $testFile @invokePesterParams } } }; # end scriptBlock @@ -209,6 +242,7 @@ Function Invoke-PSRemotely { try { do{ Write-VerboseLog -Message "Invoking test script -> $($testscript.path)" + # TODO pass the PesterSplatHash if specified in the command line & $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters } until ($true) } From d64db608190475464ea7f4ab94b82d7c1013e693 Mon Sep 17 00:00:00 2001 From: DexterPOSH Date: Mon, 19 Jun 2017 16:03:36 +0530 Subject: [PATCH 4/6] [WIP] Fixes typo in the tests, replace $reservedKey with $key --- Tests/Unit/ConfigurationData.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Unit/ConfigurationData.Tests.ps1 b/Tests/Unit/ConfigurationData.Tests.ps1 index 487d3b9..ddb8e14 100644 --- a/Tests/Unit/ConfigurationData.Tests.ps1 +++ b/Tests/Unit/ConfigurationData.Tests.ps1 @@ -213,13 +213,13 @@ InModuleScope -ModuleName $ENV:BHProjectName { foreach ($key in $OrigSplatHash.keys) { if ($ExcludedKeys -contains $key) { - It "Should NOT have the reserved key -> $reservedKey in the Splat hash" { - $SplatHash.ContainsKey($reservedKey) | Should Be $False + It "Should NOT have the reserved key -> $key in the Splat hash" { + $SplatHash.ContainsKey($key) | Should Be $False } } else { It "Should retain the non-reserved key -> $key in the Splat hash" { - $SplatHash.ContainsKey($reservedKey) | Should Be $True + $SplatHash.ContainsKey($key) | Should Be $True } } From c7c4cdb7f6f854a8beb6e6a20c1f020394533e24 Mon Sep 17 00:00:00 2001 From: DexterPOSH Date: Tue, 11 Jul 2017 14:20:27 +0530 Subject: [PATCH 5/6] Refactors the build steps --- InvokeBuild.ps1 => Build/InvokeBuild.ps1 | 0 Build/Start-Build.ps1 | 16 ++++++ Build/build.requirements.psd1 | 14 +++++ Build/helpers/Install-PSDepend.ps1 | 50 +++++++++++++++++ .../psremotely.psdeploy.ps1 | 0 appveyor.yml | 3 +- build.ps1 | 54 ------------------- 7 files changed, 82 insertions(+), 55 deletions(-) rename InvokeBuild.ps1 => Build/InvokeBuild.ps1 (100%) create mode 100644 Build/Start-Build.ps1 create mode 100644 Build/build.requirements.psd1 create mode 100644 Build/helpers/Install-PSDepend.ps1 rename psremotely.psdeploy.ps1 => Build/psremotely.psdeploy.ps1 (100%) delete mode 100644 build.ps1 diff --git a/InvokeBuild.ps1 b/Build/InvokeBuild.ps1 similarity index 100% rename from InvokeBuild.ps1 rename to Build/InvokeBuild.ps1 diff --git a/Build/Start-Build.ps1 b/Build/Start-Build.ps1 new file mode 100644 index 0000000..e3b36a4 --- /dev/null +++ b/Build/Start-Build.ps1 @@ -0,0 +1,16 @@ +param( + $Task = 'Build' # build is the default task, add support to deploy later +) + +# dependencies +Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null +if(-not (Get-Module -ListAvailable PSDepend)) +{ + & (Resolve-Path "$PSScriptRoot\helpers\Install-PSDepend.ps1") +} +Import-Module PSDepend +$null = Invoke-PSDepend -Path "$PSScriptRoot\build.requirements.psd1" -Install -Import -Force + +Set-BuildEnvironment -Force + +Invoke-Build -File $PSScriptRoot\InvokeBuild.ps1 -Task $Task \ No newline at end of file diff --git a/Build/build.requirements.psd1 b/Build/build.requirements.psd1 new file mode 100644 index 0000000..2b9fc1d --- /dev/null +++ b/Build/build.requirements.psd1 @@ -0,0 +1,14 @@ +@{ + # Some defaults for all dependencies + PSDependOptions = @{ + Target = '$ENV:USERPROFILE\Documents\WindowsPowerShell\Modules' + AddToPath = $True + } + + # Grab some modules without depending on PowerShellGet + 'InvokeBuild' = @{ DependencyType = 'PSGalleryNuget' } + 'PSDeploy' = @{ DependencyType = 'PSGalleryNuget' } + 'BuildHelpers' = @{ DependencyType = 'PSGalleryNuget' } + 'Pester' = @{ DependencyType = 'PSGalleryNuget' } + 'PSScriptAnalyzer' = @{ DependencyType = 'PSGalleryNuget' } +} \ No newline at end of file diff --git a/Build/helpers/Install-PSDepend.ps1 b/Build/helpers/Install-PSDepend.ps1 new file mode 100644 index 0000000..66dad28 --- /dev/null +++ b/Build/helpers/Install-PSDepend.ps1 @@ -0,0 +1,50 @@ + <# + .SYNOPSIS + Bootstrap PSDepend + + .DESCRIPTION + Bootstrap PSDepend + + Why? No reliance on PowerShellGallery + + * Downloads nuget to your ~\ home directory + * Creates $Path (and full path to it) + * Downloads module to $Path\PSDepend + * Moves nuget.exe to $Path\PSDepend (skips nuget bootstrap on initial PSDepend import) + + .PARAMETER Path + Module path to install PSDepend + + Defaults to Profile\Documents\WindowsPowerShell\Modules + + .EXAMPLE + .\Install-PSDepend.ps1 -Path C:\Modules + + # Installs to C:\Modules\PSDepend + #> + [cmdletbinding()] + param( + [string]$Path = $( Join-Path ([Environment]::GetFolderPath('MyDocuments')) 'WindowsPowerShell\Modules') + ) + $ExistingProgressPreference = "$ProgressPreference" + $ProgressPreference = 'SilentlyContinue' + try { + # Bootstrap nuget if we don't have it + if(-not ($NugetPath = (Get-Command 'nuget.exe' -ErrorAction SilentlyContinue).Path)) { + $NugetPath = Join-Path $ENV:USERPROFILE nuget.exe + if(-not (Test-Path $NugetPath)) { + Invoke-WebRequest -uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile $NugetPath + } + } + + # Bootstrap PSDepend, re-use nuget.exe for the module + if($path) { $null = mkdir $path -Force } + $NugetParams = 'install', 'PSDepend', '-Source', 'https://www.powershellgallery.com/api/v2/', + '-ExcludeVersion', '-NonInteractive', '-OutputDirectory', $Path + & $NugetPath @NugetParams + Move-Item -Path $NugetPath -Destination "$(Join-Path $Path PSDepend)\nuget.exe" -Force + } + finally { + $ProgressPreference = $ExistingProgressPreference + } + diff --git a/psremotely.psdeploy.ps1 b/Build/psremotely.psdeploy.ps1 similarity index 100% rename from psremotely.psdeploy.ps1 rename to Build/psremotely.psdeploy.ps1 diff --git a/appveyor.yml b/appveyor.yml index 4bba822..8e95bb2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,7 +23,8 @@ build: false #Kick off the CI/CD pipeline test_script: - - ps: . .\build.ps1 + - ps: . .\build\Start-Build.ps1 -Task Build + # Block the RDP for debugging #on_finish: diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index e07592b..0000000 --- a/build.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -function Resolve-Module -{ - [Cmdletbinding()] - param - ( - [Parameter(Mandatory)] - [string[]]$Name - ) - Process - { - foreach ($ModuleName in $Name) - { - $Module = Get-Module -Name $ModuleName -ListAvailable - Write-Verbose -Message "Resolving Module $($ModuleName)" - - if ($Module) - { - $Version = $Module | Measure-Object -Property Version -Maximum | Select-Object -ExpandProperty Maximum - $GalleryVersion = Find-Module -Name $ModuleName -Repository PSGallery | Measure-Object -Property Version -Maximum | Select-Object -ExpandProperty Maximum - - if ($Version -lt $GalleryVersion) - { - if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') { Set-PSRepository -Name PSGallery -InstallationPolicy Trusted } - - Write-Verbose -Message "$($ModuleName) Installed Version [$($Version.tostring())] is outdated. Installing Gallery Version [$($GalleryVersion.tostring())]" - Install-Module -Name $ModuleName -Force - Import-Module -Name $ModuleName -Force -RequiredVersion $GalleryVersion - } - else - { - Write-Verbose -Message "Module Installed, Importing $($ModuleName)" - Import-Module -Name $ModuleName -Force -RequiredVersion $Version - } - } - else - { - Write-Verbose -Message "$($ModuleName) Missing, installing Module" - Install-Module -Name $ModuleName -Force - Import-Module -Name $ModuleName -Force -RequiredVersion $Version - } - } - } -} - -# Grab nuget bits, install modules, set build variables, start build. -Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null - -Resolve-Module InvokeBuild, Pester, BuildHelpers, PSScriptAnalyzer, PSDeploy -Verbose - - -Set-BuildEnvironment - -Invoke-Build -File .\InvokeBuild.ps1 -# exit ( [int]( -not $psake.build_success ) ) From 47c5f8c7bccc6b70ff97662620e1850a509cb0c0 Mon Sep 17 00:00:00 2001 From: DexterPOSH Date: Tue, 11 Jul 2017 14:34:43 +0530 Subject: [PATCH 6/6] Sets the project root in the Build task --- Build/InvokeBuild.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Build/InvokeBuild.ps1 b/Build/InvokeBuild.ps1 index 28fa48e..6d863f7 100644 --- a/Build/InvokeBuild.ps1 +++ b/Build/InvokeBuild.ps1 @@ -80,6 +80,7 @@ task Test Init, { task Build Test, { $lines + Set-Location $ProjectRoot # Load the module, read the exported functions, update the psd1 FunctionsToExport Set-ModuleFunctions