diff --git a/.gitignore b/.gitignore
index 23681f22a..4f3039eee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
# Cloned modules
DSCResource.Tests
-PowerStig.Tests
# Editors
.vscode/
.vs/
+
+# local preference scripts/utilities
+.local/
diff --git a/Tests/Integration/.tests.header.ps1 b/Tests/Integration/.tests.header.ps1
index 4a72f4659..0caa29442 100644
--- a/Tests/Integration/.tests.header.ps1
+++ b/Tests/Integration/.tests.header.ps1
@@ -3,14 +3,6 @@ $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$script:moduleName = 'PowerStig.Convert'
$script:modulePath = "$($script:moduleRoot)\$($script:moduleName).psm1"
-if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests'))) -or `
- (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests\TestHelper.psm1'))) )
-{
- & git @('clone', 'https://github.com/Microsoft/PowerStig.Tests', (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests'))
-}
-
-Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (
- Join-Path -Path 'PowerStig.Tests' -ChildPath 'TestHelper.psm1')
- ) -Force
-
+$helperModulePath = Join-Path -Path $script:moduleRoot -ChildPath 'Tools\TestHelper\TestHelper.psm1'
+Import-Module $helperModulePath -Force
Import-Module $modulePath -Force
diff --git a/Tests/Integration/DSCResources/.tests.header.ps1 b/Tests/Integration/DSCResources/.tests.header.ps1
index 5475a5d34..bc24e5f64 100644
--- a/Tests/Integration/DSCResources/.tests.header.ps1
+++ b/Tests/Integration/DSCResources/.tests.header.ps1
@@ -9,7 +9,7 @@ if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCR
& git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
}
-Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tests\helper.psm1' ) -Force
+Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'Tools\TestHelper\TestHelper.psm1' ) -Force
Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
$TestEnvironment = Initialize-TestEnvironment `
-DSCModuleName $script:DSCModuleName `
diff --git a/Tests/Integration/PowerStig.Integration.tests.ps1 b/Tests/Integration/PowerStig.Integration.tests.ps1
index b1d8d8153..597c1e064 100644
--- a/Tests/Integration/PowerStig.Integration.tests.ps1
+++ b/Tests/Integration/PowerStig.Integration.tests.ps1
@@ -4,13 +4,7 @@ $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$script:moduleName = 'PowerStig'
$script:modulePath = "$($script:moduleRoot)\$($script:moduleName).psd1"
-if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests'))) -or `
- (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests\TestHelper.psm1'))) )
-{
- & git @('clone', 'https://github.com/Microsoft/PowerStig.Tests', (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests'))
-}
-
-Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'PowerStig.Tests' -ChildPath 'TestHelper.psm1')) -Force
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'Tools\TestHelper\TestHelper.psm1') -Force
Import-Module $modulePath -Force
#endregion
diff --git a/Tests/Unit/.tests.header.ps1 b/Tests/Unit/.tests.header.ps1
new file mode 100644
index 000000000..39fdedd43
--- /dev/null
+++ b/Tests/Unit/.tests.header.ps1
@@ -0,0 +1,13 @@
+# Unit Test Header
+$script:moduleSection = Split-Path -Path (Split-Path -Path (Get-PSCallStack)[1].ScriptName -Parent ) -Leaf
+$script:projectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+$script:toolsRoot = Join-Path -Path $script:projectRoot -ChildPath 'Tools'
+$script:moduleName = (Get-PSCallStack)[1].Command -replace '\.tests\.ps1', ''
+
+$script:modulePath = "$($script:projectRoot)\$($script:moduleSection)\$script:moduleName\$($script:moduleName).psm1"
+
+$helperModulePath = Join-Path -Path $script:toolsRoot -ChildPath (
+ Join-Path -Path 'TestHelper' -ChildPath 'TestHelper.psm1')
+
+Import-Module -Name $helperModulePath -Force -Global
+Import-Module -Name $script:modulePath -Force
diff --git a/Tests/Unit/DSCResources/.tests.header.ps1 b/Tests/Unit/DSCResources/.tests.header.ps1
index d630130b1..13ed16ecb 100644
--- a/Tests/Unit/DSCResources/.tests.header.ps1
+++ b/Tests/Unit/DSCResources/.tests.header.ps1
@@ -1,3 +1,3 @@
# Unit Test Header
$script:moduleRoot = Split-Path -Parent (Split-Path -Parent ( Split-Path -Parent $PSScriptRoot ) )
-Import-Module (Join-Path -Path $moduleRoot -ChildPath 'Tests\helper.psm1') -Force
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'Tools\TestHelper\TestHelper.psm1') -Force
diff --git a/Tests/Unit/DSCResources/Composite.tests.ps1 b/Tests/Unit/DSCResources/Composite.tests.ps1
index f3981368e..61cb1ec96 100644
--- a/Tests/Unit/DSCResources/Composite.tests.ps1
+++ b/Tests/Unit/DSCResources/Composite.tests.ps1
@@ -2,7 +2,7 @@ $script:DSCModuleName = 'PowerStig'
# Header
$script:moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
-Import-Module (Join-Path -Path $moduleRoot -ChildPath 'Tests\helper.psm1') -Force
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'Tools\TestHelper\TestHelper.psm1') -Force
Describe 'Common Tests - Configuration Module Requirements' {
diff --git a/Tests/Unit/Module/.tests.header.ps1 b/Tests/Unit/Module/.tests.header.ps1
index 09ba21104..b4b735183 100644
--- a/Tests/Unit/Module/.tests.header.ps1
+++ b/Tests/Unit/Module/.tests.header.ps1
@@ -3,15 +3,7 @@ $script:moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent
$script:moduleName = (Get-PSCallStack)[1].Command -replace '\.tests\.ps1', ''
$script:modulePath = "$($script:moduleRoot)$(($PSScriptRoot -split 'Unit')[1])\$script:moduleName\$($script:moduleName).psm1"
-if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests'))) -or `
- (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests\TestHelper.psm1'))) )
-{
- & git @('clone', 'https://github.com/Microsoft/PowerStig.Tests', (Join-Path -Path $script:moduleRoot -ChildPath 'PowerStig.Tests'))
-}
-
-Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (
- Join-Path -Path 'PowerStig.Tests' -ChildPath 'TestHelper.psm1')
- ) -Force -Global
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'Tools\TestHelper\TestHelper.psm1') -Force -Global
<#
Several classes check for duplicate rules against a global variable stigSettings.
diff --git a/Tests/Unit/Tools/Release.Tests.ps1 b/Tests/Unit/Tools/Release.Tests.ps1
new file mode 100644
index 000000000..7a2470581
--- /dev/null
+++ b/Tests/Unit/Tools/Release.Tests.ps1
@@ -0,0 +1,746 @@
+if ($PSVersionTable.PSEdition -ne 'Core')
+{
+ return
+}
+$unitTestRoot = Split-Path -Path $PSScriptRoot -Parent
+. "$unitTestRoot\.tests.header.ps1"
+
+try
+{
+ InModuleScope $script:ModuleName {
+
+ Describe 'Test-ModuleVersion' -Tag 'tools' {
+
+ $manifestPath = "$TestDrive\testManifest.psd1"
+ New-ModuleManifest -Path $manifestPath -ModuleVersion '1.0.0.0'
+ $moduleVersion = '1.1.0.0'
+
+ Mock -CommandName Get-GitBranch -MockWith { 'dev' }
+ Mock -CommandName Set-GitBranch -MockWith { } -ParameterFilter {$Branch -eq 'master'} -Verifiable
+
+ It "Should change to the master branch if not already in it" {
+ $null = Test-ModuleVersion -ModuleVersion $moduleVersion -ManifestPath $manifestPath
+ Assert-VerifiableMock
+ }
+ It "Should return $true when the supplied module version is higher than the master branch manifest" {
+ Test-ModuleVersion -ModuleVersion $moduleVersion -ManifestPath $manifestPath | Should Be $true
+ }
+ It "Should return $false when the supplied module version is NOT higher than the master branch manifest" {
+ $moduleVersion = '1.0.0.0'
+ Test-ModuleVersion -ModuleVersion $moduleVersion -ManifestPath $manifestPath | Should Be $false
+ }
+ It "Should import the manifest from `$PWD if a ManifestPath is not provided" {
+ $mockGetChildItem = @{FullName = $manifestPath}
+ Mock -CommandName Get-ChildItem -MockWith { return $mockGetChildItem } -Verifiable
+ $null = Test-ModuleVersion -ModuleVersion $moduleVersion
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Get-PowerStigRepository' -Tag 'tools' {
+ $mockGetCommand = @{
+ 'CommandType' = 'Application'
+ 'Name' = 'git.exe'
+ 'Version' = '2.15.1.2'
+ 'Source' = 'C:\Program Files\Git\cmd\git.exe'
+ }
+
+ It "Should Throw if Git is not installed" {
+ Mock -CommandName Get-Command -MockWith { return $null }
+ { Get-PowerStigRepository } | Should Throw
+ }
+
+ $repositoryList = @(
+ @{
+ Name = 'https://github.com/Microsoft/PowerStig.git'
+ Result = @{
+ 'name' = 'PowerStig'
+ 'html_url' = 'https://github.com/Microsoft/PowerStig'
+ 'api_url' = 'https://api.github.com/repos/Microsoft/PowerStig'
+ }
+ },
+ @{
+ Name = 'https://github.com/Microsoft/PowerStigDsc.git'
+ Result = @{
+ 'name' = 'PowerStigDsc'
+ 'html_url' = 'https://github.com/Microsoft/PowerStigDsc'
+ 'api_url' = 'https://api.github.com/repos/Microsoft/PowerStigDsc'
+ }
+ }
+ )
+
+ foreach ($repository in $repositoryList)
+ {
+ Mock -CommandName Get-Command -MockWith { return $mockGetCommand }
+ Mock -CommandName Invoke-Command -MockWith { return $repository.Name }
+ Mock -CommandName Invoke-Git -MockWith { $repository.Name}
+ $PowerStigRepository = Get-PowerStigRepository
+
+ It "Should return the correct name" {
+ $PowerStigRepository.name | Should Be $repository.Result.name
+ }
+ It "Should return the correct html URL" {
+ $PowerStigRepository.html_url | Should Be $repository.Result.html_url
+ }
+ It "Should return the correct Api URL" {
+ $PowerStigRepository.api_url | Should Be $repository.Result.api_url
+ }
+ }
+
+ It "Should throw an error if a non PowerStig project is suplied" {
+ $repository = 'https://github.com/Microsoft/NotPowerStig.git'
+ Mock -CommandName Invoke-Git -MockWith { return $repository }
+ { Test-PowerStigRepository } | Should throw
+ }
+ }
+
+ Describe 'Set-GitBranch' -Tag 'tools' {
+ $branch = 'dev'
+
+ Context 'Already in dev Branch' {
+ Mock -CommandName Get-GitBranch -MockWith { return $branch }
+ Mock -CommandName Invoke-Git -MockWith { return } -Verifiable
+ It "Should not invoke 'git checkout $branch" {
+ Set-GitBranch -Branch $branch
+ Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Command -eq "checkout $Branch" } -Times 0
+ }
+ }
+
+ Context 'Not already in dev Branch' {
+ Mock -CommandName Get-GitBranch -MockWith { return 'master' }
+ Mock -CommandName Invoke-Git -MockWith { return } `
+ -ParameterFilter { $Command -eq "checkout $Branch" } -Verifiable
+ It "Should invoke 'git checkout $branch" {
+ Set-GitBranch -Branch $branch
+ Assert-MockCalled -CommandName Invoke-Git -Times 1
+ }
+ }
+
+ Context 'SkipPull' {
+ Mock -CommandName Get-GitBranch -MockWith { return $branch }
+ Mock -CommandName Invoke-Command -MockWith { return } -Verifiable
+ It "Should not invoke a pull when SkipPull is used" {
+ Set-GitBranch -Branch $branch -SkipPull
+ Assert-MockCalled -CommandName Invoke-Command -ParameterFilter { $ScriptBlock.ToString() -match "git pull" } -Times 0
+ }
+ }
+ }
+
+ Describe 'New-GitReleaseBranch' -Tag 'tools' {
+ $branchName = '1.2.3.4-release'
+
+ Mock -CommandName Get-GitBranch -MockWith { return 'master' } -Verifiable
+ Mock -CommandName Set-GitBranch -MockWith { } -ParameterFilter { $Branch -eq 'dev'} -Verifiable
+
+ Context 'Branch exists' {
+ Mock -CommandName Invoke-Git -MockWith {"[$branchName] Last Commit message"} `
+ -ParameterFilter { $Command -eq "show-branch $branchName"} -Verifiable
+ Mock -CommandName Invoke-Git -MockWith {$null} `
+ -ParameterFilter { $Command -eq "checkout $branchName"} -Verifiable
+
+ It "Should switch to the $branchName branch" {
+ New-GitReleaseBranch -BranchName $branchName
+ Assert-VerifiableMock
+ }
+ }
+
+ Context 'Branch does not exist' {
+ Mock -CommandName Invoke-Git -MockWith {"fatal: bad sha1 reference $branchName"} `
+ -ParameterFilter { $Command -eq "show-branch $branchName" } -Verifiable
+ Mock -CommandName Invoke-Git -MockWith {$null} `
+ -ParameterFilter { $Command -eq "checkout -b $branchName dev" } -Verifiable
+
+ It "Should create the $branchName branch" {
+ New-GitReleaseBranch -BranchName $branchName
+ Assert-VerifiableMock
+ }
+ }
+ }
+
+ Describe 'Remove-GitReleaseBranch' -Tag 'tools' {
+ $branchName = '1.2.3.4-release'
+ Mock -CommandName Invoke-Git -MockWith {} `
+ -ParameterFilter {$Command -eq "merge $branchName"} -Verifiable
+ Mock -CommandName Invoke-Git -MockWith {} `
+ -ParameterFilter {$Command -eq "push"} -Verifiable
+ Mock -CommandName Invoke-Git -MockWith {} `
+ -ParameterFilter {$Command -eq "branch -d $branchName"} -Verifiable
+ Mock -CommandName Invoke-Git -MockWith {} `
+ -ParameterFilter {$Command -eq "push origin -d $branchName"} -Verifiable
+ Mock -CommandName Invoke-Git -MockWith {} `
+ -ParameterFilter {$Command -eq "remote prune origin"} -Verifiable
+ It "Should remove $branchName from the local and remote repo" {
+ Remove-GitReleaseBranch -BranchName $branchName
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Push-GitBranch' -Tag 'tools' {
+ $branchName = '1.2.3.4-release'
+
+ Mock -CommandName Invoke-Git -MockWith { return } -ParameterFilter { $Command -eq "push -u origin $BranchName"} -Verifiable
+
+ It "Should NOT Commit or push $branchName if there are no changes" {
+ Mock -CommandName Invoke-Git -MockWith { 'Your branch is up to date' } `
+ -ParameterFilter { $Command -eq "commit -a -m $CommitMessage"} -Verifiable
+ Push-GitBranch -Name $branchName -CommitMessage 'Commit Message'
+ Assert-MockCalled -CommandName Invoke-Git -Times 1
+ }
+
+ It "Should Commit or push $branchName if there are no changes" {
+ Mock -CommandName Invoke-Git -MockWith { 'Last commit message' } `
+ -ParameterFilter { $Command -eq "commit -a -m $CommitMessage"} -Verifiable
+ Push-GitBranch -Name $branchName -CommitMessage 'Commit Message'
+ Assert-MockCalled -CommandName Invoke-Git -Times 2
+ }
+ }
+
+ Describe 'Get-UnreleasedNotes' -Tag 'tools' {
+ $sampleReadme = New-Object System.Text.StringBuilder
+ $null = $sampleReadme.AppendLine('')
+ $null = $sampleReadme.AppendLine('### Unreleased')
+ $null = $sampleReadme.AppendLine('')
+ $null = $sampleReadme.AppendLine('Update 1')
+ $null = $sampleReadme.AppendLine('Update 2')
+ $null = $sampleReadme.AppendLine('')
+ $null = $sampleReadme.AppendLine('### 1.0.0.0')
+ $null = $sampleReadme.AppendLine('')
+
+ Mock -CommandName Get-ChildItem -MockWith { return @{'FullName' = 'empty\path'} }
+ Mock -CommandName Get-Content -MockWith { return $sampleReadme.ToString().Split("`n") }
+
+ It "Should return the unreleased notes trimmed of extra lines" {
+ Get-UnreleasedNotes | Should Be ("Update 1`r`r`nUpdate 2" | Out-String).Trim()
+ }
+ }
+
+ Describe 'Update-Readme' -Tag 'tools' {
+ $moduleVersion = '1.2.3.4'
+ $sampleReadme = New-Object System.Text.StringBuilder
+ $null = $sampleReadme.AppendLine('')
+ $contributors = $null = $sampleReadme.AppendLine('### Contributors').Length
+ $null = $sampleReadme.AppendLine('')
+ $unreleased = $sampleReadme.AppendLine('### Unreleased').Length
+ $null = $sampleReadme.AppendLine('')
+ $null = $sampleReadme.AppendLine('Update 1')
+ $null = $sampleReadme.AppendLine('Update 2')
+ $null = $sampleReadme.AppendLine('')
+ $null = $sampleReadme.AppendLine('### 1.0.0.0')
+ $null = $sampleReadme.AppendLine('')
+
+ $sampleReadmePath = "$TestDrive\readme.md"
+ Mock -CommandName Get-ChildItem -MockWith { @{ FullName = $sampleReadmePath } }
+ Mock -CommandName Get-Content -MockWith {$sampleReadme.ToString()}
+
+ Context 'ReleaseNotes' {
+
+ It "Should correctly add the module version to the readme" {
+ Update-Readme -ModuleVersion $moduleVersion
+ $null = $sampleReadme.Insert($unreleased, "`n### $moduleVersion`n")
+ $readmeContent = Get-Content -Path $sampleReadmePath
+ $readmeContent | Should Be $sampleReadme.ToString()
+ }
+ }
+
+ Context 'Contributors' {
+ $contributorList = @(
+ @{
+ login = 'tester'
+ Name = 'Test'
+ }
+ )
+ Mock -CommandName Get-ProjectContributorList -MockWith { $contributorList }
+ It "Should correctly add the contributors to the readme" {
+ Update-Readme -Repository @{}
+ $null = $sampleReadme.Insert($contributors,
+ "`n* [@$($contributorList.login)](https://github.com/$($contributorList.login)) ($($contributorList.Name))`n")
+ $readmeContent = Get-Content -Path $sampleReadmePath
+ $readmeContent | Should Be $sampleReadme.ToString()
+ }
+ }
+ }
+
+ Describe 'Update-Manifest' -Tag 'tools' {
+ $moduleVersion = '1.2.3.4'
+ $releaseNotes = 'Added super cool feature'
+ $manifestPath = "$TestDrive\testManifest.psd1"
+ New-ModuleManifest -Path $manifestPath -ModuleVersion '1.0.0.0' -ReleaseNotes 'test'
+ Update-Manifest -ModuleVersion $moduleVersion -ReleaseNotes $releaseNotes -ManifestPath $manifestPath
+ $manifest = Import-PowerShellDataFile -Path $manifestPath
+
+ It "Should update the manifest version number" {
+ $manifest.ModuleVersion | Should Be $moduleVersion
+ }
+ It "Should update the manifest release notes" {
+ $manifest.PrivateData.PSData.ReleaseNotes | Should Be $releaseNotes
+ }
+ }
+
+ Describe 'Update-AppVeyorConfiguration' -Tag 'tools' {
+ $moduleVersion = '1.2.3.4'
+ $versionString = 'version: 0.2.0.{build}'
+ $newVersionString = 'version: 1.2.3.{build}'
+ $appveyor = New-Object System.Text.StringBuilder
+ $null = $appveyor.AppendLine('#---------------------------------#')
+ $null = $appveyor.AppendLine('')
+ $null = $appveyor.AppendLine($versionString)
+ $null = $appveyor.AppendLine('install:')
+ $null = $appveyor.AppendLine(' - ps: |')
+ $null = $appveyor.AppendLine(' Import-Module "$env:APPVEYOR_BUILD_FOLDER\AppVeyor.psm1"')
+ $null = $appveyor.AppendLine(' Invoke-AppveyorInstallTask')
+ $null = $appveyor.AppendLine('')
+ $null = $appveyor.AppendLine('#---------------------------------#')
+ $appveyorPath = "$TestDrive\appveyor.yml"
+
+ Mock -CommandName Get-ChildItem -MockWith { @{ FullName = $appveyorPath } }
+ Mock -CommandName Get-Content -MockWith {$appveyor.ToString()}
+ $appveyorValue = $appveyor.ToString() -replace [regex]::Escape($versionString), $newVersionString
+ Mock -CommandName Set-Content -MockWith {} `
+ -ParameterFilter {
+ $Path -eq $appveyorPath -and
+ $Value -eq $appveyorValue.TrimEnd()} -Verifiable
+
+ It "Should update the version number" {
+ Update-AppVeyorConfiguration -ModuleVersion $moduleVersion
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Get-ProjectContributorList' -Tag 'tools' {
+ $repository = @{
+ name = 'PowerStig'
+ api_url = 'https://api.github.com'
+ }
+ $users = @(
+ @{
+ user = @{
+ login = 'tester1'
+ }
+ },
+ @{
+ user = @{
+ login = 'tester2'
+ }
+ } | ConvertTo-Json
+ )
+ Mock -CommandName Invoke-RestMethod -MockWith { return $users | ConvertFrom-Json } `
+ -ParameterFilter {$URI -eq "$($repository.api_url)/pulls"}
+
+ Mock -CommandName Invoke-RestMethod -MockWith { return 'testDetails' } `
+ -ParameterFilter {$URI -match "$($repository.api_url)/users"}
+
+ $list = Get-ProjectContributorList -Repository $repository
+ It 'Should return list if user details' {
+ $list[0] | Should Be 'testDetails'
+ }
+ }
+
+ Describe 'Get-GitHubApiKey' -Tag 'tools' {
+
+ It 'Should load the secure string from disk' {
+ Mock -CommandName Split-Path -MockWith { return } -Verifiable
+ Mock -CommandName Get-Content -MockWith { 'APIKeyMaterial' } `
+ -ParameterFilter { $Path.EndsWith('PowerStigGitHubApi.txt')} -Verifiable
+ Mock -CommandName ConvertTo-SecureString -MockWith {} -Verifiable
+ Get-GitHubApiKey
+ Assert-VerifiableMock
+ }
+
+ It 'Should load the file that is passed in' {
+ Mock -CommandName Test-Path -MockWith {return $true} -Verifiable
+ Mock -CommandName Get-Content -MockWith { 'APIKeyMaterial' } `
+ -ParameterFilter { $Path.EndsWith('sampleFile.txt')} -Verifiable
+ Mock -CommandName ConvertTo-SecureString -MockWith {} -Verifiable
+ Get-GitHubApiKey -SecureFilePath "$Testdrive\sampleFile.txt"
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Get-GitHubRefStatus' -Tag 'tools' {
+
+ $stateList = @('pending', 'failure', 'success')
+ $repository = @{}
+
+ Foreach ($state in $stateList)
+ {
+ It "Should return '$state' from rest API" {
+ Mock -CommandName Invoke-RestMethod -MockWith { return @{ state = $state } }
+ $status = Get-GitHubRefStatus -Repository $repository -Name test
+ $status | Should Be $state
+ }
+ }
+
+ It "Should throw after waiting 10 minutes for a task to complete" {
+ Mock -CommandName Invoke-RestMethod -MockWith { return @{ state = 'pending' } }
+ Mock -CommandName Start-Sleep -MockWith { continue } -Verifiable
+ { Get-GitHubRefStatus -Repository $repository -Name test -WaitForSuccess } | Should Throw
+ }
+ }
+
+ Describe 'New-GitHubPullRequest' -Tag 'tools' {
+ $moduleVersion = '1.2.3.4'
+ $repository = @{
+ name = 'PowerStig'
+ api_url = 'https://api.github.com'
+ }
+ Mock -CommandName Invoke-RestMethod -MockWith { return @{ Status = '201 Created' } } `
+ -ParameterFilter { $Uri -eq "$($Repository.api_url)/pulls"} -Verifiable
+ It 'Should create a PR on GitHub' {
+ $response = New-GitHubPullRequest -Repository $repository -ModuleVersion $moduleVersion -BranchHead 'dev'
+ $response.Status | Should Be '201 Created'
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Get-GitHubPullRequest' -Tag 'tools' {
+ $pullRequestNumber = '34'
+ $repository = @{
+ name = 'PowerStig'
+ api_url = 'https://api.github.com'
+ }
+
+ $openPullRequestList = @(
+ @{
+ id = '27'
+ head = @{
+ ref = 'new-feature1'
+ }
+ base = @{
+ ref = 'dev'
+ }
+ },
+ @{
+ id = $pullRequestNumber
+ head = @{
+ ref = 'new-feature2'
+ }
+ base = @{
+ ref = 'master'
+ }
+ } #| ConvertTo-Json
+ )
+
+ It 'Should return a specifc PR with the correct head and base on GitHub' {
+ Mock -CommandName Invoke-RestMethod -MockWith { return $openPullRequestList } `
+ -ParameterFilter { $Uri -eq "$($Repository.api_url)/pulls"} -Verifiable
+ $response = Get-GitHubPullRequest -Repository $repository -BranchHead 'new-feature1' -BranchBase 'dev'
+ $response.head.ref | Should Be 'new-feature1'
+ Assert-VerifiableMock
+ }
+
+ It 'Should return a specifc PR number on GitHub' {
+ Mock -CommandName Invoke-RestMethod -MockWith { return $openPullRequestList[1] } `
+ -ParameterFilter { $Uri -eq "$($Repository.api_url)/pulls/$pullRequestNumber"} -Verifiable
+ $response = Get-GitHubPullRequest -Repository $repository -Number $pullRequestNumber
+ $response.id | Should Be $pullRequestNumber
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Approve-GitHubPullRequest' -Tag 'tools' {
+
+ $pullRequest = @{
+ name = 'PowerStig'
+ url = 'https://api.github.com/pullrequest'
+ }
+ Mock -CommandName Invoke-RestMethod -MockWith { return @{ merged = $true } } `
+ -ParameterFilter { $Uri -eq "$($pullRequest.url)/merge"} -Verifiable
+ It 'Should create a PR on GitHub' {
+ $response = Approve-GitHubPullRequest -PullRequest $pullRequest -CommitTitle 'Commit Title' -CommitMessage 'Commit Message'
+ $response.merged | Should Be $true
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'New-GitHubRelease' -Tag 'tools' {
+ $repository = @{
+ name = 'PowerStig'
+ url = 'https://api.github.com/pullrequest'
+ }
+ Mock -CommandName Invoke-RestMethod -MockWith { return @{ status = '201 Created' } } `
+ -ParameterFilter { $Uri -eq "$($repository.api_url)/releases"} -Verifiable
+ It 'Should create a release on GitHub' {
+ $response = New-GitHubRelease -Repository $repository -TagName '1.2.3.4-PSGallery' -Title 'Release Title' -Description 'Release Notes'
+ $response.status | Should Be '201 Created'
+ Assert-VerifiableMock
+ }
+ }
+
+ Describe 'Start-PowerStigRelease' -Tag 'tools' {
+
+ $testGitRepositoryPath = 'c:\dev\project'
+ $testModuleVersion = '1.2.3.4'
+ $testReleaseBranchName = "$testModuleVersion-Release"
+ $testReleaseNotes = 'added feature X'
+ $repository = @{
+ name = 'PowerStig'
+ url = 'https://api.github.com'
+ }
+ $pullRequest = @{
+ head = @{
+ sha = 'b8e9aca7e6114734b1710b727786294d0e6a277b'
+ }
+ }
+ Mock -CommandName Push-Location -MockWith { } `
+ -ParameterFilter { $GitRepositoryPath -eq $testGitRepositoryPath } -Verifiable
+ Mock -CommandName Get-GitBranch -MockWith { 'dev' } -Verifiable
+ Mock -CommandName Get-PowerStigRepository -MockWith { return $repository } -Verifiable
+ Mock -CommandName Test-ModuleVersion -MockWith { $true } `
+ -ParameterFilter { $ModuleVersion -eq $testModuleVersion } -Verifiable
+ Mock -CommandName New-GitReleaseBranch -MockWith { } `
+ -ParameterFilter { $BranchName -eq $testReleaseBranchName } -Verifiable
+ Mock -CommandName Get-UnreleasedNotes -MockWith { return $testReleaseNotes } -Verifiable
+ Mock -CommandName Update-Readme -MockWith { } `
+ -ParameterFilter { $ModuleVersion -eq $testModuleVersion } -Verifiable
+ Mock -CommandName Update-Manifest -MockWith { } `
+ -ParameterFilter { $ModuleVersion -eq $testModuleVersion -and $ReleaseNotes -eq $testReleaseNotes } -Verifiable
+ Mock -CommandName Update-AppVeyorConfiguration -MockWith { } `
+ -ParameterFilter { $ModuleVersion -eq $testModuleVersion } -Verifiable
+ Mock -CommandName Push-GitBranch -MockWith { } `
+ -ParameterFilter { $Name -eq $testReleaseBranchName } -Verifiable
+ Mock -CommandName Get-GitHubApiKey -MockWith { } -Verifiable
+ Mock -CommandName Get-GitHubRefStatus -MockWith { 'Success' } `
+ -ParameterFilter { $Name -eq $testReleaseBranchName -and $WaitForSuccess -eq $true } -Verifiable
+ Mock -CommandName New-GitHubPullRequest -MockWith { return $pullRequest } `
+ -ParameterFilter { $BranchHead -eq $testReleaseBranchName } -Verifiable
+ Mock -CommandName Get-GitHubRefStatus -MockWith { 'Success' } `
+ -ParameterFilter { $Name -eq $pullRequest.head.sha -and $WaitForSuccess -eq $true } -Verifiable
+
+ Context 'New Release' {
+
+ Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion
+
+ It 'Should push to the repo location' {
+ Assert-MockCalled -CommandName Push-Location
+ }
+ It 'Should get the repo details' {
+ Assert-MockCalled -CommandName Get-PowerStigRepository
+ }
+
+ Context 'Module Version' {
+ It 'Should test the module version is greater than currently release' {
+ Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion
+ Assert-MockCalled -CommandName Test-ModuleVersion
+ }
+ It 'Should throw if the module version is not greater than currently release' {
+ Mock -CommandName Test-ModuleVersion -MockWith { $false } `
+ -ParameterFilter { $ModuleVersion -eq $testModuleVersion }
+ {Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion} |
+ Should Throw
+ }
+ }
+ It 'Should create a new release branch' {
+ Assert-MockCalled -CommandName New-GitReleaseBranch
+ }
+ Context 'Release Notes' {
+ It 'Should return the release notes' {
+ Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion
+ Assert-MockCalled -CommandName Get-UnreleasedNotes
+ }
+ It 'Should throw if no release notes are found' {
+ Mock -CommandName Get-UnreleasedNotes -MockWith { return '' } -Verifiable
+ {Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion} |
+ Should Throw
+ }
+ }
+ It 'Should update the readme' {
+ Assert-MockCalled -CommandName Update-Readme
+ }
+ It 'Should update the module manifest' {
+ Assert-MockCalled -CommandName Update-Manifest
+ }
+ It 'Should update the AppVeyor yaml' {
+ Assert-MockCalled -CommandName Update-AppVeyorConfiguration
+ }
+ It 'Should push the release branch to GitHub' {
+ Assert-MockCalled -CommandName Push-GitBranch
+ }
+ It 'Should get the GitHub api key' {
+ Assert-MockCalled -CommandName Get-GitHubApiKey
+ }
+ Context 'Release branch build status' {
+ It 'Should check the status of the release build' {
+ Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion
+ Assert-MockCalled -CommandName Get-GitHubRefStatus
+ }
+ It 'Should throw if the release build status is not success' {
+ Mock -CommandName Get-GitHubRefStatus -MockWith { 'Failed' } `
+ -ParameterFilter { $Name -eq $testReleaseBranchName -and $WaitForSuccess -eq $true }
+ {Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion} |
+ Should Throw
+ }
+ }
+ Context 'Pull request' {
+ It 'Should create a new Pull request' {
+ Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion
+ Assert-MockCalled -CommandName New-GitHubPullRequest
+ }
+ It 'Should check the status of the pull request build' {
+ Assert-MockCalled -CommandName Get-GitHubRefStatus -ParameterFilter { $Name -eq $pullRequest.head.sha -and $WaitForSuccess -eq $true }
+ }
+ It 'Should throw if the pull request build status is not success' {
+ Mock -CommandName Get-GitHubRefStatus -MockWith { 'Failed' } `
+ -ParameterFilter { $Name -eq $pullRequest.head.sha -and $WaitForSuccess -eq $true }
+ {Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion} |
+ Should Throw
+ }
+ }
+ }
+
+ Context 'Continue Release' {
+
+ Start-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion -Continue
+
+ It 'Should push to the repo location' {
+ Assert-MockCalled -CommandName Push-Location
+ }
+ It 'Should get the repo details' {
+ Assert-MockCalled -CommandName Get-PowerStigRepository
+ }
+ It 'Should not test the module version' {
+ Assert-MockCalled -CommandName Test-ModuleVersion -Times 0
+ }
+ It 'Should not create a new release branch' {
+ Assert-MockCalled -CommandName New-GitReleaseBranch -Times 0
+ }
+ It 'Should not return the release notes' {
+ Assert-MockCalled -CommandName Get-UnreleasedNotes -Times 0
+ }
+ It 'Should not update the readme' {
+ Assert-MockCalled -CommandName Update-Readme -Times 0
+ }
+ It 'Should not update the module manifest' {
+ Assert-MockCalled -CommandName Update-Manifest -Times 0
+ }
+ It 'Should not update the AppVeyor yaml' {
+ Assert-MockCalled -CommandName Update-AppVeyorConfiguration -Times 0
+ }
+ It 'Should not push the release branch to GitHub' {
+ Assert-MockCalled -CommandName Push-GitBranch -Times 0
+ }
+ }
+ }
+
+ Describe 'Complete-PowerStigRelease' -Tag 'tools' {
+ $testGitRepositoryPath = 'c:\dev\project'
+ $testModuleVersion = '1.2.3.4'
+ $testReleaseBranchName = "$testModuleVersion-Release"
+ $testReleaseNotes = 'added feature X'
+ $powerShellDataFileObject = @{
+ PrivateData = @{
+ PSData = @{
+ ReleaseNotes = $testReleaseNotes
+ }
+ }
+ }
+ $repository = @{
+ name = 'PowerStig'
+ url = 'https://api.github.com'
+ }
+ $pullRequest = @{
+ head = @{
+ sha = 'b8e9aca7e6114734b1710b727786294d0e6a277b'
+ }
+ }
+ $testManifestPath = 'c:\test\path\manifest.psd1'
+
+ Mock -CommandName Push-Location -MockWith { } `
+ -ParameterFilter { $GitRepositoryPath -eq $testGitRepositoryPath } -Verifiable
+ Mock -CommandName Get-GitBranch -MockWith { 'dev' } -Verifiable
+ Mock -CommandName Get-PowerStigRepository -MockWith { return $repository } -Verifiable
+ Mock -CommandName Get-GitHubApiKey -MockWith { } -Verifiable
+ Mock -CommandName Get-GitHubPullRequest -MockWith { return $pullRequest } `
+ -ParameterFilter { $BranchHead -eq $testReleaseBranchName } -Verifiable
+ Mock -CommandName Approve-GitHubPullRequest -MockWith { } `
+ -ParameterFilter { $PullRequest -eq $PullRequest -and $MergeMethod -eq 'merge' } -Verifiable
+ Mock -CommandName Get-ChildItem -MockWith { @{FullName = $testManifestPath } }
+ Mock -CommandName Import-PowerShellDataFile -MockWith { return $powerShellDataFileObject } `
+ -ParameterFilter { $Path -eq $testManifestPath } -Verifiable
+ Mock -CommandName New-GitHubRelease -ParameterFilter { $Description -eq $testReleaseNotes } -Verifiable
+ Mock -CommandName Remove-GitReleaseBranch -ParameterFilter { $BranchName -eq $testReleaseBranchName} -Verifiable
+
+ Complete-PowerStigRelease -GitRepositoryPath $testGitRepositoryPath -ModuleVersion $testModuleVersion
+
+ It 'Should push to the repo location' {
+ Assert-MockCalled -CommandName Push-Location
+ }
+ It 'Should get the repo details' {
+ Assert-MockCalled -CommandName Get-PowerStigRepository
+ }
+ It 'Should get the GitHub API key' {
+ Assert-MockCalled -CommandName Get-GitHubApiKey
+ }
+ It 'Should get the GitHub release pull request' {
+ Assert-MockCalled -CommandName Get-GitHubPullRequest
+ }
+ It 'Should approve the GitHub release pull request' {
+ Assert-MockCalled -CommandName Approve-GitHubPullRequest
+ }
+ It 'Should Create a new GitHub release' {
+ Assert-MockCalled -CommandName New-GitHubRelease
+ }
+ It 'Should remove the release branch from all repos' {
+ Assert-MockCalled -CommandName Remove-GitReleaseBranch
+ }
+ }
+
+ Describe 'Complete-PowerStigDevMerge' -Tag 'tools' {
+ $testGitRepositoryPath = 'c:\dev\project'
+ $testPullRequestNumber = 488
+ $repository = @{
+ name = 'PowerStig'
+ url = 'https://api.github.com'
+ }
+ $pullRequest = @{
+ head = @{
+ sha = 'b8e9aca7e6114734b1710b727786294d0e6a277b'
+ }
+ }
+
+ Mock -CommandName Push-Location -MockWith { } `
+ -ParameterFilter { $GitRepositoryPath -eq $testGitRepositoryPath } -Verifiable
+ Mock -CommandName Get-PowerStigRepository -MockWith { return $repository } -Verifiable
+ Mock -CommandName Get-GitHubApiKey -MockWith { } -Verifiable
+ Mock -CommandName Get-GitHubPullRequest -MockWith { return $pullRequest } `
+ -ParameterFilter { $Number -eq $testPullRequestNumber } -Verifiable
+ Mock -CommandName Approve-GitHubPullRequest -MockWith { } `
+ -ParameterFilter { $PullRequest -eq $PullRequest -and $MergeMethod -eq 'squash' } -Verifiable
+ Mock Set-GitBranch -MockWith {} -ParameterFilter { $Branch -eq 'dev' } -Verifiable
+ Mock -CommandName Update-Readme -MockWith { } `
+ -ParameterFilter { $Repository -eq $repository } -Verifiable
+ Mock -CommandName Push-GitBranch -MockWith { } `
+ -ParameterFilter { $Name -eq 'dev' } -Verifiable
+
+ Complete-PowerStigDevMerge -GitRepositoryPath $testGitRepositoryPath -PullRequestNumber $testPullRequestNumber
+
+ It 'Should push to the repo location' {
+ Assert-MockCalled -CommandName Push-Location
+ }
+ It 'Should get the repo details' {
+ Assert-MockCalled -CommandName Get-PowerStigRepository
+ }
+ It 'Should get the GitHub API key' {
+ Assert-MockCalled -CommandName Get-GitHubApiKey
+ }
+ It 'Should get the GitHub release pull request' {
+ Assert-MockCalled -CommandName Get-GitHubPullRequest
+ }
+ It 'Should approve the GitHub release pull request' {
+ Assert-MockCalled -CommandName Approve-GitHubPullRequest
+ }
+ It 'Should switch to the dev branch' {
+ Assert-MockCalled -CommandName Set-GitBranch
+ }
+ It 'Should update the dev branch readme contributors' {
+ Assert-MockCalled -CommandName Update-Readme
+ }
+ It 'Should push the dev branch to GitHub' {
+ Assert-MockCalled -CommandName Push-GitBranch
+ }
+ }
+ }
+}
+finally
+{
+
+}
diff --git a/Tests/Unit/Tools/TestHelper.tests.ps1 b/Tests/Unit/Tools/TestHelper.tests.ps1
new file mode 100644
index 000000000..734c72e8b
--- /dev/null
+++ b/Tests/Unit/Tools/TestHelper.tests.ps1
@@ -0,0 +1,13 @@
+$unitTestRoot = Split-Path -Path $PSScriptRoot -Parent
+. "$unitTestRoot\.tests.header.ps1"
+
+try
+{
+ Describe 'TestHelper' -Tag 'tools' {
+
+ }
+}
+finally
+{
+
+}
diff --git a/Tests/Unit/Tools/WikiPages.tests.ps1 b/Tests/Unit/Tools/WikiPages.tests.ps1
new file mode 100644
index 000000000..5a42df985
--- /dev/null
+++ b/Tests/Unit/Tools/WikiPages.tests.ps1
@@ -0,0 +1,58 @@
+$unitTestRoot = Split-Path -Path $PSScriptRoot -Parent
+. "$unitTestRoot\.tests.header.ps1"
+try
+{
+ InModuleScope $script:ModuleName {
+
+ Describe 'Format-HelpString' -Tag 'tools' {
+
+ Context 'Multi-Line' {
+ It "Should Format the help string" {
+ $testString = @{
+ sample = @(
+ 'This is a multi line string. '
+ 'There are multiple lines.') -Join "`n"
+ result = @(
+ 'This is a multi line string.'
+ 'There are multiple lines.') -Join "`n"
+ }
+ Format-HelpString -String $testString.sample |
+ Should Be $testString.result
+ }
+ It "Should return full sentences on each line." {
+ $testString = @{
+ sample = @(
+ 'This is a multi line'
+ 'string. That has a period.') -Join "`n"
+ result = @(
+ 'This is a multi line string.'
+ 'That has a period.') -Join "`n"
+ }
+
+ Format-HelpString -String $testString.sample |
+ Should Be $testString.result
+ }
+ }
+
+ Context 'Single-Line' {
+
+ It "Should return multiple sentences on a singe line." {
+ $testString = @{
+ sample = @(
+ 'This is a multi line string. '
+ 'There are multiple lines.') -Join "`n"
+ result = @(
+ 'This is a multi line string.'
+ 'There are multiple lines.') -Join " "
+ }
+ Format-HelpString -String $testString.sample -SingleLine |
+ Should Be $testString.result
+ }
+ }
+ }
+ }
+}
+finally
+{
+
+}
diff --git a/Tests/helper.psm1 b/Tests/helper.psm1
deleted file mode 100644
index 9f8bc52d3..000000000
--- a/Tests/helper.psm1
+++ /dev/null
@@ -1,195 +0,0 @@
-Import-Module $PSScriptRoot\..\PowerStig.psm1
-
-function Get-RequiredStigDataVersion
-{
- [CmdletBinding()]
- param()
-
- $Manifest = Import-PowerShellDataFile -Path "$relDirectory\$moduleName.psd1"
-
- return $Manifest.RequiredModules.Where({$PSItem.ModuleName -eq 'PowerStig'}).ModuleVersion
-}
-
-function Get-StigDataRootPath
-{
- param ( )
-
- return Resolve-Path -Path "$PsScriptRoot\..\StigData"
-}
-
-<#
- .SYNOPSIS
- Get all of the version files to test
-
- .PARAMETER CompositeResourceName
- The name of the composite resource used to filter the results
-#>
-function Get-StigFileList
-{
- [CmdletBinding()]
- param
- (
- [Parameter(Mandatory = $true)]
- [string]
- $CompositeResourceName
- )
-
- #
- $stigFilePath = Get-StigDataRootPath
- $stigVersionFiles = Get-ChildItem -Path $stigFilePath -Exclude "*.org*"
-
- $stigVersionFiles
-}
-
-<#
- .SYNOPSIS
- Returns a list of stigs for a given resource. This is used in integration testign by looping
- through every valide STIG found in the StigData directory.
-
- .PARAMETER CompositeResourceName
- The resource to filter the results
-
- .PARAMETER Filter
- Parameter description
-
-#>
-function Get-StigVersionTable
-{
- [OutputType([psobject])]
- [CmdletBinding()]
- param
- (
- [Parameter(Mandatory = $true)]
- [string]
- $CompositeResourceName,
-
- [Parameter()]
- [string]
- $Filter
- )
-
- $include = Import-PowerShellDataFile -Path $PSScriptRoot\CompositeResourceFilter.psd1
-
- $path = "$(Get-StigDataRootPath)\Processed"
-
- $versions = Get-ChildItem -Path $path -Exclude "*.org.*", "*.xsd" -Include $include.$CompositeResourceName -File -Recurse
-
- $versionTable = @()
- foreach ($version in $versions)
- {
- if ($version.Basename -match $Filter)
- {
- $stigDetails = $version.BaseName -Split "-"
-
- $versionTable += @{
- 'Technology' = $stigDetails[0]
- 'TechnologyVersion' = $stigDetails[1]
- 'TechnologyRole' = $stigDetails[2]
- 'StigVersion' = $stigDetails[3]
- 'Path' = $version.fullname
- }
- }
- }
-
- return $versionTable
-}
-
-<#
- .SYNOPSIS
- Using an AST, it returns the name of a configuration in the composite resource schema file.
-
- .PARAMETER FilePath
- The full path to the resource schema module file
-#>
-function Get-ConfigurationName
-{
- [CmdletBinding()]
- [OutputType([string[]])]
- param
- (
- [Parameter(Mandatory = $true)]
- [String]
- $FilePath
- )
-
- $AST = [System.Management.Automation.Language.Parser]::ParseFile(
- $FilePath, [ref] $null, [ref] $Null
- )
-
- # Get the Export-ModuleMember details from the module file
- $ModuleMember = $AST.Find( {
- $args[0] -is [System.Management.Automation.Language.ConfigurationDefinitionAst]}, $true)
-
- return $ModuleMember.InstanceName.Value
-}
-
-<#
- .SYNOPSIS
- Returns the list of StigVersion nunmbers that are defined in the ValidateSet parameter attribute
-
- .PARAMETER FilePath
- THe full path to the resource to read from
-#>
-function Get-StigVersionParameterValidateSet
-{
- [OutputType([string])]
- [CmdletBinding()]
- param
- (
- [Parameter(Mandatory = $true)]
- [string]
- $FilePath
- )
-
- $compositeResource = Get-Content -Path $FilePath -Raw
-
- $AbstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput(
- $compositeResource, [ref]$null, [ref]$null)
-
- $params = $AbstractSyntaxTree.FindAll(
- {$args[0] -is [System.Management.Automation.Language.ParameterAst]}, $true)
-
- # Filter the specifc ParameterAst
- $paramToUpdate = $params |
- Where-Object {$PSItem.Name.VariablePath.UserPath -eq 'StigVersion'}
-
- # Get the specifc parameter attribute to update
- $validate = $paramToUpdate.Attributes.Where(
- {$PSItem.TypeName.Name -eq 'ValidateSet'})
-
- return $validate.PositionalArguments.Value
-}
-
-<#
- .SYNOPSIS
- Get a unique list of valid STIG versions from the StigData
-
- .PARAMETER TechnologyRoleFilter
- The technology role to filter the results
-#>
-
-function Get-ValidStigVersionNumbers
-{
- [OutputType([string])]
- [CmdletBinding()]
- param
- (
- [Parameter(Mandatory = $true)]
- [string]
- $TechnologyRoleFilter
- )
-
- $versionNumbers = (Get-Stiglist |
- Where-Object {$PSItem.TechnologyRole -match $TechnologyRoleFilter} |
- Select-Object StigVersion -ExpandProperty StigVersion -Unique )
-
- return $versionNumbers
-}
-
-Export-ModuleMember -Function @(
- 'Get-PowerStigVersionFromManifest',
- 'Get-StigVersionTable',
- 'Get-ConfigurationName',
- 'Get-StigVersionParameterValidateSet',
- 'Get-ValidStigVersionNumbers'
-)
diff --git a/Tools/AppVeyor/AppVeyor.psm1 b/Tools/AppVeyor/AppVeyor.psm1
new file mode 100644
index 000000000..954791483
--- /dev/null
+++ b/Tools/AppVeyor/AppVeyor.psm1
@@ -0,0 +1,274 @@
+# Load the test helper module.
+#$testHelperPath = Join-Path -Path $PSScriptRoot -ChildPath 'TestHelper.psm1'
+#Import-Module -Name $testHelperPath -Force
+
+<#
+ .SYNOPSIS
+ Performs the after tests tasks for the AppVeyor build process.
+
+ This includes:
+ 1. Optional: Produce and upload Wiki documentation to AppVeyor.
+ 2. Set version number in Module Manifest to build version
+ 3. Zip up the module content and produce a checksum file and upload to AppVeyor.
+ 4. Pack the module into a Nuget Package.
+ 5. Upload the Nuget Package to AppVeyor.
+
+ Executes Start-CustomAppveyorAfterTestTask if defined in .AppVeyor\CustomAppVeyorTasks.psm1
+ in resource module repository.
+
+ .PARAMETER Type
+ This controls the additional processes that can be run after testing.
+ To produce wiki documentation specify 'Wiki', otherwise leave empty to use
+ default value 'Default'.
+
+ .PARAMETER MainModulePath
+ This is the relative path of the folder that contains the module manifest.
+ If not specified it will default to the root folder of the repository.
+
+ .PARAMETER ResourceModuleName
+ Name of the Resource Module being produced.
+ If not specified will default to GitHub repository name.
+
+ .PARAMETER Author
+ The Author string to insert into the NUSPEC file for the package.
+ If not specified will default to 'Microsoft'.
+
+ .PARAMETER Owners
+ The Owners string to insert into the NUSPEC file for the package.
+ If not specified will default to 'Microsoft'.
+#>
+function Invoke-PowerStigAppveyorAfterTestTask
+{
+ [CmdletBinding(DefaultParameterSetName = 'Default')]
+ param
+ (
+ [Parameter()]
+ [ValidateNotNullOrEmpty()]
+ [String]
+ $MainModulePath = $env:APPVEYOR_BUILD_FOLDER,
+
+ [Parameter()]
+ [String]
+ $ModuleName = (($env:APPVEYOR_REPO_NAME -split '/')[1]),
+
+ [Parameter()]
+ [String]
+ $Author,
+
+ [Parameter()]
+ [String]
+ $Owners,
+
+ [Parameter()]
+ [String]
+ $Tags
+ )
+
+ # Convert the Main Module path into an absolute path if it is relative
+ if (-not ([System.IO.Path]::IsPathRooted($MainModulePath)))
+ {
+ $MainModulePath = Join-Path -Path $env:APPVEYOR_BUILD_FOLDER `
+ -ChildPath $MainModulePath
+ }
+
+ $manifest = Import-PowerShellDataFile -Path "$MainModulePath\$moduleName`.psd1"
+
+ $fileListPath = "$env:APPVEYOR_BUILD_FOLDER\.NuspecFileList.json"
+ if (Test-Path -Path $fileListPath )
+ {
+ $fileList = Get-Content -Path $fileListPath | ConvertFrom-Json
+ }
+
+ # Create the Nuspec file for the Nuget Package in the Main Module Folder
+ $nuspecPath = Join-Path -Path $MainModulePath -ChildPath "$ModuleName.nuspec"
+
+ $nuspecParams = @{
+ PackageName = $ModuleName
+ DestinationPath = $MainModulePath
+ Version = $env:APPVEYOR_BUILD_VERSION
+ Author = $manifest.Author
+ Owners = $manifest.CompanyName
+ LicenseUrl = $manifest.PrivateData.PSData.LicenseUri
+ ProjectUrl = $manifest.PrivateData.PSData.ProjectUri
+ PackageDescription = $ModuleName
+ Tags = $manifest.PrivateData.PSData.Tags -join ' '
+ ReleaseNotes = $manifest.PrivateData.PSData.ReleaseNotes
+ FileList = $fileList
+ RequiredModules = $manifest.RequiredModules
+ }
+ New-Nuspec @nuspecParams
+
+ # Create the Nuget Package
+ $nugetExePath = (Get-Command nuget).Source
+
+ Start-Process -FilePath $nugetExePath -Wait -ArgumentList @(
+ 'Pack', $nuspecPath
+ '-OutputDirectory', $env:APPVEYOR_BUILD_FOLDER
+ '-BasePath', $MainModulePath
+ )
+
+ # Push the Nuget Package up to AppVeyor
+ $nugetPackageName = Join-Path -Path $env:APPVEYOR_BUILD_FOLDER `
+ -ChildPath "$ModuleName.$($env:APPVEYOR_BUILD_VERSION).nupkg"
+ Get-ChildItem $nugetPackageName | ForEach-Object -Process {
+ Push-AppveyorArtifact -Path $_.FullName -FileName $_.Name
+ }
+
+ Write-Info -Message 'After Test Task Complete.'
+}
+
+
+<#
+ .SYNOPSIS
+ Creates a nuspec file for a nuget package at the specified path.
+
+ .EXAMPLE
+ New-Nuspec `
+ -PackageName 'TestPackage' `
+ -Version '1.0.0.0' `
+ -Author 'Microsoft Corporation' `
+ -Owners 'Microsoft Corporation' `
+ -DestinationPath C:\temp `
+ -LicenseUrl 'http://license' `
+ -PackageDescription 'Description of the package' `
+ -Tags 'tag1 tag2'
+#>
+function New-Nuspec
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [String]
+ $PackageName,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $Version,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $Author,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $Owners,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $DestinationPath,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $LicenseUrl,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $ProjectUrl,
+
+ [Parameter()]
+ [String]
+ $IconUrl,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $PackageDescription,
+
+ [Parameter(Mandatory = $true)]
+ [String]
+ $ReleaseNotes,
+
+ [Parameter()]
+ [String]
+ $Tags,
+
+ [Parameter()]
+ [AllowNull()]
+ [pscustomobject]
+ $FileList,
+
+ [Parameter()]
+ [AllowNull()]
+ [pscustomobject]
+ $RequiredModules
+ )
+ # https://docs.microsoft.com/en-us/nuget/reference/nuspec
+
+ $currentYear = (Get-Date).Year
+
+ $nuspecFileContent = New-Object System.Text.StringBuilder
+ $null = $nuspecFileContent.AppendLine('')
+ $null = $nuspecFileContent.AppendLine('')
+ $null = $nuspecFileContent.AppendLine(' ')
+ $null = $nuspecFileContent.AppendLine(" $PackageName")
+ $null = $nuspecFileContent.AppendLine(" $Version")
+ $null = $nuspecFileContent.AppendLine(" $Author")
+ $null = $nuspecFileContent.AppendLine(" $Owners")
+
+ if (-not [String]::IsNullOrEmpty($LicenseUrl))
+ {
+ $null = $nuspecFileContent.AppendLine(" $LicenseUrl")
+ }
+
+ if (-not [String]::IsNullOrEmpty($ProjectUrl))
+ {
+ $null = $nuspecFileContent.AppendLine(" $ProjectUrl")
+ }
+
+ if (-not [String]::IsNullOrEmpty($IconUrl))
+ {
+ $null = $nuspecFileContent.AppendLine(" $IconUrl")
+ }
+
+ $null = $nuspecFileContent.AppendLine(" true")
+ $null = $nuspecFileContent.AppendLine(" $PackageDescription")
+ $null = $nuspecFileContent.AppendLine(" $ReleaseNotes")
+ $null = $nuspecFileContent.AppendLine(" Copyright $currentYear")
+ $null = $nuspecFileContent.AppendLine(" $Tags")
+
+ if ($RequiredModules)
+ {
+ $null = $nuspecFileContent.AppendLine(" ")
+
+ ForEach($dependency in $RequiredModules)
+ {
+ $moduleName = $dependency.ModuleName
+ $moduleVersion = "[$($dependency.ModuleVersion)]"
+ $null = $nuspecFileContent.AppendLine(" ")
+ }
+ $null = $nuspecFileContent.AppendLine(" ")
+ }
+
+ $null = $nuspecFileContent.AppendLine(" ")
+
+ if (-not [String]::IsNullOrEmpty($fileList))
+ {
+ $null = $nuspecFileContent.AppendLine(" ")
+
+ foreach ($file in $fileList)
+ {
+ $null = $nuspecFileContent.AppendLine(" ")
+ }
+ $null = $nuspecFileContent.AppendLine(" ")
+ }
+
+ $null = $nuspecFileContent.AppendLine("")
+
+ if (-not $DestinationPath)
+ {
+ $DestinationPath = $Path
+ }
+
+ if (-not (Test-Path -Path $DestinationPath))
+ {
+ $null = New-Item -Path $DestinationPath -ItemType 'Directory'
+ }
+
+ $nuspecFilePath = Join-Path -Path $DestinationPath -ChildPath "$PackageName.nuspec"
+
+ $null = New-Item -Path $nuspecFilePath -ItemType 'File' -Force
+
+ $null = Set-Content -Path $nuspecFilePath -Value $nuspecFileContent.ToString()
+}
+
+Export-ModuleMember -Function *
diff --git a/Tools/Release/Release.psm1 b/Tools/Release/Release.psm1
new file mode 100644
index 000000000..8f6472128
--- /dev/null
+++ b/Tools/Release/Release.psm1
@@ -0,0 +1,1129 @@
+#requires -module posh-git
+#requires -version 6.0
+
+$script:ReleaseName = "{0}-Release"
+
+<#
+ .SYNOPSIS
+ Validates that the supplied module version is greater than the current
+ version listed in the module manifest.
+#>
+function Test-ModuleVersion
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [version]
+ $ModuleVersion,
+
+ [Parameter()]
+ [string]
+ $ManifestPath
+ )
+
+ if (Get-GitBranch -ne 'master')
+ {
+ Set-GitBranch -Branch master
+ }
+
+ if (-not $ManifestPath)
+ {
+ $ManifestPath = (Get-ChildItem -Path $PWD -Filter "*.psd1").FullName
+ }
+
+ $masterManifest = Import-PowerShellDataFile -Path $ManifestPath
+
+ if ($ModuleVersion -le $masterManifest.ModuleVersion)
+ {
+ return $false
+ }
+
+ return $true
+}
+#region Git
+
+function Invoke-Git
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Command
+ )
+
+ Invoke-Expression "git $Command" 2>&1
+}
+
+<#
+ .SYNOPSIS
+ Returns the remote url details of the local git repository. The remote
+ url is used to build the return object properties.
+#>
+function Get-PowerStigRepository
+{
+ [OutputType([string])]
+ [CmdletBinding()]
+ param()
+
+ Write-Host "Testing git is installed"
+
+ $git = Get-Command "git" -ErrorAction SilentlyContinue
+ if ($git)
+ {
+ Write-Verbose "Git is found on the System at $($git.Source)"
+ }
+ else
+ {
+ throw "Git was not found on the System. Please install Git and try again"
+ }
+
+ Write-Host "Getting remote repository details"
+
+ $gitRemote = Invoke-Git -Command "remote get-url origin"
+
+ $baseUrl = $gitRemote -replace '\.git$',''
+ if ([string]::IsNullOrEmpty($gitRemote) -or
+ $gitRemote -notmatch "^https://github.com/Microsoft/PowerStig")
+ {
+ throw "$gitRemote is not a PowerStig Project. Please select a PowerStig project to release."
+ }
+ else
+ {
+ Write-Verbose -Message "Releasing $baseUrl"
+ }
+
+ return [ordered]@{
+ 'name' = ($baseUrl -split "/")[-1]
+ 'html_url' = $baseUrl
+ 'api_url' = $baseUrl -replace 'github\.com', 'api.github.com/repos'
+ }
+}
+
+<#
+ .SYNOPSIS
+ A set companion function to the posh-git Get-GitBranch function.
+
+ .PARAMETER Branch
+ The name of the branch to switch to.
+
+ .Parameter SkipPull
+ By default the branch remote is pulled as soon as it is swithced into.
+ This switch skips the pull step. This normaly just a perf boost.
+#>
+function Set-GitBranch
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Branch,
+
+ [Parameter()]
+ [switch]
+ $SkipPull
+ )
+
+ $currentBranch = Get-GitBranch
+
+ # If the case is input incorrectly, drop it to lower for dev and master
+ if ($Branch -match "dev|master")
+ {
+ $Branch = $Branch.ToLower()
+ }
+
+ if ($currentBranch -ne $Branch)
+ {
+ Write-Host "Switching to $Branch branch"
+ $null = Invoke-Git -Command "checkout $Branch"
+ }
+ else
+ {
+ Write-Verbose -Message "Already in $Branch branch"
+ }
+
+ if ($SkipPull)
+ {
+ Write-Verbose -Message "Skipping Pull"
+ }
+ else
+ {
+ Write-Verbose -Message "Pulling $Branch branch from origin"
+ $null = Invoke-Git -Command "pull"
+ }
+}
+
+function New-GitReleaseBranch
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $BranchName
+ )
+
+ if(Get-GitBranch -ne 'dev')
+ {
+ Set-GitBranch -Branch dev
+ }
+
+ Write-Host "Creating Git branch ($BranchName)"
+
+ $branch = Invoke-Git -Command "show-branch $BranchName"
+
+ If ($branch -match "^\[$BranchName\]")
+ {
+ $null = Invoke-Git -Command "checkout $BranchName"
+ }
+ else
+ {
+ $null = Invoke-Git -Command "checkout -b $BranchName dev"
+ }
+}
+
+function Remove-GitReleaseBranch
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $BranchName
+ )
+
+ Write-Host "Removing Git branch ($BranchName)"
+
+ # Merge the release back into dev
+ if(Get-GitBranch -ne 'dev')
+ {
+ Set-GitBranch -Branch dev
+ }
+
+ Invoke-Git -Command "merge $branchName"
+ # Push dev to GitHub
+ Invoke-Git -Command "push"
+ # Delete the release branch (Locally and remotely)
+ Invoke-Git -Command "branch -d $branchName"
+ # push the delete to GitHub
+ Invoke-Git -Command "push origin -d $branchName"
+ # Remove the origin branch reference from the local repo
+ Invoke-Git -Command "remote prune origin"
+}
+
+function Push-GitBranch
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Name,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $CommitMessage
+ )
+
+ Write-Host "Pushing ($Name) to GitHub."
+
+ $commit = Invoke-Git -Command "commit -a -m '$CommitMessage'"
+
+ if ($commit -match 'Your branch is up to date')
+ {
+ Write-Host "Nothing to commit or push"
+ return
+ }
+ $null = Invoke-Git -Command "push -u origin $Name"
+}
+#endregion
+
+#region Update Version and release notes
+
+<#
+ .SYNOPSIS
+ Get Unreleased content from the readme
+#>
+function Get-UnreleasedNotes
+{
+ [OutputType([string])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter()]
+ [string]
+ $Branch
+ )
+ Write-Host "Getting unreleased notes from Readme."
+ $readmePath = (Get-ChildItem -Path $PWD -Filter "readme.md").FullName
+ $readmeContent = Get-Content -Path $readmePath
+
+ $h3 = '^###'
+ $unreleasedHeader = "$h3\s+Unreleased"
+ $latestedreleaseHeader = "$h3\s+\d\.\d\.\d\.\d"
+
+ $unreleasedLine = $readmeContent |
+ Select-String -Pattern $unreleasedHeader
+ $latestedreleaseLine = ($readmeContent |
+ Select-String -Pattern $latestedreleaseHeader)[0]
+
+ $releaseNotes = $readmeContent[
+ ($unreleasedLine.LineNumber)..($latestedreleaseLine.LineNumber - 2)] |
+ Out-String
+
+ return $releaseNotes.Trim()
+}
+
+<#
+ .SYNOPSIS
+ Adds a new version section to the readme released notes or updates the
+ list of contributors to the Contributors section.
+
+ .PARAMETER ModuleVersion
+ The module version to add to the readme below the unreleased section
+
+ .PARAMETER Repository
+ A hashtable that contains the repository api url to query for the list of contributors
+#>
+function Update-Readme
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true, ParameterSetName = 'ReleaseNotes')]
+ [version]
+ $ModuleVersion,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'Contributors')]
+ [hashtable]
+ $Repository
+ )
+
+ Write-Host "Updating $($PSCmdlet.ParameterSetName)."
+ $readmePath = (Get-ChildItem -Path $PWD -Filter "readme.md").FullName
+ $readmeContent = Get-Content -Path $readmePath -Raw
+
+ switch ($PSCmdlet.ParameterSetName)
+ {
+ 'ReleaseNotes'
+ {
+ $unreleasedHeaderRegEx = '###\sUnreleased'
+ $unreleasedHeaderReplace = New-Object System.Text.StringBuilder
+ $null = $unreleasedHeaderReplace.AppendLine('### Unreleased')
+ $null = $unreleasedHeaderReplace.AppendLine('')
+ $null = $unreleasedHeaderReplace.AppendLine("### $ModuleVersion")
+
+ $readmeContent = $readmeContent -replace $unreleasedHeaderRegEx,
+ $unreleasedHeaderReplace.ToString().Trim()
+ continue
+ }
+ 'Contributors'
+ {
+ $contributorList = Get-ProjectContributorList -Repository $Repository
+ $contributorsMd = New-Object System.Text.StringBuilder
+ $null = $contributorsMd.AppendLine('')
+ $null = $contributorsMd.AppendLine('')
+
+ foreach ($contributor in $contributorList)
+ {
+ $line = "* [@$($contributor.login)](https://github.com/$($contributor.login))"
+ if ($contributor.Name)
+ {
+ $line = $line + " ($($contributor.Name))"
+ }
+
+ $null = $contributorsMd.AppendLine($line)
+ }
+ $null = $contributorsMd.AppendLine('')
+
+ $readmeContributorsRegEx = '(?<=### Contributors)[^#]+(?=#)'
+ $readmeContent = $readmeContent -replace $readmeContributorsRegEx,$contributorsMd.ToString()
+ continue
+ }
+ }
+
+ Set-Content -Path $readmePath -Value $readmeContent.Trim()
+}
+
+<#
+ .SYNOPSIS
+ Updates a module manifest version and release notes.
+
+ .PARAMETER ModuleVersion
+ The version number to update the manifest with
+
+ .PARAMETER ReleaseNotes
+ The release notes to update the manifest with
+#>
+function Update-Manifest
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [version]
+ $ModuleVersion,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $ReleaseNotes,
+
+ [Parameter()]
+ [string]
+ $ManifestPath
+ )
+
+ Write-Host "Updating version number and release notes in module manifest."
+
+ if (-not $ManifestPath)
+ {
+ $ManifestPath = (Get-ChildItem -Path $PWD -Filter "*.psd1").FullName
+ }
+
+ $manifestContent = Get-Content -Path $ManifestPath -Raw
+ $moduleVersionRegex = '(?<=ModuleVersion\s*=\s*'')(?.*)(?=''(?!(\s*)}))'
+ $manifestContent = $manifestContent -replace $moduleVersionRegex, $ModuleVersion
+
+ $releaseNotesRegEx = "(?<=ReleaseNotes\s*=\s*')[^']+(?=')"
+ $manifestContent = $manifestContent -replace $releaseNotesRegEx, $ReleaseNotes
+
+ Set-Content -Path $ManifestPath -Value $manifestContent.TrimEnd()
+}
+
+<#
+ .SYNOPSIS
+ Updates the AppVeyor build configuration yaml file with the module
+ version number
+
+ .PARAMETER ModuleVersion
+ The version number to update the manifest with
+#>
+function Update-AppVeyorConfiguration
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [version]
+ $ModuleVersion
+ )
+
+ Write-Host "Updating appveyor configuration."
+
+ $appveyorPath = (Get-ChildItem -Path $PWD -Filter "appveyor.yml").FullName
+ $appveyorContent = Get-Content -Path $appveyorPath
+ $regex = 'version\:\s\d\.\d\.\d\.\{build\}'
+ $appveyorContent = $appveyorContent -replace $regex,
+ "version: $($moduleVersion.major).$($moduleVersion.Minor).$($moduleVersion.Build).{build}"
+
+ Set-Content -Path $appveyorPath -Value $appveyorContent.TrimEnd()
+}
+
+#endregion
+#region Update Contributor list
+
+<#
+ .SYNOPSIS
+ Queries the GitHub repo and returns a unique list of users that have
+ submitted closed pull requests into the dev branch
+
+ .PARAMETER Repository
+ A hashtable that contains the repository api url to query for the list of contributors
+#>
+function Get-ProjectContributorList
+{
+ [OutputType([string[]])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [hashtable]
+ $Repository
+ )
+
+ # https://developer.github.com/v3/pulls/#list-pull-requests
+ $gitHubReleaseParam = [ordered]@{
+ Authentication = 'OAuth'
+ Token = $script:GitHubApiKeySecure
+ Uri = "$($Repository.api_url)/pulls"
+ Method = 'Get'
+ Body = [ordered]@{
+ state = 'closed'
+ base = 'dev'
+ }
+ }
+ $pulls = Invoke-RestMethod @gitHubReleaseParam
+
+ [System.Collections.ArrayList]$users = @(($pulls | Select-Object user ).user.login | Select-Object -Unique)
+
+ # There were several contributors before this project was moved to GitHub, so
+ # make sure they are given credit along side the contributions from GitHub.
+ $preGitHubContributors = @{
+ PowerStig = @('jcwalker','regedit32','bgouldman','mcollera')
+ PowerStigDsc = @('jcwalker','regedit32','bgouldman','mcollera')
+ }
+
+ foreach($user in $preGitHubContributors.($Repository.name))
+ {
+ if($users -notcontains $user)
+ {
+ $null = $users.Add($user)
+ }
+ }
+
+ $contributors = [System.Collections.ArrayList]@()
+ foreach ($user in $users)
+ {
+ # https://developer.github.com/v3/users/#get-a-single-user
+ $gitHubReleaseParam = [ordered]@{
+ Authentication = 'OAuth'
+ Token = $script:GitHubApiKeySecure
+ Uri = "https://api.github.com/users/$user"
+ Method = 'Get'
+ }
+ # The GitHub release triggers the AppVeyor deployment to the Gallery.
+ $userDetails = Invoke-RestMethod @gitHubReleaseParam
+
+ $null = $contributors.Add($userDetails)
+ }
+
+ return $contributors | Sort-Object login
+}
+
+#endregion
+#region GitHub
+
+function Get-GitHubApiKey
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(ParameterSetName = 'SecureFile')]
+ [AllowNull()]
+ [string]
+ $SecureFilePath
+ )
+
+ if ([string]::IsNullOrEmpty($SecureFilePath))
+ {
+ $SecureFilePath = "$(Split-Path $profile)\PowerStigGitHubApi.txt"
+ }
+ elseif (-not (Test-Path -Path $SecureFilePath))
+ {
+ throw "$SecureFilePath was not found. Please move the secure file here or provide a valid path."
+ }
+
+ try
+ {
+ [System.Security.SecureString] $GitHubKeySecure =
+ Get-Content -Path $SecureFilePath | ConvertTo-SecureString
+ }
+ catch
+ {
+ throw "Unable to convert $SecureFilePath to a secure string."
+ }
+
+ $script:GitHubApiKeySecure = $GitHubKeySecure
+}
+
+<#
+ .SYNOPSIS
+ Get's the status of a branch based on the policies that are applied
+
+ .PARAMETER Repository
+ A hashtable that contains the repository api url to query
+
+ .PARAMETER Name
+ A SHA, branch name, or tag name to get the status for
+
+ .PARAMETER WaitForSuccess
+ A switch that will start a loop that waits for a success status message
+ or a 10 minute wait timeout
+#>
+function Get-GitHubRefStatus
+{
+ [OutputType([string])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [hashtable]
+ $Repository,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Name,
+
+ [Parameter()]
+ [switch]
+ $WaitForSuccess
+ )
+
+ # https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
+ $restParameters = [ordered]@{
+ 'Authentication' = 'OAuth'
+ 'Token' = $script:GitHubApiKeySecure
+ 'Uri' = "$($Repository.api_url)/commits/$Name/status"
+ 'Method' = 'Get'
+ }
+
+ [int] $i = 0
+ [int] $waitCounter = 20
+ [int] $waitSeconds = 30
+ do
+ {
+ $response = (Invoke-RestMethod @restParameters).state
+
+ if ($response -eq 'pending')
+ {
+ if (-not $WaitForSuccess)
+ {
+ return $response
+ }
+ Write-Host "$Name is in a pending state, starting sleep (30 seconds)."
+ Start-Sleep -Seconds $waitSeconds
+ $i ++
+ }
+ elseif ($response -eq 'failure')
+ {
+ return $response
+ }
+ else
+ {
+ return $response
+ }
+ }
+ until ($response.state -eq 'success' -or $i -ge $waitCounter )
+
+ throw "Timeout $([timespan]::fromseconds($waitCounter*$waitSeconds))"
+}
+
+<#
+ .SYNOPSIS
+ Create a Pull request on GitHub with the provided branch head that is
+ merging into the branch base.
+
+#>
+function New-GitHubPullRequest
+{
+ [OutputType([PSObject])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [hashtable]
+ $Repository,
+
+ [Parameter(Mandatory = $true)]
+ [version]
+ $ModuleVersion,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $BranchHead,
+
+ [Parameter()]
+ [string]
+ $BranchBase = 'master'
+ )
+
+ # https://developer.github.com/v3/pulls/#create-a-pull-request
+ $restMethodParamList = [ordered]@{
+ Authentication = 'OAuth'
+ Token = $script:GitHubApiKeySecure
+ Uri = "$($Repository.api_url)/pulls"
+ Method = 'Post'
+ Body = [ordered]@{
+ title = "Release of version $ModuleVersion."
+ body = "Releasing version $ModuleVersion."
+ head = $BranchName
+ base = $BranchBase
+ } | ConvertTo-Json
+ }
+
+ Invoke-RestMethod @restMethodParamList
+}
+
+function Get-GitHubPullRequest
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true, ParameterSetName = 'one')]
+ [Parameter(Mandatory = $true, ParameterSetName = 'all')]
+ [hashtable]
+ $Repository,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'one')]
+ [int]
+ $Number,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'all')]
+ [string]
+ $BranchHead,
+
+ [Parameter(Mandatory = $true, ParameterSetName = 'all')]
+ [string]
+ $BranchBase
+ )
+
+ # https://developer.github.com/v3/pulls/#list-pull-requests
+ $pullRequestParams = @{
+ Authentication = 'OAuth'
+ Token = $script:GitHubApiKeySecure
+ Method = 'Get'
+ Uri = "$($Repository.api_url)/pulls"
+ }
+
+ If ($PSCmdlet.ParameterSetName -eq 'one')
+ {
+ # https://developer.github.com/v3/pulls/#get-a-single-pull-request
+ $pullRequestParams.Uri = $pullRequestParams.Uri + "/$Number"
+ return Invoke-RestMethod @pullRequestParams
+ }
+
+ $prList = Invoke-RestMethod @pullRequestParams
+
+ return $prList | Where-Object {
+ $PSItem.head.ref -eq $BranchHead -and
+ $PSItem.base.ref -eq $BranchBase
+ }
+}
+
+function Approve-GitHubPullRequest
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [psobject]
+ $PullRequest,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $CommitTitle,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $CommitMessage,
+
+ [Parameter()]
+ [ValidateSet('merge','squash','rebase')]
+ [string]
+ $MergeMethod = 'merge'
+ )
+
+ # https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
+ $restMethodParam = [ordered]@{
+ Authentication = 'OAuth'
+ Token = $script:GitHubApiKeySecure
+ Uri = "$($PullRequest.url)/merge"
+ Method = 'Put'
+ Body = [ordered]@{
+ commit_title = $CommitTitle
+ commit_message = $CommitMessage
+ sha = $PullRequest.head.sha
+ merge_method = $MergeMethod.ToLower()
+ } | ConvertTo-Json
+ }
+
+ Invoke-RestMethod @restMethodParam
+}
+
+function New-GitHubRelease
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [hashtable]
+ $Repository,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $TagName,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Title,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Description,
+
+ [Parameter()]
+ [bool]
+ $Draft,
+
+ [Parameter()]
+ [bool]
+ $Prerelease
+ )
+
+ # https://developer.github.com/v3/repos/releases/#create-a-release
+ $restMethodParam = [ordered]@{
+ Authentication = 'OAuth'
+ Token = $script:GitHubApiKeySecure
+ Uri = "$($Repository.api_url)/releases"
+ Method = 'Post'
+ Body = [ordered]@{
+ tag_name = $TagName
+ target_commitish = 'master'
+ name = $Title
+ body = $Description
+ draft = $Draft
+ prerelease = $Prerelease
+ } | ConvertTo-Json
+ }
+
+ Invoke-RestMethod @restMethodParam
+}
+
+#endregion
+
+<#
+ .SYNOPSIS
+ Starts the PowerStig release process for a given module
+
+ .DESCRIPTION
+ Applies a standard process and comment structure to the release process
+ for a given module. At a high level, this function will:
+
+ 1. Validate that you are trying to deploy and PowerStig module
+ 2. Validate that you are trying to release a higher version number than
+ is currently released.
+ 3. Create a release branch from dev and update the module version number
+ in all of the appropriate places.
+ 4. Push the release branch to GitHub and create a pull request into the
+ master branch.
+
+ .PARAMETER GitRepositoryPath
+ The path to the git repository on your local machine. If this path is
+ not a valid git repository, this function will throw an error.
+ Additionally, if the repository remote origin is not a PowerStig project,
+ this function will throw an error
+
+ .PARAMETER ModuleVersion
+ The version number that is injected to the different areas of the
+ release process. Specifically the module manifest, AppVeyor build config,
+ and readme release notes.
+
+ .PARAMETER GitHubApiSecureFilePath
+ The path to the secured GitHub API key. If you have a file named
+ PowerStigGitHubApi.txt in $profile, it will automatically be loaded.
+ Running the following command will prompt you for your GitHub API key
+ and create the file for you so that you can skip this parameter.
+
+ Read-Host "Enter Password" -AsSecureString |
+ ConvertFrom-SecureString |
+ Out-File "$(Split-Path $profile)\PowerStigGitHubApi.txt"
+#>
+function Start-PowerStigRelease
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $GitRepositoryPath,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $ModuleVersion,
+
+ [Parameter()]
+ [string]
+ $GitHubApiSecureFilePath,
+
+ [Parameter()]
+ [switch]
+ $Continue
+ )
+
+ # Convert GitRepositoryPath into an absolute path if it is relative
+ if (-not ([System.IO.Path]::IsPathRooted($GitRepositoryPath)))
+ {
+ $GitRepositoryPath = Resolve-Path -Path $GitRepositoryPath
+ }
+
+ Push-Location -Path $GitRepositoryPath
+ $beginGitBranch = Get-GitBranch
+ $branchName = $script:ReleaseName -f $ModuleVersion
+
+ try
+ {
+ $repository = Get-PowerStigRepository
+
+ if (-not $Continue)
+ {
+ if (Test-ModuleVersion -ModuleVersion $ModuleVersion)
+ {
+ Write-Verbose -Message "$ModuleVersion is greater than $($masterManifest.ModuleVersion)"
+ }
+ else
+ {
+ throw "The module version ($ModuleVersion) is not greater than currently released."
+ }
+
+ New-GitReleaseBranch -BranchName $branchName
+
+ $releaseNotes = Get-UnreleasedNotes
+
+ if ([string]::IsNullOrEmpty($releaseNotes))
+ {
+ throw 'There are no release notes for this release.'
+ }
+
+ Update-Readme -ModuleVersion $ModuleVersion
+
+ Update-Manifest -ModuleVersion $ModuleVersion -ReleaseNotes $releaseNotes
+
+ Update-AppVeyorConfiguration -ModuleVersion $ModuleVersion
+
+ # Push the release branch to GitHub
+ Push-GitBranch -Name $branchName -CommitMessage "Bumped version number to $ModuleVersion for release."
+ }
+
+ Get-GitHubApiKey -SecureFilePath $GitHubApiSecureFilePath
+
+ # Get the Dev Banch status. Wait until it is success or failure
+ $gitHubRefStatusParam = [ordered]@{
+ 'Repository' = $repository
+ 'Name' = $branchName
+ 'WaitForSuccess' = $true
+ }
+ $gitHubReleaseBranchStatus = Get-GitHubRefStatus @gitHubRefStatusParam
+
+ if ($gitHubReleaseBranchStatus -eq 'success')
+ {
+ $pullRequestParameters = @{
+ Repository = $Repository
+ ModuleVersion = $ModuleVersion
+ BranchHead = $branchName
+ }
+ $pullRequest = New-GitHubPullRequest @pullRequestParameters
+
+ $gitHubRefStatusParam = [ordered]@{
+ Repository = $repository
+ Name = $pullRequest.head.sha
+ WaitForSuccess = $true
+ }
+ $gitHubPullRequestStatus = Get-GitHubRefStatus @gitHubRefStatusParam
+
+ if ($gitHubPullRequestStatus -eq 'success')
+ {
+ Write-Output "Pull request for $branchName success."
+ }
+ else
+ {
+ throw 'Pull Request build failed, Aborting release'
+ }
+ }
+ else
+ {
+ throw "$branchName is currently failing and cannot be merged into Master."
+ }
+ }
+ finally
+ {
+ Write-Verbose -Message 'Reverting to initial location'
+ Pop-Location
+ Write-Verbose -Message 'Reverting to initial branch'
+ Set-GitBranch -Branch $beginGitBranch -SkipPull
+ }
+}
+
+<#
+ .SYNOPSIS
+ Completes the PowerStig release process for a given module that was
+ created using the Start-PowerStigRelease function.
+
+ .DESCRIPTION
+ Applies a standard process and comment structure to the release process
+ for a given module. At a high level, this function will:
+
+ 1. Validate that you are trying to deploy and PowerStig module
+ 2. Approve the pull request on GitHub
+ 3. Create a GitHub Release (Triggers an AppVeyor deployment)
+ 4. Cleans up the release branch from the local and remote repository
+
+ .PARAMETER GitRepositoryPath
+ The path to the git repository on your local machine. If this path is
+ not a valid git repository, this function will throw an error.
+ Additionally, if the repository remote origin is not a PowerStig project,
+ this function will throw an error
+
+ .PARAMETER ModuleVersion
+ The version number that is injected to the different areas of the
+ release process. Specifically the module manifest, AppVeyor build config,
+ and readme release notes.
+
+ .PARAMETER GitHubApiSecureFilePath
+ The path to the secured GitHub API key. If you have a file named
+ PowerStigGitHubApi.txt in $profile, it will automatically be loaded.
+ Running the following command will prompt you for your GitHub API key
+ and create the file for you so that you can skip this parameter.
+
+ Read-Host "Enter Password" -AsSecureString |
+ ConvertFrom-SecureString |
+ Out-File "$(Split-Path $profile)\PowerStigGitHubApi.txt"
+#>
+function Complete-PowerStigRelease
+{
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $GitRepositoryPath,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $ModuleVersion,
+
+ [Parameter()]
+ [string]
+ $GitHubApiSecureFilePath
+ )
+
+ # Convert GitRepositoryPath into an absolute path if it is relative
+ if (-not ([System.IO.Path]::IsPathRooted($GitRepositoryPath)))
+ {
+ $GitRepositoryPath = Resolve-Path -Path $GitRepositoryPath
+ }
+
+ Push-Location -Path $GitRepositoryPath
+ $beginGitBranch = Get-GitBranch
+ $branchName = $script:ReleaseName -f $ModuleVersion
+
+ try
+ {
+ $repository = Get-PowerStigRepository
+
+ Get-GitHubApiKey -SecureFilePath $GitHubApiSecureFilePath
+
+ $pullRequestParam = @{
+ Repository = $repository
+ BranchBase = 'master'
+ BranchHead = $branchName
+ }
+ $pullRequest = Get-GitHubPullRequest @pullRequestParam
+
+ $approvePullRequestParam = [ordered]@{
+ PullRequest = $pullRequest
+ CommitTitle = 'Release'
+ CommitMessage = 'This PR is automatically completed.'
+ MergeMethod = 'merge'
+ }
+ $null = Approve-GitHubPullRequest @approvePullRequestParam
+
+ # Get the manifest release notes to add to release
+ $manifestPath = (Get-ChildItem -Path $PWD -Filter "*.psd1").FullName
+ $releaseNotes = (Import-PowerShellDataFile -Path $manifestPath).PrivateData.PSData.ReleaseNotes
+
+ $gitHubReleaseParams = @{
+ Repository = $repository
+ TagName = $branchName -replace 'Release', 'PSGallery'
+ Title = "Release of version $(($branchName -Split '-')[0])"
+ Description = $releaseNotes
+ }
+ # The GitHub release triggers the AppVeyor deployment to the Gallery.
+ $null = New-GitHubRelease @gitHubReleaseParams
+
+ Remove-GitReleaseBranch -BranchName $branchName
+ }
+ finally
+ {
+ Write-Verbose -Message 'Reverting to initial location'
+ Pop-Location -Verbose
+ Write-Verbose -Message 'Reverting to initial branch'
+ Set-GitBranch -Branch $beginGitBranch -SkipPull
+ }
+}
+
+<#
+ .SYNOPSIS
+ Completes the PowerStig release process for a given module that was
+ created using the Start-PowerStigRelease function.
+
+ .DESCRIPTION
+
+
+ .PARAMETER GitRepositoryPath
+ The path to the git repository on your local machine. If this path is
+ not a valid git repository, this function will throw an error.
+ Additionally, if the repository remote origin is not a PowerStig project,
+ this function will throw an error
+
+ .PARAMETER PullRequestNumber
+ The pull request number to complete.
+
+ .PARAMETER GitHubApiSecureFilePath
+ The path to the secured GitHub API key. If you have a file named
+ PowerStigGitHubApi.txt in $profile, it will automatically be loaded.
+ Running the following command will prompt you for your GitHub API key
+ and create the file for you so that you can skip this parameter.
+
+ Read-Host "Enter Password" -AsSecureString |
+ ConvertFrom-SecureString |
+ Out-File "$(Split-Path $profile)\PowerStigGitHubApi.txt"
+#>
+function Complete-PowerStigDevMerge
+{
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $GitRepositoryPath,
+
+ [Parameter(Mandatory = $true)]
+ [int]
+ $PullRequestNumber,
+
+ [Parameter()]
+ [string]
+ $GitHubApiSecureFilePath
+ )
+
+ # Convert GitRepositoryPath into an absolute path if it is relative
+ if (-not ([System.IO.Path]::IsPathRooted($GitRepositoryPath)))
+ {
+ $GitRepositoryPath = Resolve-Path -Path $GitRepositoryPath
+ }
+
+ Push-Location -Path $GitRepositoryPath
+
+ try
+ {
+ $repository = Get-PowerStigRepository
+
+ Get-GitHubApiKey -SecureFilePath $GitHubApiSecureFilePath
+
+ $pullRequestParam = @{
+ Repository = $repository
+ Number = $PullRequestNumber
+ }
+ $pullRequest = Get-GitHubPullRequest @pullRequestParam
+
+ $approvePullRequestParam = [ordered]@{
+ PullRequest = $pullRequest
+ CommitTitle = 'Merged into dev'
+ CommitMessage = 'Accepted PR'
+ MergeMethod = 'squash'
+ }
+ $pullRequest = Approve-GitHubPullRequest @approvePullRequestParam
+
+ Set-GitBranch -Branch dev
+
+ Update-Readme -Repository $repository
+
+ Push-GitBranch -Name 'dev' -CommitMessage "Updated contributor list"
+ }
+ catch
+ {
+ Pop-Location
+ }
+}
+
+Export-ModuleMember -Function '*-PowerStigRelease', '*-PowerStigDevMerge'
diff --git a/Tests/CompositeResourceFilter.psd1 b/Tools/TestHelper/CompositeResourceFilter.psd1
similarity index 100%
rename from Tests/CompositeResourceFilter.psd1
rename to Tools/TestHelper/CompositeResourceFilter.psd1
diff --git a/Tools/TestHelper/Data/readme.md b/Tools/TestHelper/Data/readme.md
new file mode 100644
index 000000000..ac66cbe53
--- /dev/null
+++ b/Tools/TestHelper/Data/readme.md
@@ -0,0 +1,51 @@
+# Sample XCCDF
+
+The Sample xccdf base content is designed to be reusable across all unit and integration tests using
+PowerShell composite formatting.
+
+## Index
+The samplexccdf contains 4 indexes that you can inject data into for testing. If any of these fields
+are not applicable to your tests, then simply pass in an empty string. Since these are indexed, the
+order is important.
+
+* 0 - The STIG Title
+* 1 - release-info
+* 2 - version
+* 3 - groups
+ If you do not want to provide the contents of the group element, you can use the sample group
+
+
+## How to use the content in your tests that use the benchmark element.
+
+ # Import the base benchmark xml string data.
+ $BaseXccdfContent = Get-Content -Path "$PSScriptRoot\..\..\..\sampleXccdf.xml.txt"
+
+ # Create a test drive File
+ $TestFile = "TestDrive:\TextData.xml"
+
+ # Inject the data you need for your tests using the index above
+ $BaseXccdfContent -f $title.key,'','','' | Out-File $TestFile
+
+
+# How to use the content in your tests that use the group elements.
+
+ # Import the base benchmark xml string data.
+ $BaseXccdfContent = Get-Content -Path "$PSScriptRoot\..\..\..\sampleXccdf.xml.txt"
+
+ # Create a test drive File
+ $TestFile = "TestDrive:\TextData.xml"
+
+ # Create a sample group that structured like your test target
+ $group = '
+
+ Rule Title
+
+
+ The check content you want to test goes here.
+
+
+
+
+ '
+ # Inject the data you need for your tests using the index above
+ $BaseXccdfContent -f $title.key,'','',$group | Out-File $TestFile
diff --git a/Tools/TestHelper/Data/sampleXccdf.xml.readme.txt b/Tools/TestHelper/Data/sampleXccdf.xml.readme.txt
new file mode 100644
index 000000000..47020bfe2
--- /dev/null
+++ b/Tools/TestHelper/Data/sampleXccdf.xml.readme.txt
@@ -0,0 +1,46 @@
+The Sample xccdf base content is designed to be reusable across all unit and integration tests using
+PowerShell composite formatting.
+
+Index
+
+0 - The STIG Title
+1 - release-info
+2 - version
+3 - groups
+4 - id - an element of the Benchmark node
+
+
+# How to use the content in your tests that use the benchmark element.
+
+ # Import the base benchmark xml string data.
+ $BaseXccdfContent = Get-Content -Path "$PSScriptRoot\..\..\..\sampleXccdf.xml.txt"
+
+ # Create a test drive File
+ $TestFile = "TestDrive:\TextData.xml"
+
+ # Inject the data you need for your tests using the index above
+ $BaseXccdfContent -f $title.key,'','','' | Out-File $TestFile
+
+
+# How to use the content in your tests that use the group elements.
+
+ # Import the base benchmark xml string data.
+ $BaseXccdfContent = Get-Content -Path "$PSScriptRoot\..\..\..\sampleXccdf.xml.txt"
+
+ # Create a test drive File
+ $TestFile = "TestDrive:\TextData.xml"
+
+ # Create a sample group that structured like your test target
+ $group = '
+
+ Rule Title
+
+
+ The check content you want to test goes here.
+
+
+
+
+ '
+ # Inject the data you need for your tests using the index above
+ $BaseXccdfContent -f $title.key,'','',$group | Out-File $TestFile
diff --git a/Tools/TestHelper/Data/sampleXccdf.xml.txt b/Tools/TestHelper/Data/sampleXccdf.xml.txt
new file mode 100644
index 000000000..44aee439c
--- /dev/null
+++ b/Tools/TestHelper/Data/sampleXccdf.xml.txt
@@ -0,0 +1,23 @@
+
+
+
+ accepted
+ {0}
+ The {0} (STIG) is published as a tool to improve the security of Department of Defense (DoD) information systems. Comments or proposed revisions to this document should be sent via e-mail to the following address: disa.stig_spt@mail.mil.
+ Developed_by
+
+ DISA
+ STIG.DOD.MIL
+
+ {1}
+ {2}
+{3}
+
diff --git a/Tools/TestHelper/Data/samplegroup.xml.txt b/Tools/TestHelper/Data/samplegroup.xml.txt
new file mode 100644
index 000000000..c7d0499a8
--- /dev/null
+++ b/Tools/TestHelper/Data/samplegroup.xml.txt
@@ -0,0 +1,25 @@
+
+ {1}
+ <GroupDescription></GroupDescription>
+
+ XXXX-YY-000002
+ {2}
+ <VulnDiscussion>{3}</VulnDiscussion><FalsePositives></FalsePositives><FalseNegatives></FalseNegatives><Documentable>false</Documentable><Mitigations></Mitigations><SeverityOverrideGuidance></SeverityOverrideGuidance><PotentialImpacts></PotentialImpacts><ThirdPartyTools></ThirdPartyTools><MitigationControl></MitigationControl><Responsibility></Responsibility><IAControls></IAControls>
+
+
+ DPMS Target Technology
+ DISA
+ DPMS Target
+ Technology
+ 2350
+
+ CCE--12345-6
+ CCI-123456
+ {4}
+
+
+
+ {5}
+
+
+
diff --git a/Tools/TestHelper/TestHelper.psm1 b/Tools/TestHelper/TestHelper.psm1
new file mode 100644
index 000000000..af0cd6d6a
--- /dev/null
+++ b/Tools/TestHelper/TestHelper.psm1
@@ -0,0 +1,449 @@
+
+<#
+ .SYNOPSIS
+ Used to process the Check-Content test strings the same way that the SplitCheckContent
+ static method does in the the STIG base class.
+ .PARAMETER CheckContent
+ Strings to process.
+#>
+function Split-TestStrings
+{
+ [OutputType([string[]])]
+ param
+ (
+ [Parameter(mandatory = $true)]
+ [string]
+ $CheckContent
+ )
+
+ $CheckContent -split '\n' |
+ Select-String -Pattern "\w" |
+ ForEach-Object { $PSitem.ToString().Trim() }
+}
+
+<#
+ .SYNOPSIS
+ Used to validate an xml file against a specified schema
+
+ .PARAMETER XmlFile
+ Path and file name of the XML file to be validated
+
+ .PARAMETER Xml
+ An already loaded System.Xml.XmlDocument
+
+ .PARAMETER SchemaFile
+ Path of XML schema used to validate the XML document
+
+ .PARAMETER ValidationEventHandler
+ Script block that is run when an error occurs while validating XML
+
+ .EXAMPLE
+ Test-XML -XmlFile C:\source\test.xml -SchemaFile C:\Source\test.xsd
+
+ .EXAMPLE
+ $xmlobject = Get-StigData -OsVersion 2012R2 -OsRole MemberServer
+ Test-XML -Xml $xmlobject -SchemaFile C:\Source\test.xsd
+#>
+function Test-Xml
+{
+ [OutputType([Boolean])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'File')]
+ [string]
+ $XmlFile,
+
+ [Parameter(ValueFromPipeline = $true, Mandatory = $true, ParameterSetName = 'Object')]
+ [xml]
+ $Xml,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $SchemaFile,
+
+ [scriptblock]
+ $ValidationEventHandler = { Throw $PSItem.Exception }
+ )
+
+ If (-not (Test-Path -Path $SchemaFile))
+ {
+ Throw "Schema file not found"
+ }
+
+ $schemaReader = New-Object System.Xml.XmlTextReader $SchemaFile
+ $schema = [System.Xml.Schema.XmlSchema]::Read($schemaReader, $ValidationEventHandler)
+
+ If ($PsCmdlet.ParameterSetName -eq "File")
+ {
+ $xml = New-Object System.Xml.XmlDocument
+ $xml.Load($XmlFile)
+ }
+
+ $xml.Schemas.Add($schema) | Out-Null
+ $xml.Validate($ValidationEventHandler)
+}
+
+<#
+ .SYNOPSIS
+ Creates a sample xml docuement that injects class specifc data
+
+ .PARAMETER TestFile
+ The test rule to merge into the data
+#>
+function Get-TestStigRule
+{
+ [CmdletBinding(DefaultParameterSetName = 'UseExisting')]
+ param
+ (
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $XccdfTitle = '(Technology) Security Technical Implementation Guide',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $XccdfRelease = 'Release: 1 Benchmark Date: 1 Jan 1970',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $XccdfVersion = '2',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $XccdfId = 'Technology_Target',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $CheckContent = 'This is a string of text that tells an admin what item to check to verify compliance.',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $GroupId = 'V-1000',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $GroupTitle = 'Sample Title',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $GroupRuleTitle = 'A more descriptive title.',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $GroupRuleDescription = 'A description of what this vulnerability addresses and how it mitigates the threat.',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [string]
+ $FixText = 'This is a string of text that tells an admin how to fix an item if it is not currently configured properly and ignored by the parser',
+
+ [Parameter(Parametersetname = 'UseExisting')]
+ [Parameter(Parametersetname = 'FileProvided')]
+ [switch]
+ $ReturnGroupOnly,
+
+ [Parameter(Mandatory = $true, Parametersetname = 'FileProvided')]
+ [string]
+ $FilePathToGroupElementText
+ )
+
+ # If a file path is provided, override the default sample group element text.
+ if ( $FilePathToGroupElementText )
+ {
+ try
+ {
+ $groupElement = Get-Content -Path $FilePathToGroupElementText -Raw
+ }
+ catch
+ {
+ throw "$FilePathToGroupElementText was not found"
+ }
+ }
+ else
+ {
+ # Get the samplegroup element text and merge in the parameter strings
+ $groupElement = Get-Content -Path "$PSScriptRoot\data\sampleGroup.xml.txt" -Encoding UTF8 -Raw
+ $groupElement = $groupElement -f $GroupId, $GroupTitle, $RuleTitle, $RuleDescription, $FixText, $CheckContent
+ }
+
+ # Get and merge the group element data into the xccdf xml document and create an xml object to return
+ $xmlDocument = Get-Content -Path "$PSScriptRoot\data\sampleXccdf.xml.txt" -Encoding UTF8 -Raw
+ [xml] $xmlDocument = $xmlDocument -f $XccdfTitle, $XccdfRelease, $XccdfVersion, $groupElement, $XccdfId
+
+ # Some tests only need the group to test functionality.
+ if ($ReturnGroupOnly)
+ {
+ return $xmlDocument.Benchmark.group
+ }
+
+ return $xmlDocument
+}
+
+<#
+ .SYNOPSIS
+ Used to retrieve an array of Stig class base methods and optionally add child class methods
+ for purpose of testing methods
+
+ .PARAMETER ChildClassMethodNames
+ An array of child class method to add to the class base methods
+
+ .EXAMPLE
+ $RegistryRuleClassMethods = Get-StigBaseMethods -ChildClassMethodsNames @('SetKey','SetName')
+#>
+Function Get-StigBaseMethods
+{
+ [OutputType([System.Collections.ArrayList])]
+ param
+ (
+ [Parameter()]
+ [system.array]
+ $ChildClassMethodNames,
+
+ [Parameter()]
+ [switch]
+ $Static
+ )
+
+ if ( $Static )
+ {
+ $stigClassMethodNames = @('Equals', 'new', 'ReferenceEquals', 'SplitCheckContent',
+ 'GetRuleTypeMatchList', 'GetFixText')
+ }
+ else
+ {
+ $objectClassMethodNames = @('Equals', 'GetHashCode', 'GetType', 'ToString')
+ $stigClassMethodNames = @('Clone', 'IsDuplicateRule', 'SetDuplicateTitle', , 'SetStatus',
+ 'SetIsNullOrEmpty', 'SetOrganizationValueRequired', 'GetOrganizationValueTestString',
+ 'ConvertToHashTable', 'SetStigRuleResource', 'IsHardCoded', 'GetHardCodedString',
+ 'IsHardCodedOrganizationValueTestString', 'GetHardCodedOrganizationValueTestString',
+ 'IsExistingRule')
+
+ $stigClassMethodNames += $ObjectClassMethodNames
+ }
+
+ if ( $ChildClassMethodNames )
+ {
+ $stigClassMethodNames += $ChildClassMethodNames
+ }
+
+ return ( $stigClassMethodNames | Select-Object -Unique )
+}
+
+function Format-RuleText
+{
+ [CmdletBinding()]
+ Param
+ (
+ [Parameter(Mandatory = $true)]
+ [AllowEmptyString()]
+ [string[]]
+ $RuleText
+ )
+
+ $return = ($RuleText -split '\n' |
+ Select-String -Pattern "\w" |
+ ForEach-Object { $PSitem.ToString().Trim() })
+
+ return $return
+}
+
+
+function Get-RequiredStigDataVersion
+{
+ [CmdletBinding()]
+ param()
+
+ $Manifest = Import-PowerShellDataFile -Path "$relDirectory\$moduleName.psd1"
+
+ return $Manifest.RequiredModules.Where({$PSItem.ModuleName -eq 'PowerStig'}).ModuleVersion
+}
+
+function Get-StigDataRootPath
+{
+ param ( )
+
+ $projectRoot = Split-Path -Path (Split-Path -Path $PsScriptRoot)
+ return Join-Path -Path $projectRoot -Child 'StigData'
+}
+
+<#
+ .SYNOPSIS
+ Get all of the version files to test
+
+ .PARAMETER CompositeResourceName
+ The name of the composite resource used to filter the results
+#>
+function Get-StigFileList
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $CompositeResourceName
+ )
+
+ #
+ $stigFilePath = Get-StigDataRootPath
+ $stigVersionFiles = Get-ChildItem -Path $stigFilePath -Exclude "*.org*"
+
+ $stigVersionFiles
+}
+
+<#
+ .SYNOPSIS
+ Returns a list of stigs for a given resource. This is used in integration testign by looping
+ through every valide STIG found in the StigData directory.
+
+ .PARAMETER CompositeResourceName
+ The resource to filter the results
+
+ .PARAMETER Filter
+ Parameter description
+
+#>
+function Get-StigVersionTable
+{
+ [OutputType([psobject])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $CompositeResourceName,
+
+ [Parameter()]
+ [string]
+ $Filter
+ )
+
+ $include = Import-PowerShellDataFile -Path $PSScriptRoot\CompositeResourceFilter.psd1
+
+ $path = "$(Get-StigDataRootPath)\Processed"
+
+ $versions = Get-ChildItem -Path $path -Exclude "*.org.*", "*.xsd" -Include $include.$CompositeResourceName -File -Recurse
+
+ $versionTable = @()
+ foreach ($version in $versions)
+ {
+ if ($version.Basename -match $Filter)
+ {
+ $stigDetails = $version.BaseName -Split "-"
+
+ $versionTable += @{
+ 'Technology' = $stigDetails[0]
+ 'TechnologyVersion' = $stigDetails[1]
+ 'TechnologyRole' = $stigDetails[2]
+ 'StigVersion' = $stigDetails[3]
+ 'Path' = $version.fullname
+ }
+ }
+ }
+
+ return $versionTable
+}
+
+<#
+ .SYNOPSIS
+ Using an AST, it returns the name of a configuration in the composite resource schema file.
+
+ .PARAMETER FilePath
+ The full path to the resource schema module file
+#>
+function Get-ConfigurationName
+{
+ [CmdletBinding()]
+ [OutputType([string[]])]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [String]
+ $FilePath
+ )
+
+ $AST = [System.Management.Automation.Language.Parser]::ParseFile(
+ $FilePath, [ref] $null, [ref] $Null
+ )
+
+ # Get the Export-ModuleMember details from the module file
+ $ModuleMember = $AST.Find( {
+ $args[0] -is [System.Management.Automation.Language.ConfigurationDefinitionAst]}, $true)
+
+ return $ModuleMember.InstanceName.Value
+}
+
+<#
+ .SYNOPSIS
+ Returns the list of StigVersion nunmbers that are defined in the ValidateSet parameter attribute
+
+ .PARAMETER FilePath
+ THe full path to the resource to read from
+#>
+function Get-StigVersionParameterValidateSet
+{
+ [OutputType([string])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $FilePath
+ )
+
+ $compositeResource = Get-Content -Path $FilePath -Raw
+
+ $AbstractSyntaxTree = [System.Management.Automation.Language.Parser]::ParseInput(
+ $compositeResource, [ref]$null, [ref]$null)
+
+ $params = $AbstractSyntaxTree.FindAll(
+ {$args[0] -is [System.Management.Automation.Language.ParameterAst]}, $true)
+
+ # Filter the specifc ParameterAst
+ $paramToUpdate = $params |
+ Where-Object {$PSItem.Name.VariablePath.UserPath -eq 'StigVersion'}
+
+ # Get the specifc parameter attribute to update
+ $validate = $paramToUpdate.Attributes.Where(
+ {$PSItem.TypeName.Name -eq 'ValidateSet'})
+
+ return $validate.PositionalArguments.Value
+}
+
+<#
+ .SYNOPSIS
+ Get a unique list of valid STIG versions from the StigData
+
+ .PARAMETER TechnologyRoleFilter
+ The technology role to filter the results
+#>
+
+function Get-ValidStigVersionNumbers
+{
+ [OutputType([string])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $TechnologyRoleFilter
+ )
+
+ $versionNumbers = (Get-Stiglist |
+ Where-Object {$PSItem.TechnologyRole -match $TechnologyRoleFilter} |
+ Select-Object StigVersion -ExpandProperty StigVersion -Unique )
+
+ return $versionNumbers
+}
+
+Export-ModuleMember -Function @(
+ 'Split-TestStrings',
+ 'Get-StigDataRootPath',
+ 'Test-Xml',
+ 'Get-TestStigRule',
+ 'Get-StigBaseMethods',
+ 'Format-RuleText',
+ 'Get-PowerStigVersionFromManifest',
+ 'Get-StigVersionTable',
+ 'Get-ConfigurationName',
+ 'Get-StigVersionParameterValidateSet',
+ 'Get-ValidStigVersionNumbers'
+)
diff --git a/Tools/WikiPages/WikiPages.psm1 b/Tools/WikiPages/WikiPages.psm1
new file mode 100644
index 000000000..fc99b1ffe
--- /dev/null
+++ b/Tools/WikiPages/WikiPages.psm1
@@ -0,0 +1,626 @@
+#region Strings
+
+$header = @'
+# {0} Class
+
+{1}
+'@
+
+$advancedHeader = @'
+# {0} Syntax
+'@
+
+$constructorHeader = @'
+
+## Constructors
+
+| Name | Description |
+|-|-|
+'@
+
+$advancedconstructorHeader = @'
+
+## Constructors
+
+'@
+
+$advancedConstructor = @'
+{0}
+
+```PowerShell
+{1}
+```
+
+### Parameters
+
+| Name | Type | Description |
+|-|-|-|
+{2}
+
+'@
+
+$propertiesHeader = @'
+
+## Properties
+
+| Name | Description |
+|-|-|
+'@
+
+$advancedPropertyHeader = @'
+## {0}
+
+```PowerShell
+{1}
+```
+
+'@
+
+$methodsHeader = @'
+
+## Methods
+
+| Name | Description |
+|-|-|
+'@
+
+$advancedMethod = @'
+{0}
+
+```PowerShell
+{1}
+```
+
+### Parameters
+
+{2}
+
+'@
+
+$examplesHeader = @'
+
+## Example
+'@
+
+$powershellCodeSnip = @'
+
+'@
+
+#endregion
+
+function Get-WikiContent
+{
+ [OutputType([hashtable])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Path
+ )
+
+ $input = Get-Content -Path $Path -Raw
+
+ [System.Management.Automation.Language.Token[]] $tokens = $null
+
+ $ast = [System.Management.Automation.Language.Parser]::ParseInput(
+ $input, [ref]$tokens, [ref]$null)
+
+ $return = [System.Collections.Specialized.OrderedDictionary]@{}
+ <#
+ 1. Get the Type Definition and Build a hashtable for the
+ a. Class
+ b. Constructors
+ c. Properties
+ d. Methods
+
+ 2. Modify the Class definition in memory to replace key words with the function key word
+ 3. Parse the updated script file
+ 4. Search for FunctionDefinitionAst to get access to GetHelpContent
+ 5. Update each item in the hashtable from step one with it's help content.
+ #>
+
+ $TypeDefinitionAst = $ast.FindAll(
+ {$args[0] -is [System.Management.Automation.Language.TypeDefinitionAst ]}, $true)
+
+ $return.Add(
+ "Class.$($TypeDefinitionAst.Extent.StartLineNumber)",
+ @{
+ Name = $TypeDefinitionAst.Name
+ }
+ )
+
+ #Properties
+ foreach ($entry in $TypeDefinitionAst.Members.Where( {$PSItem.PropertyType -ne $null}))
+ {
+ $details = @{
+ Name = $entry.Name
+ LineNumber = $entry.Extent.StartLineNumber
+ PropertyType = $entry.PropertyType.TypeName.FullName
+ PropertyAttributes = $entry.PropertyAttributes
+ Link = "[Property.$($entry.Name)]: Stig.OrganizationalSettings.Class.Syntax#property$($entry.Name.ToLower())"
+ }
+ $return.Add("Property.$($entry.Extent.StartLineNumber)", $details)
+ }
+
+ # Constructors
+ foreach ($entry in $TypeDefinitionAst.Members.Where( {$PSItem.Name -eq $TypeDefinitionAst.Name}))
+ {
+ $details = @{
+ Name = $entry.Name
+ LineNumber = $entry.Extent.StartLineNumber
+ MethodAttributes = $entry.MethodAttributes
+ Parameters = $entry.Parameters
+ }
+
+ if ($details.Parameters.Count -gt 0)
+ {
+ $parameterType = $entry.Parameters.Attributes.TypeName.Name -join "."
+ $linkpath = "Constructor.$parameterType"
+ }
+ else
+ {
+ $linkpath = "Constructor"
+ }
+
+ $details.Add('Link', "[$linkpath]: Stig.$($TypeDefinitionAst.Name).Class.Syntax#$($linkpath.ToLower() -replace "\.",'')")
+ $return.Add("Constructor.$($entry.Extent.StartLineNumber)", $details)
+ }
+
+ #Methods
+ foreach ($entry in $TypeDefinitionAst.Members.Where( {$PSItem.Name -ne $TypeDefinitionAst.Name -and
+ $PSItem.MethodAttributes -ne $null}))
+ {
+ $details = @{
+ Name = $entry.Name
+ LineNumber = $entry.Extent.StartLineNumber
+ PropertyType = $entry.PropertyType.TypeName.FullName
+ MethodAttributes = $entry.MethodAttributes
+ ReturnType = $entry.ReturnType.TypeName.FullName
+ Parameters = $entry.Parameters
+ }
+
+ if ($details.Parameters.Count -gt 0)
+ {
+ $parameterType = $entry.Parameters.Attributes.TypeName.Name
+ $linkpath = "Method.$($entry.Name).$parameterType"
+ }
+ else
+ {
+ $linkpath = "Method.$($entry.Name)"
+ }
+ $details.Add('Link', "[$linkpath]: Stig.OrganizationalSettings.Class.Syntax#$($linkpath.ToLower() -replace "\.",'')")
+ $return.Add("Method.$($entry.Extent.StartLineNumber)", $details)
+ }
+
+ # Update the content with the function keyword to extract help content
+ $classAndConstructorFilter = "^(Class)?\s+$($TypeDefinitionAst.Name)(\s+:\s+\w+)?"
+ $methodFilter = '^\s*(static)?\s*\[\w*(\[(\s*)?\])?\]\s*'
+
+ $inputModified = (($input -split "`n") -replace $classAndConstructorFilter, "function $($TypeDefinitionAst.Name)")
+ <#
+ I couldn't come up with a regex that updated the methods without also
+ stepping on the class properties, so I just apply the regex against directly
+ to the method line number.
+ #>
+ foreach ($method in $return.Keys.Where( {$PSItem -match "^Method\."}))
+ {
+ $lineNumber = $return.$method.LineNumber - 1
+ $inputModified[$lineNumber] = $inputModified[$lineNumber] -replace $methodFilter, "function "
+ }
+
+ $astModified = [System.Management.Automation.Language.Parser]::ParseInput(
+ $inputModified, [ref]$tokens, [ref]$null)
+
+ $FunctionDefinitionAst = $astModified.FindAll(
+ {$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $true)
+
+ Foreach ($Function in $FunctionDefinitionAst)
+ {
+ $keyName = ($return.Keys.Where( {$PSitem -match ".\.$($Function.Extent.StartLineNumber)"}))[0]
+
+ [void] $return[$keyName].Add('Help', $Function.GetHelpContent())
+ }
+
+ return $return
+}
+
+<#
+ .SYNOPSIS
+ The help content is returned as typed, so this function will merge everything
+ back into individual sentences and whole lines.
+#>
+function Format-HelpString
+{
+ [OutputType([string])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [AllowEmptyString()]
+ [string]
+ $String,
+
+ [Parameter()]
+ [switch]
+ $SingleLine
+ )
+
+ $returnString = $String -split "`n" -join " "
+
+ if ($SingleLine)
+ {
+ $returnString = $returnString -replace "\.\s+", ". "
+ }
+ else
+ {
+ $returnString = $returnString -replace "\.\s+", ".`n"
+ }
+
+ return $returnString.TrimEnd()
+}
+
+function New-WikiPage
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [string]
+ $Path,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $OutputPath
+ )
+
+ begin
+ {
+ }
+
+ process
+ {
+ $wikiContent = Get-WikiContent -Path $Path
+
+ $helpFileStrings = [System.Collections.ArrayList]@()
+ $advancedHelpFileStrings = [System.Collections.ArrayList]@()
+ $linkTable = [System.Collections.ArrayList]@('')
+
+ #region Header
+
+ $classKey = ($wikiContent.Keys.Where( {$PSitem -match 'Class\.'}))[0]
+ #$classDefinition = $TypeDefinitionAstForHelp.Where({$PSItem.Name -eq $TypeDefinitionAst.Name})
+ #$helpString = Get-HelpString -FunctionDefinitionAst $classDefinition -Property DESCRIPTION
+
+ $class = $wikiContent[$classKey]
+ $description = Format-HelpString -String $class.Help.Description
+ $null = $helpFileStrings.Add(($header -f $class.Name, $description))
+ $null = $advancedHelpFileStrings.Add($advancedHeader -f $class.Name)
+ #endregion
+
+ #region Constructors
+ $constructorKeyList = $wikiContent.Keys.Where( {$PSitem -match 'Constructor\.'})
+ $null = $helpFileStrings.Add($constructorHeader)
+ $null = $advancedHelpFileStrings.Add($advancedconstructorHeader)
+
+ foreach ($constructorKey in $constructorKeyList.GetEnumerator())
+ {
+ $constructor = $wikiContent[$constructorKey]
+
+ $link = ($constructor.Link -split "\:")[0]
+ $synopsis = Format-HelpString -String $constructor.Help.Synopsis -SingleLine
+ $parameterString = $constructor.Parameters.Attributes.TypeName.Name -join ","
+ $null = $helpFileStrings.Add("| [$($constructor.name)($parameterString)]$link | $synopsis |")
+ $null = $linkTable.Add($constructor.Link)
+ # Advanced syntax
+ $description = Format-HelpString -String $constructor.Help.Description -SingleLine
+ $constructorSyntax = "$($constructor.MethodAttributes) $($constructor.name)($($constructor.Parameters -Join ', '))"
+
+ $parameterList = $constructor.Parameters
+ $parameterhelp = $constructor.Help.Parameters
+
+ $parameterHelpString = @()
+ foreach ($parameter in $parameterList.GetEnumerator())
+ {
+ $parameterName = $parameter.Name.VariablePath.UserPath
+ $parameterType = $parameter.StaticType.FullName
+ $parameterDescription = Format-HelpString -String $parameterhelp.$($parameterName.ToUpper()) -SingleLine
+
+ $parameterHelpString += "| $parameterName | $parameterType | $parameterDescription |"
+ }
+ $parameterHelpString = $parameterHelpString | Out-String
+ $null = $advancedHelpFileStrings.Add(
+ ($advancedConstructor -f $description, $constructorSyntax, $parameterHelpString)
+ )
+ }
+
+ #endregion
+
+ #region Properties
+ $propertyKeyList = $wikiContent.Keys.Where( {$PSitem -match 'Property\.'})
+ [void]$helpFileStrings.Add($propertiesHeader)
+
+ Foreach ($propertyKey in $propertyKeyList.GetEnumerator())
+ {
+ $property = $wikiContent[$propertyKey]
+
+ $link = ($property.Link -split "\:")[0]
+ $helpString = $class.Help.Parameters[$property.Name.ToUpper()]
+ $synopsis = Format-HelpString -String $helpString -SingleLine
+ $propertyEntry = "| {0} | {1} |" -f "[$($property.Name)]$link", $synopsis
+ $null = $helpFileStrings.Add($propertyEntry)
+ $null = $linkTable.Add($property.Link)
+
+
+ $advancedPropertTitle = $link -replace '\[|\]', ''
+ $advancedPropertySyntax = "$($property.PropertyAttributes) $($property.PropertyType) {get; set;}"
+ $null = $advancedHelpFileStrings.Add(
+ ($advancedPropertyHeader -f $advancedPropertTitle,$advancedPropertySyntax)
+ )
+ }
+ #endregion
+
+ #region Methods
+ $methodKeyList = $wikiContent.Keys.Where( {$PSitem -match 'Method\.'})
+ [void]$helpFileStrings.Add($methodsHeader)
+
+ Foreach ($methodKey in $methodKeyList.GetEnumerator())
+ {
+ $method = $wikiContent[$methodKey]
+ $link = ($method.Link -split "\:")[0]
+ $synopsis = Format-HelpString -String $method.Help.Synopsis -SingleLine
+
+ $parameterString = $method.Parameters.Attributes.TypeName.Name -join ","
+ $methodEntry = "| [$($method.name)($parameterString)]$link | $synopsis |"
+ [void] $helpFileStrings.Add($methodEntry)
+ [void] $linkTable.Add($method.Link)
+
+
+ '| Name | Type | Description |'
+ '|-|-|-|'
+ }
+ #endregion
+ <#
+ #region Examples
+
+ [void]$helpFileStrings.Add($examplesHeader)
+
+ foreach($example in $examples)
+ {
+ "```PowerShell`n$example`n``` "
+ }
+ #endregion
+
+ #>
+
+
+ [void]$helpFileStrings.Add($linkTable)
+
+ $file = Get-Item -Path $path
+
+ $helpFileStrings | Out-File -FilePath $OutputPath\"$($file.BaseName).Class.md"
+ $advancedHelpFileStrings | Out-File -FilePath $OutputPath\"$($file.BaseName).Class.Syntax.md"
+ }
+
+ end
+ {
+ }
+}
+
+function Get-DscCompositeWikiContent
+{
+ [OutputType([hashtable])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Path
+ )
+
+ $input = Get-Content -Path $Path -Raw
+
+ [System.Management.Automation.Language.Token[]] $tokens = $null
+
+ $ast = [System.Management.Automation.Language.Parser]::ParseInput(
+ $input, [ref]$tokens, [ref]$null)
+
+
+}
+
+function ConvertTo-Function
+{
+ [OutputType([System.Management.Automation.Language.FunctionDefinitionAst])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $Path
+ )
+ $configurationContent = Get-Content -Path $Path -Raw
+
+ # Update the content with the function keyword to extract help content
+ $ConfigurationKeyword = "^Configuration(?=\s+\w+)"
+ $inputModified = (($configurationContent -split "`n") -replace $ConfigurationKeyword, "function")
+
+
+ [System.Management.Automation.Language.Token[]] $tokens = $null
+ $astModified = [System.Management.Automation.Language.Parser]::ParseInput(
+ $inputModified, [ref]$tokens, [ref]$null)
+
+ $FunctionDefinitionAst = $astModified.FindAll(
+ {$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]}, $true)
+
+ return $FunctionDefinitionAst
+}
+
+function Get-ConfigurationParameters
+{
+ [OutputType([hashtable[]])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [System.Management.Automation.Language.FunctionDefinitionAst]
+ $FunctionDefinitionAst
+ )
+
+ $help = $FunctionDefinitionAst.GetHelpContent()
+ $parameterList = [System.Collections.ArrayList]@()
+ foreach ($parameter in $FunctionDefinitionAst.body.ParamBlock.Parameters)
+ {
+ $parameterName = $parameter.Name.VariablePath.UserPath
+
+ $parametermandatory = $false
+ $parameterAttribute = $parameter.Attributes.Where( {$PSItem.TypeName.Name -eq 'Parameter'})
+ If ($parameterAttribute.NamedArguments.Where( {$PSItem.ArgumentName -eq 'Mandatory'}).Argument.VariablePath.UserPath -eq $true)
+ {
+ $parametermandatory = $true
+ }
+
+ $parameterObject = @{
+ Name = $parameterName
+ DataType = $parameter.StaticType.Name
+ Attribute = $parametermandatory
+ }
+
+ $parameterSetAttribute = $parameter.Attributes.Where( {$PSItem.TypeName.Name -eq 'ValidateSet'})
+ if ($null -ne $parameterSetAttribute)
+ {
+ $null = $parameterObject.Add(
+ 'AllowedValues', $parameterSetAttribute.PositionalArguments.Value -join ','
+ )
+ }
+
+ $null = $parameterObject.Add(
+ 'Description', (Format-HelpString -String $help.Parameters.($parameterName.ToUpper()) -SingleLine )
+ )
+ $null = $parameterList.Add($parameterObject)
+ }
+ return $parameterList
+}
+
+function Get-ConfigurationExamples
+{
+ [OutputType([hashtable[]])]
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true)]
+ [string]
+ $ConfigurationName,
+
+ [Parameter()]
+ [string]
+ $Path
+ )
+
+ $basePath = 'https://github.com/Microsoft/PowerStigDsc/tree/master/Examples'
+ $examplelist = [System.Collections.ArrayList]@()
+
+ $filter = "Sample_$ConfigurationName*.ps1"
+ $rootPath = ($Path -split 'DscResources')[0]
+ $exampleFileList = Get-ChildItem -Path $rootPath -Recurse -Filter $filter
+
+ foreach ($exampleFile in $exampleFileList)
+ {
+ $configurationContent = ConvertTo-Function -Path $exampleFile.FullName
+
+ $exampleObject = @{
+ Text = (Format-HelpString -String $configurationContent.GetHelpContent().Synopsis -SingleLine)
+ Link = "$basePath/$($exampleFile.Name)"
+ }
+ $null = $examplelist.Add($exampleObject)
+ }
+ return $exampleList
+}
+
+<#
+ .SYNOPSIS
+ Creates a DSC Composite wiki page
+#>
+function New-DscCompositeWikiPage
+{
+ [CmdletBinding()]
+ param
+ (
+ [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
+ [string]
+ $Path,
+
+ [Parameter(Mandatory = $true)]
+ [string]
+ $OutputPath
+ )
+
+ process
+ {
+ $wikiStrings = [System.Collections.ArrayList]@()
+
+ $configurationContent = Get-Content -Path $Path -Raw
+
+ [System.Management.Automation.Language.Token[]] $tokens = $null
+
+ $ast = [System.Management.Automation.Language.Parser]::ParseInput(
+ $configurationContent, [ref]$tokens, [ref]$null)
+
+ $configuration = $ast.FindAll(
+ {$args[0] -is [System.Management.Automation.Language.ConfigurationDefinitionAst]}, $true)
+
+ # ConfigurationName
+
+ $null = $wikiStrings.Add("# $($configuration.InstanceName.Value)")
+ $null = $wikiStrings.Add("")
+
+ # Configuration Synopsis as the header text TO DO
+ $configurationDetails = ConvertTo-Function -Path $Path
+ $help = $configurationDetails.GetHelpContent()
+ $null = $wikiStrings.Add("$(Format-HelpString -String $help.Synopsis)")
+
+ $null = $wikiStrings.Add("")
+ $null = $wikiStrings.Add("## Requirements")
+ $null = $wikiStrings.Add("")
+
+ $requirements = # Get the list of DSC resources and using statements defined in the composite
+ if ($requirements)
+ {
+ # | Resource Name | Resource Version |
+ }
+ else
+ {
+ $null = $wikiStrings.Add("None")
+ }
+
+ $null = $wikiStrings.Add("")
+ $null = $wikiStrings.Add("## Parameters")
+ $null = $wikiStrings.Add("")
+ $null = $wikiStrings.Add("| Parameter | Attribute | DataType | Description | Allowed Values |")
+ $null = $wikiStrings.Add("| --------- | --------- | -------- | ----------- | -------------- |")
+
+ $parameterList = Get-ConfigurationParameters -FunctionDefinitionAst $configurationDetails
+ foreach ($parameter in $parameterList)
+ {
+ $null = $wikiStrings.Add("| $($parameter.Name) | $($parameter.Attribute) | $($parameter.DataType) | $($parameter.Description) | $($parameter.AllowedValues) |")
+ }
+
+ $null = $wikiStrings.Add("")
+ $null = $wikiStrings.Add("## Examples")
+ $null = $wikiStrings.Add("")
+
+ $exampleList = Get-ConfigurationExamples -Configurationname $configuration.InstanceName.Value -Path $Path
+ foreach ($example in $exampleList)
+ {
+ $null = $wikiStrings.Add("* [$($example.Text)]($($example.Link))")
+ }
+
+ $wikiStrings | Out-File -FilePath "$OutputPath\$($configuration.InstanceName.Value).md"
+ }
+}
+
+function New-WikiPageAdvanced
+{
+
+}
+
+Export-ModuleMember -Function 'New-WikiPage', 'New-DscCompositeWikiPage'
diff --git a/appveyor.yml b/appveyor.yml
index be59ca004..931b6e73b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -25,8 +25,7 @@ install:
ForEach-Object { Install-Module $PSItem.moduleName -RequiredVersion $PSItem.ModuleVersion -Repository PSGallery -Scope CurrentUser -Force }
- git clone https://github.com/PowerShell/DscResource.Tests
- ps: Import-Module "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1"
- - git clone https://github.com/Microsoft/PowerStig.Tests
- - ps: Import-Module "$env:APPVEYOR_BUILD_FOLDER\PowerStig.Tests\PowerStig.Tests.psd1"
+ - ps: Import-Module "$env:APPVEYOR_BUILD_FOLDER\Tools\AppVeyor\AppVeyor.psm1"
- ps: Invoke-AppveyorInstallTask
#---------------------------------#
@@ -41,7 +40,7 @@ build: false
test_script:
- ps: |
- Invoke-AppveyorTestScriptTask -CodeCoverage -CodeCovIo -ExcludeTag @()
+ Invoke-AppveyorTestScriptTask -CodeCoverage -CodeCovIo -ExcludeTag @('tools')
#---------------------------------#
# deployment configuration #
@@ -49,7 +48,7 @@ test_script:
# scripts to run before deployment
before_deploy:
- - ps: PowerStig.Tests\Invoke-AppveyorAfterTestTask
+ - ps: Invoke-PowerStigAppveyorAfterTestTask
for:
-