Full Code of PoshCode/Configuration for AI

main d5f74a9ff405 cached
48 files
204.2 KB
50.5k tokens
1 requests
Download .txt
Showing preview only (217K chars total). Download the full file or copy to clipboard to get everything.
Repository: PoshCode/Configuration
Branch: main
Commit: d5f74a9ff405
Files: 48
Total size: 204.2 KB

Directory structure:
gitextract_4wdl1uu2/

├── .config/
│   └── dotnet-tools.json
├── .github/
│   └── workflows/
│       ├── Merge-Module.ps1
│       └── build.yml
├── .gitignore
├── .vscode/
│   ├── launch.json
│   ├── settings.json
│   ├── taskmarks.json
│   └── tasks.json
├── Benchmark/
│   ├── Benchmark.ps1
│   └── Data/
│       └── Configuration.psd1
├── Build.ps1
├── CHANGELOG.md
├── Examples/
│   ├── TestModuleOne/
│   │   ├── Configuration.psd1
│   │   ├── TestModuleOne.psd1
│   │   └── TestModuleOne.psm1
│   └── TestModuleTwo/
│       ├── Configuration.psd1
│       ├── TestModuleTwo.psd1
│       └── TestModuleTwo.psm1
├── GitVersion.yml
├── LICENSE
├── PSScriptAnalyzerSettings.psd1
├── README.md
├── ReBuild.ps1
├── RequiredModules.psd1
├── Source/
│   ├── Configuration.psd1
│   ├── Header/
│   │   └── param.ps1
│   ├── Private/
│   │   ├── InitializeStoragePaths.ps1
│   │   └── ParameterBinder.ps1
│   └── Public/
│       ├── Export-Configuration.ps1
│       ├── Get-ConfigurationPath.ps1
│       ├── Get-ParameterValue.ps1
│       ├── Import-Configuration.ps1
│       └── Import-ParameterConfiguration.ps1
├── Specs/
│   ├── Configuration.Steps.ps1
│   ├── Configuration.feature
│   ├── ConfiguredParameters.feature
│   ├── DefaultParameters.feature
│   ├── Layering.feature
│   ├── LocalStoragePath.feature
│   ├── LocalStoragePathLinux.feature
│   ├── Manifest.feature
│   ├── ScriptAnalyzer.Steps.ps1
│   ├── ScriptAnalyzer.feature
│   ├── Serialization.feature
│   └── TestVersion.feature
├── Test.ps1
├── bootstrap.ps1
└── build.psd1

================================================
FILE CONTENTS
================================================

================================================
FILE: .config/dotnet-tools.json
================================================
{
  "version": 1,
  "isRoot": true,
  "tools": {
    "gitversion.tool": {
      "version": "5.6.0",
      "commands": [
        "dotnet-gitversion"
      ]
    }
  }
}

================================================
FILE: .github/workflows/Merge-Module.ps1
================================================
#requires -Module Configuration
[CmdletBinding()]
param(
    $OutputModulePath,
    $NestedModulePath
)
$OutputModule = Get-Module $OutputModulePath -ListAvailable
$NestedModule = Get-Module $NestedModulePath -ListAvailable

# Copy and then remove the extra output
Copy-Item -Path (Join-Path $NestedModule.ModuleBase Metadata.psm1) -Destination $OutputModule.ModuleBase
Remove-Item $NestedModule.ModuleBase -Recurse

# Because this is a double-module, combine the exports of both modules
# Put the ExportedFunctions of both in the manifest
Update-Metadata -Path $OutputModule.Path -PropertyName FunctionsToExport `
                -Value @(
                    @(
                        $NestedModule.ExportedFunctions.Keys
                        $OutputModule.ExportedFunctions.Keys
                    ) | Select-Object -Unique
                    # @('*')
                )

# Put the ExportedAliases of both in the manifest
Update-Metadata -Path $OutputModule.Path -PropertyName AliasesToExport `
                -Value @(
                    @(
                        $NestedModule.ExportedAliases.Keys
                        $OutputModule.ExportedAliases.Keys
                    ) | Select-Object -Unique
                    # @('*')
                )

================================================
FILE: .github/workflows/build.yml
================================================
name: Build on push
on: [push]
jobs:
  build:
    runs-on: windows-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: GitVersion
        id: gitversion
        uses: PoshCode/Actions/gitversion@v1
      - name: Install-RequiredModules
        uses: PoshCode/Actions/install-requiredmodules@v1
      - name: Build Module
        id: build
        uses: PoshCode/actions/build-module@v1
        with:
          version: ${{ steps.gitversion.outputs.LegacySemVerPadded }}
          destination: ${{github.workspace}}/output
      - name: Upload Build Output
        uses: actions/upload-artifact@v2
        with:
          name: Modules
          path: ${{github.workspace}}/output
      - name: Upload Tests
        uses: actions/upload-artifact@v2
        with:
          name: PesterTests
          path: ${{github.workspace}}/Specs
      - name: Upload RequiredModules.psd1
        uses: actions/upload-artifact@v2
        with:
          name: RequiredModules
          path: ${{github.workspace}}/RequiredModules.psd1
      - name: Upload PSScriptAnalyzerSettings.psd1
        uses: actions/upload-artifact@v2
        with:
          name: ScriptAnalyzer
          path: ${{github.workspace}}/PSScriptAnalyzerSettings.psd1
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [windows-latest, ubuntu-latest, macos-latest]
    needs: build
    steps:
      - name: Download Build Output
        uses: actions/download-artifact@v2
      - uses: PoshCode/Actions/install-requiredmodules@v1
      - uses: PoshCode/Actions/pester@v1
        with:
          codeCoveragePath: Modules/Configuration
          moduleUnderTest: Configuration
          additionalModulePaths: ${{github.workspace}}/Modules
      - name: Upload Results
        uses: actions/upload-artifact@v2
        with:
          name: Pester Results
          path: ${{github.workspace}}/*.xml

================================================
FILE: .gitignore
================================================
/output/
/[0-9]*/

/.vs/
RequiredModules/
node_modules/

results.xml
coverage.xml

================================================
FILE: .vscode/launch.json
================================================
{
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "PowerShell",
            "request": "launch",
            "name": "Gherkin All Tests w/Code Coverage",
            "preLaunchTask": "build",
            "script": "Invoke-Gherkin",
            "args": [
                "-PesterOption @{ IncludeVSCodeMarker = $True }",
                "-CodeCoverage Output\\*.psm1"
            ],
            "cwd": "${workspaceFolder}",
            "createTemporaryIntegratedConsole": true
        },
        {
            "type": "PowerShell",
            "request": "launch",
            "name": "Gherkin Current Test File w/Args Prompt",
            "preLaunchTask": "build",
            "script": "$env:PSModulePath = '${workspaceFolder}\\Output;${env:PSModulePath};Import-Module Pester; Invoke-Gherkin -Path '${file}'",
            "args": [
                "-PesterOption @{ IncludeVSCodeMarker = $True }",
                "${command:SpecifyScriptArgs}"
            ],
            "cwd": "${workspaceFolder}",
            "createTemporaryIntegratedConsole": true
        },
        {
            "type": "PowerShell",
            "request": "launch",
            "name": "PowerShell Interactive Session",
            "cwd": "${workspaceFolder}",
            "createTemporaryIntegratedConsole": true
        },
    ]
}

================================================
FILE: .vscode/settings.json
================================================
{
    "files.defaultLanguage": "powershell",
    "powershell.codeFormatting.preset": "OTBS",
    "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationForFirstPipeline",
    "powershell.codeFormatting.useCorrectCasing": true,
    "powershell.scriptAnalysis.enable": true,
    "powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1"
}

================================================
FILE: .vscode/taskmarks.json
================================================
{
	"activeTaskName": "default",
	"tasks": [
		{
			"name": "default",
			"files": []
		}
	]
}

================================================
FILE: .vscode/tasks.json
================================================
{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "type": "shell",
            "command": "${workspaceFolder}\\build.ps1",
            "args": [
                "-OutputDirectory", "${workspaceFolder}"
            ],
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": true,
                "clear": false
            }
        },
        {
            "label": "analyze",
            "group": "test",
            "type": "shell",
            "command": "Invoke-ScriptAnalyzer",
            "args": [
                "-Path", "(Get-Module Configuration -List | Sort Version -Desc | Select -First 1 -Expand ModuleBase)"
            ],
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared",
                "showReuseMessage": true,
                "clear": false
            }
        },
        {
            "label": "test",
            "group": {
                "kind": "test",
                "isDefault": true
            },
            "type": "shell",
            "options": {
                "cwd": "${workspaceFolder}",

            },
            "command": "Invoke-Gherkin",
            "args": [
                "-Path", "${workspaceFolder}\\Specs",
                "-PesterOption", "@{ IncludeVSCodeMarker = $True }",
                "-CodeCoverage", "(Convert-Path (Join-Path (Split-Path (Get-Module Configuration -List | Sort Version -Desc | Select -First 1 -Expand ModuleBase)) *.psm1))"
            ],
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": true,
                "panel": "shared",
                "showReuseMessage": true,
                "clear": true
            }
        }
    ]
}

================================================
FILE: Benchmark/Benchmark.ps1
================================================
[CmdletBinding()]
param([int]$Count = 10)
$dataPath = Join-Path $PSScriptRoot "../Benchmark/Data/Configuration.psd1"

foreach ($Version in Get-Module Configuration -ListAvailable | Sort-Object Version) {
    Remove-Module Configuration -Force
    Import-Module $Version.Path -Force
    $Configuration = Get-Module Configuration
    Write-Host "${fg:white}$($Configuration.Name) v$($Configuration.Version)$(if(($pre=$Configuration.PrivateData.PSData.PreRelease)) {"-$pre"})"

    $ToTime = [System.Collections.Generic.List[timespan]]::new()
    $FromTime = [System.Collections.Generic.List[timespan]]::new()

    $Timer = [System.Diagnostics.Stopwatch]::new()
    for ($i=0;$i -lt $Count; $i++) {
        $Timer.Restart()
        $inputObject = ConvertFrom-Metadata -InputObject (Get-Content -Raw $dataPath)
        $FromTime.Add($Timer.Elapsed)
        $outputObject = ConvertTo-Metadata -InputObject $inputObject
        $ToTime.Add($Timer.Elapsed)
    }
    $From = $FromTime | Measure-Object TotalMilliseconds -Sum -Average -Maximum -Minimum
    $To = $ToTime | Measure-Object TotalMilliseconds -Sum -Average -Maximum -Minimum

    Write-Host "  ${fg:grey}ConvertTo-Metadata completed in ${fg:Cyan}$($To.Average) milliseconds${fg:grey} on average. Max $($To.Maximum) to $($To.Minimum) minimum."
    Write-Host "  ${fg:grey}ConvertFrom-Metadata completed in ${fg:Cyan}$($From.Average) milliseconds${fg:grey} on average. Max $($From.Maximum) to $($From.Minimum) minimum."
}

================================================
FILE: Benchmark/Data/Configuration.psd1
================================================

@{
  CurrentColorTheme = 'devblackops'
  CurrentIconTheme = 'devblackops'
  Themes = @{
    Color = @{
      devblackops = @{
        Name = 'devblackops'
        Types = @{
          Directories = @{
            '' = ''
            WellKnown = @{
              docs = '00BFFF'
              '.github' = 'C0C0C0'
              media = 'D3D3D3'
              tests = '87CEEB'
              '.git' = 'FF4500'
              '.vscode' = '87CEFA'
              src = '00FF7F'
              images = '9ACD32'
              fonts = 'DC143C'
            }
          }
          Files = @{
            '.lua' = '87CEFA'
            '.mpv' = 'FFA500'
            '.mp4' = 'FFA500'
            '.asc' = '66CDAA'
            '.apx' = '20B2AA'
            '.xml' = '98FB98'
            '.vsix' = '6495ED'
            '.mpg' = 'FFA500'
            '.eps' = '20B2AA'
            '.bmap' = 'DC143C'
            '.pub' = '66CDAA'
            '.pl' = '8A2BE2'
            '.raw' = '20B2AA'
            '.ini' = '6495ED'
            WellKnown = @{
              'firebase.json' = 'FFA500'
              '.clang-tidy' = '87CEEB'
              'code_of_conduct.txt' = 'FFFFE0'
              'docker-compose.yml' = '4682B4'
              Dockerfile = '4682B4'
              'vue.config.js' = '778899'
              '.gitlab-ci.yml' = 'FF4500'
              'docker-compose.yaml' = '4682B4'
              'CHANGELOG.md' = '98FB98'
              'docker-compose.dev.yml' = '4682B4'
              '.bowerrc' = 'CD5C5C'
              '.gitkeep' = 'FF4500'
              '.gitattributes' = 'FF4500'
              '.nmpignore' = '00BFFF'
              LICENSE = 'CD5C5C'
              '.gitignore' = 'FF4500'
              '.htaccess' = '9ACD32'
              '.esmrc' = '6B8E23'
              '.esformatter' = 'F4A460'
              'LICENSE.txt' = 'CD5C5C'
              'package-lock.json' = '6B8E23'
              '.tsbuildinfo' = 'F4A460'
              'docker-compose.staging.yml' = '4682B4'
              '.nvmrc' = '6B8E23'
              CHANGELOG = '98FB98'
              'bitbucket-pipelines.yaml' = '87CEFA'
              'authors.md' = 'FF6347'
              '.DS_Store' = '696969'
              'README.md' = '00FFFF'
              'cdp.pid' = 'F4A460'
              'composer.lock' = 'F4A460'
              '.jsbeautifyrc' = 'F4A460'
              'gulpfile.babel.js' = 'CD5C5C'
              'docker-compose.test.yml' = '4682B4'
              authors = 'FF6347'
              '.azure-pipelines.yml' = '00BFFF'
              'git-history' = 'FF4500'
              'docker-compose.production.yml' = '4682B4'
              '.travis.yml' = 'FFE4B5'
              '.mrconfig' = '87CEEB'
              README = '00FFFF'
              '.jscsrc' = 'F4A460'
              'manifest.mf' = '87CEEB'
              'package.json' = '6B8E23'
              'docker-compose.ci.yml' = '4682B4'
              'tslint.json' = 'F4A460'
              '.jshintrc' = 'F4A460'
              'code_of_conduct.md' = 'FFFFE0'
              'authors.txt' = 'FF6347'
              'tsconfig.json' = 'F4A460'
              'LICENSE.md' = 'CD5C5C'
              '.buildignore' = '87CEEB'
              'favicon.ico' = 'FFD700'
              'CHANGELOG.txt' = '98FB98'
              '.gitmodules' = 'FF4500'
              '.jenkinsfile' = '6495ED'
              '.jshintignore' = '87CEEB'
              'gulpfile.js' = 'CD5C5C'
              'docker-compose.local.yml' = '4682B4'
              'bower.json' = 'CD5C5C'
              '.npmrc' = '00BFFF'
              '.clang-format' = '87CEEB'
              'vue.config.ts' = '778899'
              '.firebaserc' = 'FFA500'
              'docker-compose.override.yml' = '4682B4'
              'README.txt' = '00FFFF'
              'docker-compose.prod.yml' = '4682B4'
              '.yardopts' = '87CEEB'
              'gulpfile.ts' = 'CD5C5C'
              'bitbucket-pipelines.yml' = '87CEFA'
            }
            '.ps1' = '00BFFF'
            '.dockerfile' = '4682B4'
            '.suit' = 'DC143C'
            '.fsi' = '00BFFF'
            '.pem' = '66CDAA'
            '.br' = 'DAA520'
            '.cpp' = 'A9A9A9'
            '.wmv' = 'FFA500'
            '.properties' = '6495ED'
            '.markdown' = '00BFFF'
            '.sql' = 'FFD700'
            '.dds' = '20B2AA'
            '.cs' = '7B68EE'
            '.aiff' = 'DB7093'
            '.ps1xml' = '00BFFF'
            '.bat' = '008000'
            '.vcxitems.filters' = 'EE82EE'
            '.vsxproj.filters' = 'EE82EE'
            '.cert' = 'FF6347'
            '.vob' = 'FFA500'
            '.sqlite' = 'FFD700'
            '.woff' = 'DC143C'
            '.ttc' = 'DC143C'
            '.exr' = '20B2AA'
            '.webp' = '20B2AA'
            '.manifest' = '98FB98'
            '.hs' = '9932CC'
            '.tif' = '20B2AA'
            '.go' = '20B2AA'
            '.json' = 'FFD700'
            '.potx' = 'DC143C'
            '.crt' = 'FF6347'
            '' = ''
            '.jpg' = '20B2AA'
            '.jb2' = '20B2AA'
            '.rs' = 'FF4500'
            '.yaml' = 'FF6347'
            '.yuv' = 'FFA500'
            '.dll' = '87CEEB'
            '.flv' = 'FFA500'
            '.mrg' = 'DC143C'
            '.zip' = 'DAA520'
            '.vue' = '20B2AA'
            '.conf' = '6495ED'
            '.sublime-project' = 'F4A460'
            '.pdb' = 'FFD700'
            '.jxr' = '20B2AA'
            '.rtf' = '00BFFF'
            '.xlsx' = '9ACD32'
            '.ico' = '20B2AA'
            '.js' = 'F0E68C'
            '.m2v' = 'FFA500'
            '.fpx' = '20B2AA'
            '.exe' = '00FA9A'
            '.pic' = '20B2AA'
            '.cur' = '20B2AA'
            '.cfg' = '6495ED'
            '.pkb' = 'FFD700'
            '.vscodeignore' = '6495ED'
            '.brotli' = 'DAA520'
            '.cer' = 'FF6347'
            '.applescript' = '4682B4'
            '.key' = '66CDAA'
            '.settings' = '6495ED'
            '.mov' = 'FFA500'
            '.toml' = '6495ED'
            '.tgz' = 'DAA520'
            '.ppam' = 'DC143C'
            '.ntf' = 'DC143C'
            '.rb' = 'FF0000'
            '.pgf' = '20B2AA'
            '.m4a' = 'DB7093'
            '.reg' = '6495ED'
            '.gemfile' = 'FF0000'
            '.vcxitems' = 'EE82EE'
            '.rar' = 'DAA520'
            '.xhtml' = 'CD5C5C'
            '.html' = 'CD5C5C'
            '.ppsx' = 'DC143C'
            '.csx' = '7B68EE'
            '.mjs' = 'F0E68C'
            '.psb' = '20B2AA'
            '.mkv' = 'FFA500'
            '.xquery' = '98FB98'
            '.tiff' = '20B2AA'
            '.sln' = 'EE82EE'
            '.less' = '6B8E23'
            '.mpeg' = 'FFA500'
            '.fonts' = 'DC143C'
            '.ttf' = 'DC143C'
            '.rst' = '00BFFF'
            '.font' = 'DC143C'
            '.ts' = 'F0E68C'
            '.php' = '6A5ACD'
            '.htm' = 'CD5C5C'
            '.psd' = '20B2AA'
            '.7z' = 'DAA520'
            '.brk' = '20B2AA'
            '.docx' = '00BFFF'
            '.sh' = 'FF4500'
            '.cljs' = '00FF7F'
            '.props' = '6495ED'
            svg = 'F4A460'
            '.log' = 'F0E68C'
            '.img' = '20B2AA'
            '.dart' = '4682B4'
            '.sui' = 'DC143C'
            '.option' = '6495ED'
            '.gbr' = '20B2AA'
            '.bz' = 'DAA520'
            '.avi' = 'FFA500'
            '.suo' = 'EE82EE'
            '.clixml' = '00BFFF'
            '.jsx' = '20B2AA'
            '.ics' = '00CED1'
            '.pdf' = 'CD5C5C'
            '.css' = '87CEFA'
            pssc = '00BFFF'
            '.pptx' = 'DC143C'
            '.bmp' = '20B2AA'
            '.dockerignore' = '4682B4'
            '.potm' = 'DC143C'
            '.txt' = '00CED1'
            '.xls' = '9ACD32'
            '.clj' = '00FF7F'
            '.psc1' = '00BFFF'
            '.fnt' = 'DC143C'
            '.vcxproj' = 'EE82EE'
            '.ogv' = 'FFA500'
            '.dng' = '20B2AA'
            '.woff2' = 'DC143C'
            '.sln.dotsettings' = '6495ED'
            '.dlc' = '6495ED'
            '.psql' = 'FFD700'
            '.pps' = 'DC143C'
            '.code-workplace' = '6495ED'
            '.flac' = 'DB7093'
            '.exs' = '8B4513'
            '.mp3' = 'DB7093'
            '.gzip' = 'DAA520'
            '.xaml' = '87CEFA'
            '.otf' = 'DC143C'
            '.psm1' = '00BFFF'
            '.tsx' = '20B2AA'
            '.c' = 'A9A9A9'
            '.leex' = '8B4513'
            '.pbm' = '20B2AA'
            '.jbig2' = '20B2AA'
            '.rmvb' = 'FFA500'
            '.resx' = '98FB98'
            '.groovy' = '87CEFA'
            '.ogg' = 'FFA500'
            '.qt' = 'FFA500'
            '.rm' = 'FFA500'
            '.fsx' = '00BFFF'
            '.mpe' = 'FFA500'
            '.sass' = 'FF00FF'
            '.pgsql' = 'FFD700'
            '.mp2' = 'FFA500'
            '.code-workspace' = '00BFFF'
            '.prefs' = '6495ED'
            '.vsixmanifest' = '6495ED'
            '.elm' = '9932CC'
            '.xsd' = '98FB98'
            '.postgres' = 'FFD700'
            '.cmd' = '008000'
            '.eex' = '8B4513'
            '.xsl' = '98FB98'
            '.asp' = 'CD5C5C'
            '.wma' = 'DB7093'
            '.tsv' = '9ACD32'
            '.config' = '6495ED'
            '.xslt' = '98FB98'
            '.psd1' = '00BFFF'
            '.csproj' = 'EE82EE'
            '.ppsm' = 'DC143C'
            '.odttf' = 'DC143C'
            '.sublime-workspace' = 'F4A460'
            '.cljc' = '00FF7F'
            '.erb' = 'FF0000'
            '.pfx' = 'FF6347'
            '.gifv' = 'FFA500'
            '.webm' = 'FFA500'
            '.sln.dotsettings.user' = '6495ED'
            '.erl' = 'FF6347'
            '.lock' = 'DAA520'
            '.project' = '98FB98'
            '.ex' = '8B4513'
            '.jng' = '20B2AA'
            '.fs' = '00BFFF'
            '.gz' = 'DAA520'
            '.accdb' = 'FFD700'
            '.plist' = '98FB98'
            '.chm' = '87CEEB'
            '.xz' = 'DAA520'
            '.eot' = 'DC143C'
            '.html_vm' = 'CD5C5C'
            '.user' = '00BFFF'
            '.dtd' = '98FB98'
            '.tsbuildinfo' = 'FFD700'
            '.tmLanguage' = '98FB98'
            '.iml' = '98FB98'
            '.ami' = '20B2AA'
            '.jpeg' = '20B2AA'
            '.esx' = 'F0E68C'
            '.gpg' = '66CDAA'
            '.md' = '00BFFF'
            '.gif' = '20B2AA'
            '.bzip2' = 'DAA520'
            '.patch' = 'FF4500'
            '.bpg' = '20B2AA'
            '.vbs' = 'EE82EE'
            '.pptm' = 'DC143C'
            '.ruleset' = 'EE82EE'
            '.pks' = 'FFD700'
            '.vb' = 'EE82EE'
            '.csv' = '9ACD32'
            '.doc' = '00BFFF'
            '.yml' = 'FF6347'
            '.fsproj' = '00BFFF'
            '.prop' = '6495ED'
            '.ppa' = 'DC143C'
            '.tar' = 'DAA520'
            '.ppt' = 'DC143C'
            '.mdb' = 'FFD700'
            '.png' = '20B2AA'
          }
        }
      }
    }
    Icon = @{
      devblackops = @{
        Name = 'devblackops'
        Types = @{
          Directories = @{
            WellKnown = @{
              '.github' = 'nf-custom-folder_github'
              docs = 'nf-fa-folder'
              '.git' = 'nf-custom-folder_git'
              images = 'nf-mdi-folder_image'
              '.vscode' = 'nf-custom-folder_config'
            }
            '' = 'nf-fa-folder'
          }
          Files = @{
            '.conf' = 'nf-fa-gear'
            '.vob' = 'nf-fa-file_video_o'
            '.php' = 'nf-dev-php'
            '.mjs' = 'nf-dev-javascript'
            '.hs' = 'nf-dev-haskell'
            '.erl' = 'nf-dev-erlang'
            '.ppt' = 'nf-mdi-file_powerpoint'
            '.xslt' = 'nf-mdi-xml'
            '.toml' = 'nf-fa-gear'
            '.user' = 'nf-mdi-visualstudio'
            WellKnown = @{
              '.jshintignore' = 'nf-fa-gear'
              'docker-compose.ci.yml' = 'nf-dev-docker'
              '.firebaserc' = 'nf-dev-firebase'
              'CHANGELOG.txt' = 'nf-fae-checklist_o'
              'code_of_conduct.md' = 'nf-mdi-check_circle'
              '.gitmodules' = 'nf-dev-git'
              'bitbucket-pipelines.yaml' = 'nf-dev-bitbucket'
              '.esmrc' = 'nf-dev-nodejs_small'
              '.bowerrc' = 'nf-dev-bower'
              'cdp.pid' = 'nf-seti-json'
              'docker-compose.override.yml' = 'nf-dev-docker'
              'README.md' = 'nf-mdi-library_books'
              'authors.md' = 'nf-oct-person'
              'manifest.mf' = 'nf-fa-gear'
              '.tsbuildinfo' = 'nf-seti-json'
              '.jshintrc' = 'nf-seti-json'
              'gulpfile.ts' = 'nf-dev-gulp'
              '.travis.yml' = 'nf-dev-travis'
              'docker-compose.production.yml' = 'nf-dev-docker'
              'gulpfile.babel.js' = 'nf-dev-gulp'
              '.yardopts' = 'nf-fa-gear'
              '.mrconfig' = 'nf-fa-gear'
              'gulpfile.js' = 'nf-dev-gulp'
              '.gitignore' = 'nf-dev-git'
              '.jscsrc' = 'nf-seti-json'
              'git-history' = 'nf-dev-git'
              'authors.txt' = 'nf-oct-person'
              'docker-compose.staging.yml' = 'nf-dev-docker'
              'tslint.json' = 'nf-seti-json'
              'package-lock.json' = 'nf-dev-nodejs_small'
              'bower.json' = 'nf-dev-bower'
              'package.json' = 'nf-dev-nodejs_small'
              '.DS_Store' = 'nf-fa-file_o'
              '.nmpignore' = 'nf-dev-npm'
              'docker-compose.prod.yml' = 'nf-dev-docker'
              '.gitkeep' = 'nf-dev-git'
              'code_of_conduct.txt' = 'nf-mdi-check_circle'
              '.buildignore' = 'nf-fa-gear'
              'docker-compose.local.yml' = 'nf-dev-docker'
              'tsconfig.json' = 'nf-seti-json'
              '.gitattributes' = 'nf-dev-git'
              'docker-compose.dev.yml' = 'nf-dev-docker'
              '.azure-pipelines.yml' = 'nf-mdi-azure'
              '.htaccess' = 'nf-mdi-xml'
              'docker-compose.yaml' = 'nf-dev-docker'
              'docker-compose.test.yml' = 'nf-dev-docker'
              'firebase.json' = 'nf-dev-firebase'
              'bitbucket-pipelines.yml' = 'nf-dev-bitbucket'
              'favicon.ico' = 'nf-seti-favicon'
              'composer.lock' = 'nf-seti-json'
              '.clang-format' = 'nf-fa-gear'
              '.jenkinsfile' = 'nf-dev-jenkins'
              '.jsbeautifyrc' = 'nf-seti-json'
              '.gitlab-ci.yml' = 'nf-fa-gitlab'
              'vue.config.ts' = 'nf-mdi-vuejs'
              'CHANGELOG.md' = 'nf-fae-checklist_o'
              '.clang-tidy' = 'nf-fa-gear'
              CHANGELOG = 'nf-fae-checklist_o'
              'README.txt' = 'nf-mdi-library_books'
              'docker-compose.yml' = 'nf-dev-docker'
              LICENSE = 'nf-mdi-certificate'
              README = 'nf-mdi-library_books'
              '.npmrc' = 'nf-dev-npm'
              Dockerfile = 'nf-dev-docker'
              '.nvmrc' = 'nf-dev-nodejs_small'
              '.esformatter' = 'nf-seti-json'
              'vue.config.js' = 'nf-mdi-vuejs'
              authors = 'nf-oct-person'
            }
            '.flac' = 'nf-fa-file_audio_o'
            '.suo' = 'nf-dev-visualstudio'
            '.bmp' = 'nf-fa-file_image_o'
            '.ntf' = 'nf-fa-font'
            '.asp' = 'nf-seti-html'
            '.clixml' = 'nf-dev-code_badge'
            '.log' = 'nf-mdi-view_list'
            '.pgsql' = 'nf-dev-database'
            '.settings' = 'nf-fa-gear'
            '.htm' = 'nf-seti-html'
            '.gpg' = 'nf-fa-key'
            '.exr' = 'nf-fa-file_image_o'
            '.rst' = 'nf-dev-markdown'
            '.erb' = 'nf-oct-ruby'
            '.config' = 'nf-fa-gear'
            '.exs' = 'nf-custom-elixir'
            '.cljc' = 'nf-dev-clojure'
            '.font' = 'nf-fa-font'
            '.ppa' = 'nf-mdi-file_powerpoint'
            '.csv' = 'nf-mdi-file_excel'
            '.lock' = 'nf-fa-lock'
            '.pptx' = 'nf-mdi-file_powerpoint'
            '.fsi' = 'nf-dev-fsharp'
            '.dockerignore' = 'nf-dev-docker'
            '.m2v' = 'nf-fa-file_video_o'
            '.cmd' = 'nf-custom-msdos'
            '.plist' = 'nf-mdi-xml'
            '.vscodeignore' = 'nf-fa-gear'
            '.doc' = 'nf-mdi-file_word'
            '.ps1' = 'nf-dev-terminal_badge'
            '.avi' = 'nf-fa-file_video_o'
            '.png' = 'nf-fa-file_image_o'
            '.woff' = 'nf-fa-font'
            '.xsd' = 'nf-mdi-xml'
            '.ts' = 'nf-seti-typescript'
            '.cer' = 'nf-fa-certificate'
            '.cljs' = 'nf-dev-clojure'
            '.pem' = 'nf-fa-key'
            '.prefs' = 'nf-fa-gear'
            '.yaml' = 'nf-mdi-format_align_left'
            '.tiff' = 'nf-fa-file_image_o'
            '.ex' = 'nf-custom-elixir'
            '.fsx' = 'nf-dev-fsharp'
            '.ami' = 'nf-fa-file_image_o'
            '.fnt' = 'nf-fa-font'
            '.patch' = 'nf-dev-git'
            '.esx' = 'nf-dev-javascript'
            '.sublime-project' = 'nf-dev-sublime'
            '.csx' = 'nf-mdi-language_csharp'
            '.fonts' = 'nf-fa-font'
            '.sh' = 'nf-oct-terminal'
            '.pptm' = 'nf-mdi-file_powerpoint'
            '.cert' = 'nf-fa-certificate'
            '.bat' = 'nf-custom-msdos'
            '.xz' = 'nf-oct-file_zip'
            '.zip' = 'nf-oct-file_zip'
            '.rm' = 'nf-fa-file_video_o'
            '.dll' = 'nf-fa-archive'
            '.csproj' = 'nf-dev-visualstudio'
            '.cs' = 'nf-mdi-language_csharp'
            '.psb' = 'nf-fa-file_image_o'
            '.ttc' = 'nf-fa-font'
            '.pgf' = 'nf-fa-file_image_o'
            '.tmLanguage' = 'nf-mdi-xml'
            '.webm' = 'nf-fa-file_video_o'
            '.gzip' = 'nf-oct-file_zip'
            '.jpeg' = 'nf-fa-file_image_o'
            '.webp' = 'nf-fa-file_image_o'
            '.mp4' = 'nf-fa-file_video_o'
            '.jxr' = 'nf-fa-file_image_o'
            '.vcxitems' = 'nf-dev-visualstudio'
            '.mov' = 'nf-fa-file_video_o'
            '.xquery' = 'nf-mdi-xml'
            '.ppsx' = 'nf-mdi-file_powerpoint'
            '.gbr' = 'nf-fa-file_image_o'
            '.gemfile' = 'nf-oct-ruby'
            '.vsxproj.filters' = 'nf-dev-visualstudio'
            '.asc' = 'nf-fa-key'
            '.reg' = 'nf-fa-gear'
            '.sln' = 'nf-dev-visualstudio'
            '.json' = 'nf-seti-json'
            '.html_vm' = 'nf-seti-html'
            '.vbs' = 'nf-dev-visualstudio'
            '.mpg' = 'nf-fa-file_video_o'
            svg = 'nf-mdi-svg'
            '.option' = 'nf-fa-gear'
            '.js' = 'nf-dev-javascript'
            '.manifest' = 'nf-mdi-xml'
            '.go' = 'nf-dev-go'
            '.mpe' = 'nf-fa-file_video_o'
            '.accdb' = 'nf-dev-database'
            '.mrg' = 'nf-fa-font'
            '.eot' = 'nf-fa-font'
            '.postgres' = 'nf-dev-database'
            '.prop' = 'nf-fa-gear'
            '.jng' = 'nf-fa-file_image_o'
            '.docx' = 'nf-mdi-file_word'
            '.resx' = 'nf-mdi-xml'
            '.vsix' = 'nf-fa-gear'
            '.psd1' = 'nf-dev-terminal_badge'
            '.txt' = 'nf-mdi-file_document'
            '.potx' = 'nf-mdi-file_powerpoint'
            '.vue' = 'nf-mdi-vuejs'
            '.yuv' = 'nf-fa-file_video_o'
            '.jbig2' = 'nf-fa-file_image_o'
            '.bpg' = 'nf-fa-file_image_o'
            '.pkb' = 'nf-dev-database'
            '.project' = 'nf-mdi-xml'
            '.dng' = 'nf-fa-file_image_o'
            '.odttf' = 'nf-fa-font'
            '.dtd' = 'nf-mdi-xml'
            '.bz' = 'nf-oct-file_zip'
            '.pbm' = 'nf-fa-file_image_o'
            '.fsproj' = 'nf-dev-fsharp'
            '.props' = 'nf-fa-gear'
            '.iml' = 'nf-mdi-xml'
            '.qt' = 'nf-fa-file_video_o'
            '.md' = 'nf-dev-markdown'
            '.mdb' = 'nf-dev-database'
            '.fpx' = 'nf-fa-file_image_o'
            '.jb2' = 'nf-fa-file_image_o'
            '.tgz' = 'nf-oct-file_zip'
            '.clj' = 'nf-dev-clojure'
            '.sass' = 'nf-dev-sass'
            '.pic' = 'nf-fa-file_image_o'
            pssc = 'nf-dev-terminal_badge'
            '.wma' = 'nf-fa-file_audio_o'
            '.rb' = 'nf-oct-ruby'
            '.pps' = 'nf-mdi-file_powerpoint'
            '.eex' = 'nf-custom-elixir'
            '.code-workplace' = 'nf-fa-gear'
            '.lua' = 'nf-seti-lua'
            '.m4a' = 'nf-fa-file_audio_o'
            '.vcxitems.filters' = 'nf-dev-visualstudio'
            '.ogg' = 'nf-fa-file_video_o'
            '.html' = 'nf-seti-html'
            '.ics' = 'nf-fa-calendar'
            '.eps' = 'nf-fa-file_image_o'
            '.raw' = 'nf-fa-file_image_o'
            '.xhtml' = 'nf-seti-html'
            '.properties' = 'nf-fa-gear'
            '.less' = 'nf-dev-less'
            '.mpeg' = 'nf-fa-file_video_o'
            '.gz' = 'nf-oct-file_zip'
            '.xaml' = 'nf-mdi-xaml'
            '.yml' = 'nf-mdi-format_align_left'
            '.groovy' = 'nf-dev-groovy'
            '.aiff' = 'nf-fa-file_audio_o'
            '.mp3' = 'nf-fa-file_audio_o'
            '.pl' = 'nf-dev-perl'
            '.jpg' = 'nf-fa-file_image_o'
            '.vb' = 'nf-dev-visualstudio'
            '.jsx' = 'nf-dev-react'
            '.brotli' = 'nf-oct-file_zip'
            '.psc1' = 'nf-dev-terminal_badge'
            '.ini' = 'nf-fa-gear'
            '.vsixmanifest' = 'nf-fa-gear'
            '.gif' = 'nf-fa-file_image_o'
            '.xml' = 'nf-mdi-xml'
            '.dockerfile' = 'nf-dev-docker'
            '.ruleset' = 'nf-dev-visualstudio'
            '.tsx' = 'nf-dev-react'
            '.crt' = 'nf-fa-certificate'
            '.applescript' = 'nf-dev-apple'
            '.cfg' = 'nf-fa-gear'
            '.tsbuildinfo' = 'nf-seti-json'
            '.ttf' = 'nf-fa-font'
            '.vcxproj' = 'nf-dev-visualstudio'
            '.bzip2' = 'nf-oct-file_zip'
            '.suit' = 'nf-fa-font'
            '.chm' = 'nf-mdi-help_box'
            '.rtf' = 'nf-mdi-file_word'
            '.cur' = 'nf-fa-file_image_o'
            '.xls' = 'nf-mdi-file_excel'
            '.xlsx' = 'nf-mdi-file_excel'
            '.7z' = 'nf-oct-file_zip'
            '.sui' = 'nf-fa-font'
            '.br' = 'nf-oct-file_zip'
            '.bmap' = 'nf-fa-font'
            '.psm1' = 'nf-dev-terminal_badge'
            '.ppsm' = 'nf-mdi-file_powerpoint'
            '.css' = 'nf-dev-css3'
            '.leex' = 'nf-custom-elixir'
            '.key' = 'nf-fa-key'
            '.markdown' = 'nf-dev-markdown'
            '.psql' = 'nf-dev-database'
            '.sln.dotsettings.user' = 'nf-fa-gear'
            '.rs' = 'nf-dev-rust'
            '.dart' = 'nf-dev-dart'
            '.mkv' = 'nf-fa-file_video_o'
            '.mp2' = 'nf-fa-file_video_o'
            '.pub' = 'nf-fa-key'
            '.dds' = 'nf-fa-file_image_o'
            '.mpv' = 'nf-fa-file_video_o'
            '.code-workspace' = 'nf-mdi-visualstudio'
            '.pdb' = 'nf-dev-database'
            '.tif' = 'nf-fa-file_image_o'
            '.pks' = 'nf-dev-database'
            '.pfx' = 'nf-fa-certificate'
            '.elm' = 'nf-custom-elm'
            '.sqlite' = 'nf-dev-database'
            '.otf' = 'nf-fa-font'
            '.cpp' = 'nf-mdi-language_cpp'
            '.sublime-workspace' = 'nf-dev-sublime'
            '.brk' = 'nf-fa-file_image_o'
            '.woff2' = 'nf-fa-font'
            '.pdf' = 'nf-mdi-file_pdf'
            '.rar' = 'nf-oct-file_zip'
            '.flv' = 'nf-fa-file_video_o'
            '.sql' = 'nf-dev-database'
            '.psd' = 'nf-fa-file_image_o'
            '.img' = 'nf-fa-file_image_o'
            '.exe' = 'nf-mdi-application'
            '.gifv' = 'nf-fa-file_video_o'
            '.c' = 'nf-mdi-language_c'
            '.ico' = 'nf-fa-file_image_o'
            '.ps1xml' = 'nf-dev-terminal_badge'
            '.apx' = 'nf-fa-file_image_o'
            '.fs' = 'nf-dev-fsharp'
            '.ogv' = 'nf-fa-file_video_o'
            '.tar' = 'nf-oct-file_zip'
            '.sln.dotsettings' = 'nf-fa-gear'
            '.wmv' = 'nf-fa-file_video_o'
            '.rmvb' = 'nf-fa-file_video_o'
            '.xsl' = 'nf-mdi-xml'
            '.tsv' = 'nf-mdi-file_excel'
            '.potm' = 'nf-mdi-file_powerpoint'
            '.ppam' = 'nf-mdi-file_powerpoint'
            '.dlc' = 'nf-fa-gear'
            '' = 'nf-fa-file'
          }
        }
      }
    }
  }
}


================================================
FILE: Build.ps1
================================================
#requires -Module @{ModuleName = "ModuleBuilder"; ModuleVersion = "2.0.0"}, Configuration
[CmdletBinding()]
param(
    # A specific folder to build into
    $OutputDirectory,

    # The version of the output module
    [Alias("ModuleVersion")]
    [string]$SemVer
)
Push-Location $PSScriptRoot -StackName BuildTestStack

if (-not $Semver -and (Get-Command gitversion -ErrorAction Ignore)) {
    if ($semver = gitversion -showvariable SemVer) {
        $null = $PSBoundParameters.Add("SemVer", $SemVer)
    }
}

try {
    Build-Module @PSBoundParameters -Target CleanBuild
} finally {
    Pop-Location -StackName BuildTestStack
}

================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [1.6.0] - 2023-08-24

### Added

- Get-ParameterValue. Get parameter values from PSBoundParameters + DefaultValues and optionally, a configuration file.

### Fixed

- Only call Add-MetadataConverter at load if converters are supplied at load time.

## [1.5.1] - 2022-06-06

### Fixed

- Stop re-importing the metadata module at import

## [1.5.0] - 2021-07-03

### Removed

This is the first release without the Metadata module included. This module is now available as a separate module on the PowerShell Gallery.

### Added

Test runs on GitHub Actions now include Linux and Mac OS.

AllowedVariables now flow through the whole module (and into calls to the Metadata module).

================================================
FILE: Examples/TestModuleOne/Configuration.psd1
================================================
@{
    Address = "http://PoshCode.org"
    Credential = $null
}

================================================
FILE: Examples/TestModuleOne/TestModuleOne.psm1
================================================
# If your default configuration has some blank settings, you can do something like this:
# Assume I have a mandatory credential:
function ImportConfiguration {
    $Configuration = Import-Configuration
    if(!$Configuration.Credential.Password.Length) {
        Write-Warning "Thanks for using the Acme Industries Module, please run Set-AimConfiguration to configure."
        throw "Module not configured. Run Set-AimConfiguration"
    }
    $Configuration
}

function Set-AimConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
        [string]$Address,

        [Parameter(Mandatory,ValueFromPipelineByPropertyName)]
        [ValidateScript({
            if ($_.Password.Length -eq 0) {
                throw "Credential must include a password."
            }
            $true
        })]
        [PSCredential]$Credential
    )
    end {
        $PSBoundParameters | Export-Configuration
    }
}

# Test for it **during** module import:
try {
    $null = ImportConfiguration
} catch {
    # Hide the error on import, just warn them
    Write-Host "You must configure module to avoid this warning on first run. Use Set-AimConfiguration" -ForegroundColor Black -BackgroundColor Yellow
}

================================================
FILE: Examples/TestModuleTwo/Configuration.psd1
================================================
@{
    Address = "http://PoshCode.org"
}

================================================
FILE: Examples/TestModuleTwo/TestModuleTwo.psm1
================================================
# If your default configuration has valid defaults, but you still want them to review it,
# Provide a public Get-Configuration and test the path(s):
Write-Verbose "No Settings"
function TestStoragePath {
    $Path = Get-StoragePath
    Test-Path (Join-Path $Path "Configuration.psd1")
}

function Set-AimConfiguration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [string]$Address
    )
    process {
        $PSBoundParameters | Export-Configuration
    }
}

function Get-AimConfiguration {
    Import-Configuration
}

if (!(TestStoragePath -Verbose)) {
    Write-Warning "Not Configured"
    Write-Host @"
Welcome first-time users:
You should review and approve the configuration of this module by running:
    `$Configuration = Get-AimConfiguration
    `$Configuration

And then review the settings. When you're satisfied, approve them by:

    Set-AimConfiguration @Configuration
"@ -ForegroundColor Black -BackgroundColor Yellow
}

================================================
FILE: GitVersion.yml
================================================
mode: Mainline
commit-message-incrementing: MergeMessageOnly

assembly-versioning-format: '{Major}.{Minor}.{Patch}.{env:BUILDCOUNT ?? 0}'
assembly-informational-format: '{NuGetVersionV2}+Build.{env:BUILDCOUNT ?? 0}.Date.{CommitDate}.Branch.{env:SAFEBRANCHNAME ?? unknown}.Sha.{Sha}'
commit-date-format: yyyyMMddTHHmmss

branches:
  master:
    increment: Patch
    is-release-branch: true
  pull-request:
    tag: rc
    increment: Patch
  feature:
    regex: .*/
    tag: useBranchName
    increment: Patch
    source-branches: ['master', 'feature']
    track-merge-target: true
  release:
    tag: ''
    regex: releases?[/-]\d+\.\d+\.\d+
    increment: Patch
    is-release-branch: true

================================================
FILE: LICENSE
================================================
Copyright (c) 2015 Joel Bennett

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

================================================
FILE: PSScriptAnalyzerSettings.psd1
================================================
@{
    Severity     = @('Error', 'Warning')
    ExcludeRules = @('PSAvoidUsingDeprecatedManifestFields', 'PSPossibleIncorrectUsageOfAssignmentOperator')
}

================================================
FILE: README.md
================================================
[![Build Status](https://github.com/PoshCode/Configuration/actions/workflows/build.yml/badge.svg)](https://github.com/PoshCode/Configuration/actions/workflows/build.yml)
# The Configuration Module

Configuration commands for storing and loading settings (usually just hashtables, but can be any PSObject). Just pipe your hashtable to `Export-Configuration` and load it back with `Import-Configuration`!

- Ship default configuration files with your module
- Automatically determines where to save your settings
  - Supports Windows roaming profiles
  - Supports XDG settings for Linux (and MacOS)
- Allow users to configure settings using our commands, or your own custom commands
- Supports _layered_ configurations:
  - Machine-level config
  - User override files
- Supports automatic configuration of parameters values for a command
  - Reads `Noun` configuration files in your working directory
  - Filters only values which apply to the current command
  - Supports recursively defining defaults in folders
- Supports combining parameter values from PSBoundParameters, DefaultValues, and an optional configuration file.

These modules work back to much older versions of PowerShell, but are currently being tested on Windows PowerShell and PowerShell 7. They depend on the Metadata module for serialization of objects to psd1 format.

## Metadata commands for working with .psd1 are in the [Metadata](https://github.com/PoshCode/Metadata) module

- Manipulating metadata files
- Extensible serialization of types
- Built in support for DateTime, Version, Guid, SecureString, ScriptBlocks and more
- Lets you store almost anything in readable metadata (.psd1) files
- Serializing (`Export`) to metadata (.psd1) files
- Deserializing (`Import`) from metadata (.psd1) files

It supports WindowsPowerShell, as well as PowerShell Core on Windows, Linux and OS X.

## Installation

```posh
Install-Module Configuration
```

## Usage

The Configuration module is designed to be used by other modules (or from scripts) to allow the storage of configuration data (generally, hashtables, but any PSObject).

In its simplest form, you add a `Configuration.psd1` file to a module you're authoring, and put your default settings in it -- perhaps something as simple as this:

```posh
@{
    DriveName = "data"
}
```

Then, in your module, you import those settings _in a function_ when you need them, or expose them to your users like this:

```posh
function Get-FaqConfig {
    Import-Configuration
}
```

Perhaps, in a simple case like this one, you might write a wrapper function so your users can get _and set_ that one configuration option directly:

```posh
function Get-DataDriveName {
    $script:Config = Import-Configuration
    $config.DriveName
}

function Set-DataDriveName {
    param([Parameter(Mandatory)][String]$Name)

    @{ DriveName = $Name} | Export-Config
}
```

Of course, you could have imported the configuration, edited that one setting, and then exported the whole config, but you can also just export a few settings, because `Import-Configuration` supports a layered configuration. More on that in a moment, but first, let's talk about how this all works.

### Versioning

Versioning your configuration is supported, but is only done explicitly (in `Import-Configuration`, `Export-Configuration`, and `Get-ConfigurationPath`). Whenever you need to change your module's configuration in an incompatible way, you can write a migration function that runs at import-time in your new version, something like this:

```powershell
# Specify a script-level version number
$ConfigVersion = @{ Version = 1.1 }

function MigrateData {
    # Specify the version you want to migrate
    $OldVersion = @{ <# I didn't specify a version at first #> }

    # If there are no configuration files, migrate them
    if(!(Get-ConfigurationPath @ConfigVersion | Get-ChildItem -File)) {
        # Import the old config
        $oldConfig = Import-Configuration @OldVersion
        # Transform your configuration however you like
        $newConfig = @{ PSDriveName = $existing.DriveName }
        # Export the new config
        $newConfig | Export-Configuration @ConfigVersion
    }
}

# Call your migration function during module import
MigrateData
```

Then you just need to be sure you specify the `@ConfigVersion` whenever you call `Import-Configuration` elsewhere in your module.

Note that configuration files are not currently deleted by Uninstall-Module, so they are never automatically cleaned up.

# How it works

The Configuration module works by serializing PowerShell hashtables or custom objects into PowerShell data language in a `Configuration.psd1` file!

## Configuration path

When you `Export-Configuration` you can set the `-Scope`, which determines where the Configuration.psd1 are stored:

- **User** exports to `$Env:LocalAppData` or `~/.config/`
- **Enterprise** exports to `$Env:AppData` (the roaming path) or `~/.local/share/`
- **Machine** exports to `$Env:ProgramData` or `/etc/xdg/`

Note that the linux paths are controlled by XDG environment variables, and the default paths can be overriden by manually editing the Configuration module manifest.

Within that folder, the Configuration module root is "PowerShell," followed by either a company or author and the module name -- within which your configuration file(s) are stored.

From a module that uses Configuration, you can call the `Get-ConfigurationPath` command to get the path to that folder, and since the folder is created for you, you can use it store other files, like cached images, etc.

## Layered Configuration

In addition to automatically determining the storage path, the configuration module supports layered configuration, so that you can have defaults you ship with your module, or configure default at the enterprise or machine level, and still allow users to override the settings. When you call `Import-Configuration` from within a module, it automatically imports _all_ the available files and updates the configuration object which is returned at the end:

1. First, it imports the default Configuration.psd1 from the module's folder.
2. Then it imports machine-wide settings (e.g. the ProgramData folder)
3. Then it imports the users' enterprise roaming settings (e.g. from AppData\Roaming)
4. Finally it imports the users' local settings (from AppData\Local)

Any missing files are just skipped, and each layer of settings updates the settings from the previous layers, so if you don't set a setting in one layer, the setting from the previous layers persists.

However, it's up to individual users and module authors to take advantage of this..

## Serialization

The actual serialization commands (with the `Metadata` noun) are: ConvertFrom, ConvertTo, Import and Export.  By default, the Configuration serializer can handle a variety of custom PSObjects, hashtables, and arrays recursively, and has specific handling for booleans, strings and numbers, as well as Versions, GUIDs, and DateTime, DateTimeOffset, and even ScriptBlocks and PSCredential objects.

**Important note:** PSCredentials are stored using ConvertTo-SecureString, and currently only work on Windows. They should be stored in the user scope, since they're serialized per-user, per-machine, using the Windows Data Protection API.

In other words, it handles everything you're likely to need in a configuration file. However, it also has support for adding additional type serializers via the `Add-MetadataConverter` command. If you want to store anything that doesn't work, please raise an issue :wink:.

### One little catch

The configuration module uses the caller's scope to determine the name of the module (and Company or Author name) that is asking for configuration.  For this reason you normally just call `Import-Configuration` from within _a function_ **in** your module (to make sure the callstack shows the module scope).

The _very important_ side effect is that you _must not_ change the module name nor the author of your module if you're using this Configuration module, or you will need to manually call `Import-Configuration` with the old information and then `Export` those settings to the new location (see the )

### Using the cmdlets from outside a module

It is possible to use the commands to Import and Export the configuration for a module from outside the module (or from the main module body, instead of a function), simply pipe the ModuleInfo to `Import-Configuration`. To continue our example from earlier:

```posh
$Config = Get-Module DataModule | Import-Configuration
$Config.DriveName = "DataDrive"
Get-Module DataModule | Export-Configuration $Config
```

Note that if you look at the parameter sets for `Import-Configuration` you will find that you can also just pass the the `-Author` (or `-CompanyName`) and module `-Name` by hand, but you must be sure to get them exactly right, or you'll import nothing...

```posh
$Config = Import-Configuration -Name DataModule -Author HuddledMasses.org
```

Because of how easily this can go wrong, I strongly recommend you don't use this syntax -- but if you do, be aware that you must also specify the `-DefaultPath` if you want to load the default configuration file from the module folder.

# A little history

The Configuration module is something I first wrote as part of the PoshCode packaging module and have been meaning to pull out for awhile.

I finally started working on this while I work on writing the Gherkin support for Pester. That support was  merged into Pester with the 4.0 release, and I'm using it for the tests in this module.

In any case, this module is mostly code ported from my PoshCode module as I develop the specs (the .feature files) and the Gherkin support to run them! Anything you see here has better than 95% code coverage in the feature and step files, which are executable via `Invoke-Gherkin`.

For the tests to work, you need to make sure that the module isn't already loaded, because the tests import it with the file paths mocked for testing:

```posh
Remove-Module Configuration -ErrorAction SilentlyContinue
Invoke-Gherkin -CodeCoverage *.psm1
```


================================================
FILE: ReBuild.ps1
================================================
#requires -Version "4.0" -Module PackageManagement, Pester
[CmdletBinding()]
param(
    # The step(s) to run. Defaults to "Clean", "Update", "Build", "Test", "Package"
    # You may also "Publish"
    # It's also acceptable to skip the "Clean" and particularly "Update" steps
    [ValidateSet("Clean", "Update", "Build", "Test", "Package", "Publish")]
    [string[]]$Step = @("Clean", "Update", "Build", "Test"),

    # The path to the module to build. Defaults to the folder this script is in.
    [Alias("PSPath")]
    [string]$Path = $PSScriptRoot,

    # The name of the module to build.
    # Default is hardcoded to "Configuration" because AppVeyor forces checkout to lowercase path name
    [string]$ModuleName = "Configuration",

    # The target framework for .net (for packages), with fallback versions
    # The default supports PS3:  "net40","net35","net20","net45"
    # To only support PS4, use:  "net45","net40","net35","net20"
    # To support PS2, you use:   "net35","net20"
    [string[]]$TargetFramework = @("net40","net35","net20","net45"),

    # The revision number (pulled from the environment in AppVeyor)
    [Nullable[int]]$RevisionNumber = ${Env:APPVEYOR_BUILD_NUMBER},

    [ValidateNotNullOrEmpty()]
    [String]$CodeCovToken = ${ENV:CODECOV_TOKEN},

    # The default language is your current UICulture
    [Globalization.CultureInfo]$DefaultLanguage = $((Get-Culture).Name)
)

$Script:TraceVerboseTimer = New-Object System.Diagnostics.Stopwatch
$Script:TraceVerboseTimer.Start()

$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

function init {
    #.Synopsis
    #   The init step always has to run.
    #   Calculate your paths and so-on here.
    [CmdletBinding()]
    param()

    # Calculate Paths
    # The output path is just a temporary output and logging location
    $Script:OutputPath = Join-Path $Path output

    if(Test-Path $OutputPath -PathType Leaf) {
        throw "Cannot create folder for Configuration because there's a file in the way at '$OutputPath'"
    }
    if(!(Test-Path $OutputPath -PathType Container)) {
        $null = New-Item $OutputPath -Type Directory -Force
    }

    # We expect the source for the module in a subdirectory called one of three things:
    $Script:SourcePath = "src", "source", ${ModuleName} | ForEach-Object { Join-Path $Path $_ -Resolve -ErrorAction Ignore } | Select-Object -First 1
    if(!$SourcePath) {
        Write-Warning "This Build script expects a 'Source' or '$ModuleName' folder to be alongside it."
        throw "Can't find module source folder."
    }

    $Script:ManifestPath = Join-Path $SourcePath "${ModuleName}.psd1" -Resolve -ErrorAction Ignore
    if(!$ManifestPath) {
        Write-Warning "This Build script expects a '${ModuleName}.psd1' in the '$SourcePath' folder."
        throw "Can't find module source files"
    }
    $Script:TestPath = "Tests", "Specs" | ForEach-Object { Join-Path $Path $_ -Resolve -ErrorAction Ignore } | Select-Object -First 1
    if(!$TestPath) {
        Write-Warning "This Build script expects a 'Tests' or 'Specs' folder to contain tests."
    }
    # Calculate Version here, because we need it for the release path
    [Version]$Script:Version = Get-Module $ManifestPath -ListAvailable | Select-Object -ExpandProperty Version

    # If the RevisionNumber is specified as ZERO, this is a release build ...
    # If the RevisionNumber is not specified, this is a dev box build
    # If the RevisionNumber is specified, we assume this is a CI build
    if($Script:RevisionNumber -ge 0) {
        # For CI builds we don't increment the build number
        $Script:Build = if($Version.Build -le 0) { 0 } else { $Version.Build }
    } else {
        # For dev builds, assume we're working on the NEXT release
        $Script:Build = if($Version.Build -le 0) { 1 } else { $Version.Build + 1}
    }

    if([string]::IsNullOrEmpty($RevisionNumber) -or $RevisionNumber -eq 0) {
        $Script:Version = New-Object Version $Version.Major, $Version.Minor, $Build
    } else {
        $Script:Version = New-Object Version $Version.Major, $Version.Minor, $Build, $RevisionNumber
    }

    # The release path is where the final module goes
    $Script:ReleasePath = Join-Path $Path $Version
    $Script:ReleaseManifest = Join-Path $ReleasePath "${ModuleName}.psd1"

}

function clean {
    #.Synopsis
    #   Clean output and old log
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$ReleasePath = $Script:ReleasePath,

        # Also clean packages
        [Switch]$Packages
    )

    Trace-Message "OUTPUT Release Path: $ReleasePath"
    if(Test-Path $ReleasePath) {
        Trace-Message "       Clean up old build"
        Trace-Message "DELETE $ReleasePath\"
        Remove-Item $ReleasePath -Recurse -Force
    }
    if(Test-Path $Path\packages) {
        Trace-Message "DELETE $Path\packages"
        # force reinstall by cleaning the old ones
        Remove-Item $Path\packages\ -Recurse -Force
    }
    if(Test-Path $Path\packages\build.log) {
        Trace-Message "DELETE $OutputPath\build.log"
        Remove-Item $OutputPath\build.log -Recurse -Force
    }

}

function update {
    #.Synopsis
    #   Nuget restore and git submodule update
    #.Description
    #   This works like nuget package restore, but using PackageManagement
    #   The benefit of using PackageManagement is that you can support any provider and any source
    #   However, currently only the nuget providers supports a -Destination
    #   So for most cases, you could use nuget restore instead:
    #      nuget restore $(Join-Path $Path packages.config) -PackagesDirectory "$Path\packages" -ExcludeVersion -PackageSaveMode nuspec
    [CmdletBinding()]
    param(
        # Force reinstall
        [switch]$Force=$($Step -contains "Clean"),

        # Remove packages first
        [switch]$Clean
    )
    $ErrorActionPreference = "Stop"
    Set-StrictMode -Version Latest
    Trace-Message "UPDATE $ModuleName in $Path"

    if(Test-Path (Join-Path $Path packages.config)) {
        if(!($Name = Get-PackageSource | Where-Object Location -eq 'https://www.nuget.org/api/v2' | ForEach-Object Name)) {
            Write-Warning "Adding NuGet package source"
            $Name = Register-PackageSource NuGet -Location 'https://www.nuget.org/api/v2' -ForceBootstrap -ProviderName NuGet | Where-Object Name
        }

        if($Force -and (Test-Path $Path\packages)) {
            # force reinstall by cleaning the old ones
            remove-item $Path\packages\ -Recurse -Force
        }
        if(Test-Path $Path\packages\ -PathType Leaf) {
            throw "Cannot create folder for Configuration because there's a file in the way at '$Path\packages\'"
        }
        if(!(Test-Path $Path\packages\ -PathType Container)) {
            $null = New-Item $Path\packages\ -Type Directory -Force
        }

        # Remember, as of now, only nuget actually supports the -Destination flag
        foreach($Package in ([xml](Get-Content .\packages.config)).packages.package) {
            Trace-Message "Installing $($Package.id) v$($Package.version) from $($Package.Source)"
            $null = Install-Package -Name $Package.id -RequiredVersion $Package.version -Source $Package.Source -Destination $Path\packages -Force:$Force -ErrorVariable failure
            if($failure) {
                throw "Failed to install $($package.id), see errors above."
            }
        }
    }

    # we also check for git submodules...
    git submodule update --init --recursive
}

function build {
    [CmdletBinding()]
    param()
    Trace-Message "BUILDING: $ModuleName from $Path"
    # Copy NuGet dependencies
    $PackagesConfig = (Join-Path $Path packages.config)
    if(Test-Path $PackagesConfig) {
        Trace-Message "       Copying Packages"
        foreach($Package in ([xml](Get-Content $PackagesConfig)).packages.package) {
            $LibPath = "$ReleasePath\lib"
            $folder = Join-Path $Path "packages\$($Package.id)*"

            # The git NativeBinaries are special -- we need to copy all the "windows" binaries:
            if($Package.id -eq "LibGit2Sharp.NativeBinaries") {
                $targets = Join-Path $folder 'libgit2\windows'
                $LibPath = Join-Path $LibPath "NativeBinaries"
            } else {
                # Check for each TargetFramework, in order of preference, fall back to using the lib folder
                $targets = ($TargetFramework -replace '^','lib\') + 'lib' | ForEach-Object { Join-Path $folder $_ }
            }

            $PackageSource = Get-Item $targets -ErrorAction SilentlyContinue | Select-Object -First 1 -Expand FullName
            if(!$PackageSource) {
                throw "Could not find a lib folder for $($Package.id) from package. You may need to run Setup.ps1"
            }

            Trace-Message "robocopy $PackageSource $LibPath /E /NP /LOG+:'$OutputPath\build.log' /R:2 /W:15"
            $null = robocopy $PackageSource $LibPath /E /NP /LOG+:"$OutputPath\build.log" /R:2 /W:15
            if($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1 -and $LASTEXITCODE -ne 3) {
                throw "Failed to copy Package $($Package.id) (${LASTEXITCODE}), see build.log for details"
            }
        }
    }

    $RootModule = Get-Module $ManifestPath -ListAvailable | Select-Object -ExpandProperty RootModule
    if (!$RootModule) {
        $RootModule = Get-Module $ManifestPath -ListAvailable | Select-Object -ExpandProperty ModuleToProcess
        if (!$RootModule) {
            $RootModule = "${ModuleName}.psm1"
        }
    }

    $ReleaseModule = Join-Path $ReleasePath ${RootModule}

    ## Copy PowerShell source Files (support for my new Public|Private folders, and the old simple copy way)
    # if the Source folder has "Public" and optionally "Private" in it, then the psm1 must be assembled:
    if(Test-Path (Join-Path $SourcePath Public) -Type Container){
        Trace-Message "       Collating Module Source"
        if(Test-Path $ReleasePath -PathType Leaf) {
            throw "Cannot create folder for Configuration because there's a file in the way at '$ReleasePath'"
        }
        if(!(Test-Path $ReleasePath -PathType Container)) {
            $null = New-Item $ReleasePath -Type Directory -Force
        }

        Trace-Message "       Setting content for $ReleaseModule"

        $FunctionsToExport = Join-Path $SourcePath Public\*.ps1 -Resolve | ForEach-Object { [System.IO.Path]::GetFileNameWithoutExtension($_) }
        Set-Content $ReleaseModule ((
            (Get-Content (Join-Path $SourcePath Private\*.ps1) -Raw) +
            (Get-Content (Join-Path $SourcePath Public\*.ps1) -Raw)) -join "`r`n`r`n`r`n") -Encoding UTF8

        # If there are any folders that aren't Public, Private, Tests, or Specs ...
        $OtherFolders = Get-ChildItem $SourcePath -Directory -Exclude Public, Private, Tests, Specs
        # Then we need to copy everything in them
        Copy-Item $OtherFolders -Recurse -Destination $ReleasePath

        # Finally, we need to copy any files in the Source directory
        Get-ChildItem $SourcePath -File |
            Where-Object Name -ne $RootModule |
            Copy-Item -Destination $ReleasePath

        Update-Manifest $ReleaseManifest -Property FunctionsToExport -Value $FunctionsToExport
    } else {
        # Legacy modules just have "stuff" in the source folder and we need to copy all of it
        Trace-Message "       Copying Module Source"
        Trace-Message "COPY   $SourcePath\"
        $null = robocopy $SourcePath\  $ReleasePath /E /NP /LOG+:"$OutputPath\build.log" /R:2 /W:15
        if($LASTEXITCODE -ne 3 -AND $LASTEXITCODE -ne 1) {
            throw "Failed to copy Module (${LASTEXITCODE}), see build.log for details"
        }
    }

    # Copy the readme file as an about_ help
    $ReadMe = Join-Path $Path Readme.md
    if(Test-Path $ReadMe -PathType Leaf) {
        $LanguagePath = Join-Path $ReleasePath $DefaultLanguage
        if(Test-Path $LanguagePath -PathType Leaf) {
            throw "Cannot create folder for Configuration because there's a file in the way at '$LanguagePath'"
        }
        if(!(Test-Path $LanguagePath -PathType Container)) {
            $null = New-Item $LanguagePath -Type Directory -Force
        }

        $about_module = Join-Path $LanguagePath "about_${ModuleName}.help.txt"
        if(!(Test-Path $about_module)) {
            Trace-Message "Turn readme into about_module"
            Copy-Item -LiteralPath $ReadMe -Destination $about_module
        }
    }

    ## Update the PSD1 Version:
    Trace-Message "       Update Module Version"
    Push-Location $ReleasePath
    try {
        Import-Module $ReleaseModule -Force
        $FileList = Get-ChildItem -Recurse -File | Resolve-Path -Relative
        Update-Metadata -Path $ReleaseManifest -PropertyName 'ModuleVersion' -Value $Version
        Update-Metadata -Path $ReleaseManifest -PropertyName 'FileList' -Value $FileList
        Import-Module $ReleaseManifest -Force
    } finally {
        Pop-Location
    }
    (Get-Module $ReleaseManifest -ListAvailable | Out-String -stream) -join "`n" | Trace-Message
}

function test {
    [CmdletBinding()]
    param(
        [Switch]$Quiet,

        [Switch]$ShowWip,

        [int]$FailLimit=${Env:ACCEPTABLE_FAILURE},

        [ValidateNotNullOrEmpty()]
        [String]$JobID = ${Env:APPVEYOR_JOB_ID}
    )

    if(!$TestPath) {
        Write-Warning "No tests folder found. Invoking Pester in root: $Path"
        $TestPath = $Path
    }

    Trace-Message "TESTING: $ModuleName with $TestPath"

    Trace-Message "TESTING $ModuleName v$Version" -Verbose:(!$Quiet)
    Remove-Module $ModuleName -ErrorAction SilentlyContinue
    Write-Host $(prompt) -NoNewLine
    Write-Host Remove-Module $ModuleName -ErrorAction SilentlyContinue

    $Options = @{
        OutputFormat = "NUnitXml"
        OutputFile = (Join-Path $OutputPath TestResults.xml)
    }
    if($Quiet) { $Options.Quiet = $Quiet }
    if(!$ShowWip){ $Options.ExcludeTag = @("wip") }

    Set-Content "$TestPath\VersionSpecific.Steps.ps1" "
        BeforeEachFeature {
            Remove-Module 'Configuration' -ErrorAction Ignore -Force
            Import-Module '$ReleasePath\${ModuleName}.psd1' -Force
        }
        AfterEachFeature {
            Remove-Module 'Configuration' -ErrorAction Ignore -Force
            Import-Module '$ReleasePath\${ModuleName}.psd1' -Force
        }
        AfterEachScenario {
            if(Test-Path '$ReleasePath\${ModuleName}.psd1.backup') {
                Remove-Item '$ReleasePath\${ModuleName}.psd1'
                Rename-Item '$ReleasePath\${ModuleName}.psd1.backup' '$ReleasePath\${ModuleName}.psd1'
            }
        }
    "

    # Show the commands they would have to run to get these results:
    Write-Host $(prompt) -NoNewLine
    Write-Host Import-Module $ReleasePath\${ModuleName}.psd1 -Force
    Write-Host $(prompt) -NoNewLine

    # TODO: Update dependency to Pester 4.0 and use just Invoke-Pester
    if(Get-Command Invoke-Gherkin -ErrorAction SilentlyContinue) {
        Write-Host Invoke-Gherkin -Path $TestPath -CodeCoverage "$ReleasePath\*.psm1" -PassThru @Options
        $TestResults = Invoke-Gherkin -Path $TestPath -CodeCoverage "$ReleasePath\*.psm1" -PassThru @Options
    }

    # Write-Host Invoke-Pester -Path $TestPath -CodeCoverage "$ReleasePath\*.psm1" -PassThru @Options
    # $TestResults = Invoke-Pester -Path $TestPath -CodeCoverage "$ReleasePath\*.psm1" -PassThru @Options

    Remove-Module $ModuleName -ErrorAction SilentlyContinue

    $script:failedTestsCount = 0
    $script:passedTestsCount = 0
    foreach($result in $TestResults)
    {
        if($result -and $result.CodeCoverage.NumberOfCommandsAnalyzed -gt 0)
        {
            $script:failedTestsCount += $result.FailedCount
            $script:passedTestsCount += $result.PassedCount
            $CodeCoverageTitle = 'Code Coverage {0:F1}%'  -f (100 * ($result.CodeCoverage.NumberOfCommandsExecuted / $result.CodeCoverage.NumberOfCommandsAnalyzed))

            # TODO: this file mapping does not account for the new Public|Private module source (and I don't know how to make it do so)
            # Map file paths, e.g.: \1.0 back to \src
            for($i=0; $i -lt $TestResults.CodeCoverage.HitCommands.Count; $i++) {
                $TestResults.CodeCoverage.HitCommands[$i].File = $TestResults.CodeCoverage.HitCommands[$i].File.Replace($ReleasePath, $SourcePath)
            }
            for($i=0; $i -lt $TestResults.CodeCoverage.MissedCommands.Count; $i++) {
                $TestResults.CodeCoverage.MissedCommands[$i].File = $TestResults.CodeCoverage.MissedCommands[$i].File.Replace($ReleasePath, $SourcePath)
            }

            if($result.CodeCoverage.MissedCommands.Count -gt 0) {
                $result.CodeCoverage.MissedCommands |
                    ConvertTo-Html -Title $CodeCoverageTitle |
                    Out-File (Join-Path $OutputPath "CodeCoverage-${Version}.html")
            }
            if(${CodeCovToken})
            {
                # TODO: https://github.com/PoshCode/PSGit/blob/dev/test/Send-CodeCov.ps1
                Trace-Message "Sending CI Code-Coverage Results" -Verbose:(!$Quiet)
                $response = &"$TestPath\Send-CodeCov" -CodeCoverage $result.CodeCoverage -RepositoryRoot $Path -OutputPath $OutputPath -Token ${CodeCovToken}
                Trace-Message $response.message -Verbose:(!$Quiet)
            }
        }
    }

    # If we're on AppVeyor ....
    if(Get-Command Add-AppveyorCompilationMessage -ErrorAction SilentlyContinue) {
        Add-AppveyorCompilationMessage -Message ("{0} of {1} tests passed" -f @($TestResults.PassedScenarios).Count, (@($TestResults.PassedScenarios).Count + @($TestResults.FailedScenarios).Count)) -Category $(if(@($TestResults.FailedScenarios).Count -gt 0) { "Warning" } else { "Information"})
        Add-AppveyorCompilationMessage -Message ("{0:P} of code covered by tests" -f ($TestResults.CodeCoverage.NumberOfCommandsExecuted / $TestResults.CodeCoverage.NumberOfCommandsAnalyzed)) -Category $(if($TestResults.CodeCoverage.NumberOfCommandsExecuted -lt $TestResults.CodeCoverage.NumberOfCommandsAnalyzed) { "Warning" } else { "Information"})
    }

    if(${JobID}) {
        if(Test-Path $Options.OutputFile) {
            Trace-Message "Sending Test Results to AppVeyor backend" -Verbose:(!$Quiet)
            $wc = New-Object 'System.Net.WebClient'
            if($response = $wc.UploadFile("https://ci.appveyor.com/api/testresults/nunit/${JobID}", $Options.OutputFile)) {
                if($text = [System.Text.Encoding]::ASCII.GetString($response)) {
                    Trace-Message $text -Verbose:(!$Quiet)
                } else {
                    Trace-Message "No text in response from AppVeyor" -Verbose:(!$Quiet)
                }
            } else {
                Trace-Message "No response when calling UploadFile to AppVeyor" -Verbose:(!$Quiet)
            }
        } else {
            Write-Warning "Couldn't find Test Output: $($Options.OutputFile)"
        }
    }

    if($FailedTestsCount -gt $FailLimit) {
        $exception = New-Object AggregateException "Failed Scenarios:`n`t`t'$($TestResults.FailedScenarios.Name -join "'`n`t`t'")'"
        $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "FailedScenarios", "LimitsExceeded", $TestResults
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}

function package {
    [CmdletBinding()]
    param()

    Trace-Message "robocopy '$ReleasePath' '${OutputPath}\${ModuleName}' /MIR /NP "
    $null = robocopy $ReleasePath "${OutputPath}\${ModuleName}" /MIR /NP /LOG+:"$OutputPath\build.log"

    # Obviously this should be Publish-Module, but this works on appveyor
    $zipFile = Join-Path $OutputPath "${ModuleName}-${Version}.zip"
    Add-Type -assemblyname System.IO.Compression.FileSystem
    Remove-Item $zipFile -ErrorAction SilentlyContinue
    Trace-Message "ZIP    $zipFile"
    [System.IO.Compression.ZipFile]::CreateFromDirectory((Join-Path $OutputPath $ModuleName), $zipFile)

    # You can add other artifacts here
    ls $OutputPath -File
}

function Trace-Message {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [string]$Message,

        [switch]$AsWarning,

        [switch]$ResetTimer,

        [switch]$KillTimer,

        [Diagnostics.Stopwatch]$Stopwatch
    )
    begin {
        if($Stopwatch) {
            $Script:TraceTimer = $Stopwatch
            $Script:TraceTimer.Start()
        }
        if(!(Test-Path Variable:Script:TraceTimer)) {
            $Script:TraceTimer = New-Object System.Diagnostics.Stopwatch
            $Script:TraceTimer.Start()
        }
        if($ResetTimer)
        {
            $Script:TraceTimer.Restart()
        }
    }

    process {
        $Script = Split-Path $MyInvocation.ScriptName -Leaf
        $Command = (Get-PSCallStack)[1].Command
        if($Script -ne $Command) {
            $Message = "{0} - at {1} Line {2} ({4}) | {3}" -f $Message, $Script, $MyInvocation.ScriptLineNumber, $TraceTimer.Elapsed, $Command
        } else {
            $Message = "{0} - at {1} Line {2} | {3}" -f $Message, $Script, $MyInvocation.ScriptLineNumber, $TraceTimer.Elapsed
        }

        if($AsWarning) {
            Write-Warning $Message
        } else {
            Write-Verbose $Message
        }
    }

    end {
        if($KillTimer) {
            $Script:TraceTimer.Stop()
            $Script:TraceTimer = $null
        }
    }
}

# First call to Trace-Message, pass in our TraceTimer to make sure we time EVERYTHING.
Trace-Message "BUILDING: $ModuleName in $Path" -Stopwatch $TraceVerboseTimer

Push-Location $Path

init

foreach($s in $step){
    Trace-Message "Invoking Step: $s"
    &$s
}

Pop-Location
Trace-Message "FINISHED: $ModuleName in $Path" -KillTimer

================================================
FILE: RequiredModules.psd1
================================================
@{
    Configuration    = "[1.3.1,2.0)"
    Metadata         = "1.5.*"
    Pester           = "4.10.*"
    ModuleBuilder    = "2.0.*"
    PSScriptAnalyzer = "1.19.1"
}

================================================
FILE: Source/Configuration.psd1
================================================
@{

# Script module or binary module file associated with this manifest.
ModuleToProcess = 'Configuration.psm1'

# Version number of this module.
ModuleVersion = '1.5.0'

# ID used to uniquely identify this module
GUID = 'e56e5bec-4d97-4dfd-b138-abbaa14464a6'

# Author of this module
Author = @('Joel Bennett')

# Company or vendor of this module
CompanyName = 'HuddledMasses.org'

# Copyright statement for this module
Copyright = 'Copyright (c) 2014-2021 by Joel Bennett, all rights reserved.'

# Description of the functionality provided by this module
Description = 'A module for storing and reading configuration values, with full PS Data serialization, automatic configuration for modules and scripts, etc.'

# Exports - populated by the build
FunctionsToExport = @('*')
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @('Get-StoragePath', 'Get-ManifestValue', 'Update-Manifest')
RequiredModules = @('Metadata')

# List of all files packaged with this module
FileList = @('.\Configuration.psd1','.\Configuration.psm1')

PrivateData = @{
    # Allows overriding the default paths where Configuration stores it's configuration
    # Within those folders, the module assumes a "powershell" folder and creates per-module configuration folders
    PathOverride = @{
        # Where the user's personal configuration settings go.
        # Highest presedence, overrides all other settings.
        # Defaults to $Env:LocalAppData on Windows
        # Defaults to $Env:XDG_CONFIG_HOME elsewhere ($HOME/.config/)
        UserData       = ""
        # On some systems there are "roaming" user configuration stored in the user's profile. Overrides machine configuration
        # Defaults to $Env:AppData on Windows
        # Defaults to $Env:XDG_CONFIG_DIRS elsewhere (or $HOME/.local/share/)
        EnterpriseData = ""
        # Machine specific configuration. Overrides defaults, but is overriden by both user roaming and user local settings
        # Defaults to $Env:ProgramData on Windows
        # Defaults to /etc/xdg elsewhere
        MachineData    = ""
    }
    # PSData is module packaging and gallery metadata embedded in PrivateData
    # It's for the PoshCode and PowerShellGet modules
    # We had to do this because it's the only place we're allowed to extend the manifest
    # https://connect.microsoft.com/PowerShell/feedback/details/421837
    PSData = @{
        # The semver pre-release version information
        PreRelease = ''

        # Keyword tags to help users find this module via navigations and search.
        Tags = @('Development','Configuration','Settings','Storage')

        # The web address of this module's project or support homepage.
        ProjectUri = "https://github.com/PoshCode/Configuration"

        # The web address of this module's license. Points to a page that's embeddable and linkable.
        LicenseUri = "http://opensource.org/licenses/MIT"

        # Release notes for this particular version of the module
        ReleaseNotes = '
        - Extract the Metadata module
        - Add support for arbitrary AllowedVariables
        '
    }
}

}




================================================
FILE: Source/Header/param.ps1
================================================
# Allows you to override the Scope storage paths (e.g. for testing)
param(
    $Converters = @{},
    $EnterpriseData,
    $UserData,
    $MachineData
)

if ($Converters.Count) {
    Add-MetadataConverter $Converters
}


================================================
FILE: Source/Private/InitializeStoragePaths.ps1
================================================
function InitializeStoragePaths {
    [CmdletBinding()]
    param(
        $EnterpriseData,
        $UserData,
        $MachineData
    )

    $PathOverrides = $MyInvocation.MyCommand.Module.PrivateData.PathOverride

    # Where the user's personal configuration settings go.
    # Highest presedence, overrides all other settings.
    if ([string]::IsNullOrWhiteSpace($UserData)) {
        if (!($UserData = $PathOverrides.UserData)) {
            if ($IsLinux -or $IsMacOs) {
                # Defaults to $Env:XDG_CONFIG_HOME on Linux or MacOS ($HOME/.config/)
                if (!($UserData = $Env:XDG_CONFIG_HOME)) {
                    $UserData = Join-Path $HOME .config/
                }
            } else {
                # Defaults to $Env:LocalAppData on Windows
                if (!($UserData = $Env:LocalAppData)) {
                    $UserData = [Environment]::GetFolderPath("LocalApplicationData")
                }
            }
        }
    }

    # On some systems there are "roaming" user configuration stored in the user's profile. Overrides machine configuration
    if ([string]::IsNullOrWhiteSpace($EnterpriseData)) {
        if (!($EnterpriseData = $PathOverrides.EnterpriseData)) {
            if ($IsLinux -or $IsMacOs) {
                # Defaults to the first value in $Env:XDG_CONFIG_DIRS on Linux or MacOS (or $HOME/.local/share/)
                if (!($EnterpriseData = @($Env:XDG_CONFIG_DIRS -split ([IO.Path]::PathSeparator))[0] )) {
                    $EnterpriseData = Join-Path $HOME .local/share/
                }
            } else {
                # Defaults to $Env:AppData on Windows
                if (!($EnterpriseData = $Env:AppData)) {
                    $EnterpriseData = [Environment]::GetFolderPath("ApplicationData")
                }
            }
        }
    }

    # Machine specific configuration. Overrides defaults, but is overriden by both user roaming and user local settings
    if ([string]::IsNullOrWhiteSpace($MachineData)) {
        if (!($MachineData = $PathOverrides.MachineData)) {
            if ($IsLinux -or $IsMacOs) {
                # Defaults to /etc/xdg elsewhere
                $XdgConfigDirs = $Env:XDG_CONFIG_DIRS -split ([IO.Path]::PathSeparator) | Where-Object { $_ -and (Test-Path $_) }
                if (!($MachineData = if ($XdgConfigDirs.Count -gt 1) {
                            $XdgConfigDirs[1]
                        })) {
                    $MachineData = "/etc/xdg/"
                }
            } else {
                # Defaults to $Env:ProgramData on Windows
                if (!($MachineData = $Env:ProgramAppData)) {
                    $MachineData = [Environment]::GetFolderPath("CommonApplicationData")
                }
            }
        }
    }

    Join-Path $EnterpriseData powershell
    Join-Path $UserData powershell
    Join-Path $MachineData powershell
}

$EnterpriseData, $UserData, $MachineData = InitializeStoragePaths -EnterpriseData $EnterpriseData -UserData $UserData -MachineData $MachineData

================================================
FILE: Source/Private/ParameterBinder.ps1
================================================
function ParameterBinder {
    if (!$Module) {
        [System.Management.Automation.PSModuleInfo]$Module = . {
            $Command = ($CallStack)[0].InvocationInfo.MyCommand
            $mi = if ($Command.ScriptBlock -and $Command.ScriptBlock.Module) {
                $Command.ScriptBlock.Module
            } else {
                $Command.Module
            }

            if ($mi -and $mi.ExportedCommands.Count -eq 0) {
                if ($mi2 = Get-Module $mi.ModuleBase -ListAvailable | Where-Object { ($_.Name -eq $mi.Name) -and $_.ExportedCommands } | Select-Object -First 1) {
                    $mi = $mi2
                }
            }
            $mi
        }
    }

    if (!$CompanyName) {
        [String]$CompanyName = . {
            if ($Module) {
                $CName = $Module.CompanyName -replace "[$([Regex]::Escape(-join[IO.Path]::GetInvalidFileNameChars()))]", "_"
                if ($CName -eq "Unknown" -or -not $CName) {
                    $CName = $Module.Author
                    if ($CName -eq "Unknown" -or -not $CName) {
                        $CName = "AnonymousModules"
                    }
                }
                $CName
            } else {
                "AnonymousScripts"
            }
        }
    }

    if (!$Name) {
        [String]$Name = $(if ($Module) {
                $Module.Name
            } <# else { ($CallStack)[0].InvocationInfo.MyCommand.Name } #>)
    }

    if (!$DefaultPath -and $Module) {
        [String]$DefaultPath = $(if ($Module) {
                Join-Path $Module.ModuleBase Configuration.psd1
            })
    }
}


================================================
FILE: Source/Public/Export-Configuration.ps1
================================================
function Export-Configuration {
    <#
        .Synopsis
            Exports a configuration object to a specified path.
        .Description
            Exports the configuration object to a file, by default, in the Roaming AppData location

            NOTE: this exports the FULL configuration to this file, which will override both defaults and local machine configuration when Import-Configuration is used.
        .Example
            @{UserName = $Env:UserName; LastUpdate = [DateTimeOffset]::Now } | Export-Configuration

            This example shows how to use Export-Configuration in your module to cache some data.

        .Example
            Get-Module Configuration | Export-Configuration @{UserName = $Env:UserName; LastUpdate = [DateTimeOffset]::Now }

            This example shows how to use Export-Configuration to export data for use in a specific module.
    #>
    # PSSCriptAnalyzer team refuses to listen to reason. See bugs:  #194 #283 #521 #608
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Callstack', Justification = 'This is referenced in ParameterBinder')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Module', Justification = 'This is referenced in ParameterBinder')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'DefaultPath', Justification = 'This is referenced in ParameterBinder')]
    [CmdletBinding(DefaultParameterSetName = '__ModuleInfo', SupportsShouldProcess)]
    param(
        # Specifies the objects to export as metadata structures.
        # Enter a variable that contains the objects or type a command or expression that gets the objects.
        # You can also pipe objects to Export-Metadata.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        $InputObject,

        # Serialize objects as hashtables
        [switch]$AsHashtable,

        # A callstack. You should not ever pass this.
        # It is used to calculate the defaults for all the other parameters.
        [Parameter(ParameterSetName = "__CallStack")]
        [System.Management.Automation.CallStackFrame[]]$CallStack = $(Get-PSCallStack),

        # The Module you're importing configuration for
        [Parameter(ParameterSetName = "__ModuleInfo", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [System.Management.Automation.PSModuleInfo]$Module,


        # An optional module qualifier (by default, this is blank)
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Author")]
        [String]$CompanyName,

        # The name of the module or script
        # Will be used in the returned storage path
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [String]$Name,

        # DefaultPath is IGNORED.
        # The parameter was here to match Import-Configuration, but it is meaningless in Export-Configuration
        # The only reason I haven't removed it is that I don't want to break any code that might be using it.
        # TODO: If we release a breaking changes Configuration 2.0, remove this parameter
        [Parameter(ParameterSetName = "ManualOverride", ValueFromPipelineByPropertyName = $true)]
        [Alias("ModuleBase")]
        [String]$DefaultPath,

        # The scope to save at, defaults to Enterprise (which returns a path in "RoamingData")
        [Parameter(ParameterSetName = "ManualOverride")]
        [ValidateSet("User", "Machine", "Enterprise")]
        [string]$Scope = "Enterprise",

        # The version for saved settings -- if set, will be used in the returned path
        # NOTE: this is *NOT* calculated from the CallStack
        [Version]$Version
    )
    process {
        . ParameterBinder
        if (!$Name) {
            throw "Could not determine the storage name, Export-Configuration should only be called from inside a script or module, or by piping ModuleInfo to it."
        }

        $Parameters = @{
            CompanyName = $CompanyName
            Name        = $Name
        }
        if ($Version) {
            $Parameters.Version = $Version
        }

        $MachinePath = Get-ConfigurationPath @Parameters -Scope $Scope

        $ConfigurationPath = Join-Path $MachinePath "Configuration.psd1"

        $InputObject | Export-Metadata $ConfigurationPath -AsHashtable:$AsHashtable
    }
}


================================================
FILE: Source/Public/Get-ConfigurationPath.ps1
================================================
function Get-ConfigurationPath {
    #.Synopsis
    #   Gets an storage path for configuration files and data
    #.Description
    #   Gets an AppData (or roaming profile) or ProgramData path for configuration and data storage. The folder returned is guaranteed to exist (which means calling this function actually creates folders).
    #
    #   Get-ConfigurationPath is designed to be called from inside a module function WITHOUT any parameters.
    #
    #   If you need to call Get-ConfigurationPath from outside a module, you should pipe the ModuleInfo to it, like:
    #   Get-Module Powerline | Get-ConfigurationPath
    #
    #   As a general rule, there are three scopes which result in three different root folders
    #       User:       $Env:LocalAppData
    #       Machine:    $Env:ProgramData
    #       Enterprise: $Env:AppData (which is the "roaming" folder of AppData)
    #
    #.NOTES
    #   1.  This command is primarily meant to be used in modules, to find a place where they can serialize data for storage.
    #   2.  It's techincally possible for more than one module to exist with the same name.
    #       The command uses the Author or Company as a distinguishing name.
    #
    #.Example
    #   $CacheFile = Join-Path (Get-ConfigurationPath) Data.clixml
    #   $Data | Export-CliXML -Path $CacheFile
    #
    #   This example shows how to use Get-ConfigurationPath with Export-CliXML to cache data as clixml from inside a module.
    [Alias("Get-StoragePath")]
    # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Callstack', Justification = 'This is referenced in ParameterBinder')]
    # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Module', Justification = 'This is referenced in ParameterBinder')]
    [CmdletBinding(DefaultParameterSetName = '__ModuleInfo')]
    param(
        # The scope to save at, defaults to Enterprise (which returns a path in "RoamingData")
        [ValidateSet("User", "Machine", "Enterprise")]
        [string]$Scope = "Enterprise",

        # A callstack. You should not ever pass this.
        # It is used to calculate the defaults for all the other parameters.
        [Parameter(ParameterSetName = "__CallStack")]
        [System.Management.Automation.CallStackFrame[]]$CallStack = $(Get-PSCallStack),

        # The Module you're importing configuration for
        [Parameter(ParameterSetName = "__ModuleInfo", ValueFromPipeline = $true)]
        [System.Management.Automation.PSModuleInfo]$Module,

        # An optional module qualifier (by default, this is blank)
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Author")]
        [String]$CompanyName,

        # The name of the module or script
        # Will be used in the returned storage path
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [String]$Name,

        # The version for saved settings -- if set, will be used in the returned path
        # NOTE: this is *NOT* calculated from the CallStack
        [Version]$Version,

        # By default, Get-ConfigurationPath creates the folder if it doesn't already exist
        # This switch allows overriding that behavior: if set, does not create missing paths
        [Switch]$SkipCreatingFolder
    )
    begin {
        $PathRoot = $(switch ($Scope) {
                "Enterprise" {
                    $EnterpriseData
                }
                "User" {
                    $UserData
                }
                "Machine" {
                    $MachineData
                }
                # This should be "Process" scope, but what does that mean?
                # "AppDomain"  { $MachineData }
                default {
                    $EnterpriseData
                }
            })
        if (Test-Path $PathRoot) {
            $PathRoot = Resolve-Path $PathRoot
        } elseif (!$SkipCreatingFolder) {
            Write-Warning "The $Scope path $PathRoot cannot be found"
        }
    }

    process {
        . ParameterBinder

        if (!$Name) {
            Write-Error "Empty Name ($Name) in $($PSCmdlet.ParameterSetName): $($PSBoundParameters | Format-List | Out-String)"
            throw "Could not determine the storage name, Get-ConfigurationPath should only be called from inside a script or module."
        }
        $CompanyName = $CompanyName -replace "[$([Regex]::Escape(-join[IO.Path]::GetInvalidFileNameChars()))]", "_"
        if ($CompanyName -and $CompanyName -ne "Unknown") {
            $PathRoot = Join-Path $PathRoot $CompanyName
        }

        $PathRoot = Join-Path $PathRoot $Name

        if ($Version) {
            $PathRoot = Join-Path $PathRoot $Version
        }

        if (Test-Path $PathRoot -PathType Leaf) {
            throw "Cannot create folder for Configuration because there's a file in the way at $PathRoot"
        }

        if (!$SkipCreatingFolder -and !(Test-Path $PathRoot -PathType Container)) {
            $null = New-Item $PathRoot -Type Directory -Force
        }

        # Note: this used to call Resolve-Path
        $PathRoot
    }
}


================================================
FILE: Source/Public/Get-ParameterValue.ps1
================================================
function Get-ParameterValue {
    <#
        .SYNOPSIS
            Get parameter values from PSBoundParameters + DefaultValues and optionally, a configuration file
        .DESCRIPTION
            This function gives command authors an easy way to combine default parameter values and actual arguments.
            It also supports user-specified default parameter values loaded from a configuration file.

            It returns a hashtable (like PSBoundParameters) which combines these parameter defaults with parameter values passed by the caller.
    #>
    [CmdletBinding()]
    param(
        # The base name of a configuration file to read defaults from
        # If specified, the command will read a ".psd1" file with this name
        # Suggested Value: $MyInvocation.MyCommand.Noun
        [string]$FromFile,

        # If your configuration file has defaults for multiple commands, pass
        # the top-level key which contains defaults for this invocation
        [string]$CommandKey,

        # Allows extending the valid variables which are allowed to be referenced in configuration
        # BEWARE: This exposes the value of these variables in the calling context to the configuration file
        # You are reponsible to only allow variables which you know are safe to share
        [String[]]$AllowedVariables
    )

    $CallersInvocation = $PSCmdlet.SessionState.PSVariable.GetValue("MyInvocation")
    $BoundParameters = @{} + $CallersInvocation.BoundParameters
    $AllParameters = $CallersInvocation.MyCommand.Parameters

    if ($FromFile) {
        $FromFile = [IO.Path]::ChangeExtension($FromFile, ".psd1")
    }

    $FileDefaults = if ($FromFile -and (Test-Path $FromFile)) {
        $MetadataOptions = @{
            AllowedVariables = $AllowedVariables
            PSVariable       = $PSCmdlet.SessionState.PSVariable
            ErrorAction      = "SilentlyContinue"
        }
        Write-Debug "Importing $FromFile"
        $FileValues = Import-Metadata $FromFile @MetadataOptions
        if ($CommandKey) {
            $FileValues = $FileValues.$CommandKey
        }
        $FileValues
    } else {
        @{}
    }

    # Don't support getting common parameters from the config file
    $CommonParameters = [System.Management.Automation.Cmdlet]::CommonParameters +
                        [System.Management.Automation.Cmdlet]::OptionalCommonParameters

    # Layer the defaults below config below actual parameter values
    foreach ($parameter in $AllParameters.GetEnumerator().Where({ $_.Key -notin $CommonParameters })) {
        Write-Debug "  Parameter: $($parameter.key)"
        $key = $parameter.Key

        # Support parameter aliases in the config file by changing the alias to the parameter name
        # If the value is not in the file defaults AND was not set by the user ...
        if ($FromFile -and -not $FileDefaults.ContainsKey($key) -and -not $BoundParameters.ContainsKey($key)) {
            # Check if any of the aliases are in the file defaults
            Write-Debug "  Aliases: $($parameter.Value.Aliases -join ', ')"
            foreach ($k in @($parameter.Value.Aliases)) {
                if ($null -ne $k -and $FileDefaults.ContainsKey($k)) {
                    Write-Debug "    ... Update FileDefaults[$key] from $k"
                    $FileDefaults[$key] = $FileDefaults[$k]
                    $null = $FileDefaults.Remove($k)
                    break
                }
            }
        }

        # Bound parameter values > build.psd1 values > default parameters values
        if ($CallersInvocation) {
            # If it's in the file defaults (now) AND it was not already set at a higher precedence
            if ($FromFile -and $FileDefaults.ContainsKey($Parameter) -and -not ($BoundParameters.ContainsKey($Parameter))) {
                Write-Debug "Export $Parameter = $($FileDefaults[$Parameter])"
                $BoundParameters[$Parameter] = $FileDefaults[$Parameter]
                # Set the variable in the _callers_ SessionState as well as our return hashtable
                $PSCmdlet.SessionState.PSVariable.Set($Parameter, $FileDefaults[$Parameter])
            # If it's still NOT in the file defaults and was not already set, check if there's a default value
            } elseif (-not $FileDefaults.ContainsKey($key) -and -not $BoundParameters.ContainsKey($key)) {
                # Reading the current value of the $key variable returns either the bound parameter or the default
                if ($null -ne ($value = $PSCmdlet.SessionState.PSVariable.Get($key).Value)) {
                    Write-Debug "    From Default: $($BoundParameters[$key] -join ', ')"
                    if ($value -ne ($null -as $parameter.Value.ParameterType)) {
                        $BoundParameters[$key] = $value
                    }
                }
            # Otherwise, it was set by the user, or ...
            } elseif ($BoundParameters[$key]) {
                Write-Debug "    From Parameter: $($BoundParameters[$key] -join ', ')"
            # We'll set it from the file
            } elseif ($FileDefaults[$key]) {
                Write-Debug "    From File: $($FileDefaults[$key] -join ', ')"
                $BoundParameters[$key] = $FileDefaults[$key]
            }
        }
    }

    $BoundParameters
}


================================================
FILE: Source/Public/Import-Configuration.ps1
================================================
function Import-Configuration {
    #.Synopsis
    #   Import the full, layered configuration for the module.
    #.Description
    #   Imports the DefaultPath Configuration file, and then imports the Machine, Roaming (enterprise), and local config files, if they exist.
    #   Each configuration file is layered on top of the one before (so only needs to set values which are different)
    #.Example
    #   $Configuration = Import-Configuration
    #
    #   This example shows how to use Import-Configuration in your module to load the cached data
    #
    #.Example
    #   $Configuration = Get-Module Configuration | Import-Configuration
    #
    #   This example shows how to use Import-Configuration in your module to load data cached for another module
    #
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Callstack', Justification = 'This is referenced in ParameterBinder')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Module', Justification = 'This is referenced in ParameterBinder')]
    # [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'DefaultPath', Justification = 'This is referenced in ParameterBinder')]
    [CmdletBinding(DefaultParameterSetName = '__CallStack')]
    param(
        # A callstack. You should not ever pass this.
        # It is used to calculate the defaults for all the other parameters.
        [Parameter(ParameterSetName = "__CallStack")]
        [System.Management.Automation.CallStackFrame[]]$CallStack = $(Get-PSCallStack),

        # The Module you're importing configuration for
        [Parameter(ParameterSetName = "__ModuleInfo", ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [System.Management.Automation.PSModuleInfo]$Module,

        # An optional module qualifier (by default, this is blank)
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Author")]
        [String]$CompanyName,

        # The name of the module or script
        # Will be used in the returned storage path
        [Parameter(ParameterSetName = "ManualOverride", Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [String]$Name,

        # The full path (including file name) of a default Configuration.psd1 file
        # By default, this is expected to be in the same folder as your module manifest, or adjacent to your script file
        [Parameter(ParameterSetName = "ManualOverride", ValueFromPipelineByPropertyName = $true)]
        [Alias("ModuleBase")]
        [String]$DefaultPath,

        # The version for saved settings -- if set, will be used in the returned path
        # NOTE: this is *never* calculated, if you use version numbers, you must manage them on your own
        [Version]$Version,

        # If set (and PowerShell version 4 or later) preserve the file order of configuration
        # This results in the output being an OrderedDictionary instead of Hashtable
        [Switch]$Ordered,

        # Allows extending the valid variables which are allowed to be referenced in configuration
        # BEWARE: This exposes the value of these variables in the calling context to the configuration file
        # You are reponsible to only allow variables which you know are safe to share
        [String[]]$AllowedVariables
    )
    begin {
        # Write-Debug "Import-Configuration for module $Name"
    }
    process {
        . ParameterBinder

        if (!$Name) {
            throw "Could not determine the configuration name. When you are not calling Import-Configuration from a module, you must specify the -Author and -Name parameter"
        }

        $MetadataOptions = @{
            AllowedVariables = $AllowedVariables
            PSVariable       = $PSCmdlet.SessionState.PSVariable
            Ordered          = $Ordered
            ErrorAction      = "Ignore"
        }

        if ($DefaultPath -and (Test-Path $DefaultPath -Type Container)) {
            $DefaultPath = Join-Path $DefaultPath Configuration.psd1
        }

        $Configuration = if ($DefaultPath -and (Test-Path $DefaultPath)) {
            Import-Metadata $DefaultPath @MetadataOptions
        } else {
            @{}
        }
        # Write-Debug "Module Configuration: ($DefaultPath)`n$($Configuration | Out-String)"


        $Parameters = @{
            CompanyName = $CompanyName
            Name        = $Name
        }
        if ($Version) {
            $Parameters.Version = $Version
        }

        $MachinePath = Get-ConfigurationPath @Parameters -Scope Machine -SkipCreatingFolder
        $MachinePath = Join-Path $MachinePath Configuration.psd1
        $Machine = if (Test-Path $MachinePath) {
            Import-Metadata $MachinePath @MetadataOptions
        } else {
            @{}
        }
        # Write-Debug "Machine Configuration: ($MachinePath)`n$($Machine | Out-String)"


        $EnterprisePath = Get-ConfigurationPath @Parameters -Scope Enterprise -SkipCreatingFolder
        $EnterprisePath = Join-Path $EnterprisePath Configuration.psd1
        $Enterprise = if (Test-Path $EnterprisePath) {
            Import-Metadata $EnterprisePath @MetadataOptions
        } else {
            @{}
        }
        # Write-Debug "Enterprise Configuration: ($EnterprisePath)`n$($Enterprise | Out-String)"

        $LocalUserPath = Get-ConfigurationPath @Parameters -Scope User -SkipCreatingFolder
        $LocalUserPath = Join-Path $LocalUserPath Configuration.psd1
        $LocalUser = if (Test-Path $LocalUserPath) {
            Import-Metadata $LocalUserPath @MetadataOptions
        } else {
            @{}
        }
        # Write-Debug "LocalUser Configuration: ($LocalUserPath)`n$($LocalUser | Out-String)"

        $Configuration | Update-Object $Machine |
            Update-Object $Enterprise |
            Update-Object $LocalUser
    }
}


================================================
FILE: Source/Public/Import-ParameterConfiguration.ps1
================================================
function Import-ParameterConfiguration {
    <#
        .SYNOPSIS
            Loads a metadata file based on the calling command name and combines the values there with the parameter values of the calling function.
        .DESCRIPTION
            This function gives command authors and users an easy way to let the default parameter values of the command be set by a configuration file in the folder you call it from.

            Normally, you have three places to get parameter values from. In priority order, they are:
            - Parameters passed by the caller always win
            - The PowerShell $PSDefaultParameterValues hashtable appears to the function as if the user passed it
            - Default parameter values (defined in the function)

            If you call this command at the top of a function, it overrides (only) the default parameter values with

            - Values from a manifest file in the present working directory ($pwd)
        .EXAMPLE
            Given that you've written a script like:

            function New-User {
                [CmdletBinding()]
                param(
                    $FirstName,
                    $LastName,
                    $UserName,
                    $Domain,
                    $EMail,
                    $Department,
                    [hashtable]$Permissions
                )
                Import-ParameterConfiguration -Recurse
                # Possibly calculated based on (default) parameter values
                if (-not $UserName) { $UserName = "$FirstName.$LastName" }
                if (-not $EMail)    { $EMail = "$UserName@$Domain" }

                # Lots of work to create the user's AD account, email, set permissions etc.

                # Output an object:
                [PSCustomObject]@{
                    PSTypeName  = "MagicUser"
                    FirstName   = $FirstName
                    LastName    = $LastName
                    EMail       = $EMail
                    Department  = $Department
                    Permissions = $Permissions
                }
            }

            You could create a User.psd1 in a folder with just:

            @{ Domain = "HuddledMasses.org" }

            Now the following command would resolve the `User.psd1`
            And the user would get an appropriate email address automatically:

            PS> New-User Joel Bennett

            FirstName   : Joel
            LastName    : Bennett
            EMail       : Joel.Bennett@HuddledMasses.org

        .EXAMPLE
            Import-ParameterConfiguration works recursively (up through parent folders)

            That means it reads config files in the same way git reads .gitignore,
            with settings in the higher level files (up to the root?) being
            overridden by those in lower level files down to the WorkingDirectory

            Following the previous example to a ridiculous conclusion,
            we could automate creating users by creating a tree like:

            C:\HuddledMasses\Security\Admins\ with a User.psd1 in each folder:

            # C:\HuddledMasses\User.psd1:
            @{
                Domain = "HuddledMasses.org"
            }

            # C:\HuddledMasses\Security\User.psd1:
            @{
                Department = "Security"
                Permissions = @{
                    Access = "User"
                }
            }

            # C:\HuddledMasses\Security\Admins\User.psd1
            @{
                Permissions = @{
                    Access = "Administrator"
                }
            }

            And then switch to the Admins directory and run:

            PS> New-User Joel Bennett

            FirstName   : Joel
            LastName    : Bennett
            EMail       : Joel.Bennett@HuddledMasses.org
            Department  : Security
            Permissions : { Access = Administrator }

        .EXAMPLE
            Following up on our earlier example, let's look at a way to use that -FileName parameter.
            If you wanted to use a different configuration files than your Noun, you can pass the file name in.

            You could even use one of your parameters to generate the file name. If we modify the function like ...

            function New-User {
                [CmdletBinding()]
                param(
                    $FirstName,
                    $LastName,
                    $UserName,
                    $Domain,
                    $EMail,
                    $Department,
                    [hashtable]$Permissions
                )
                Import-ParameterConfiguration -FileName "${Department}User.psd1"
                # Possibly calculated based on (default) parameter values
                if (-not $UserName) { $UserName = "$FirstName.$LastName" }
                if (-not $EMail)    { $EMail = "$UserName@$Domain" }

                # Lots of work to create the user's AD account and email etc.
                [PSCustomObject]@{
                    PSTypeName = "MagicUser"
                    FirstName = $FirstName
                    LastName = $LastName
                    EMail      = $EMail
                    # Passthru for testing
                    Permissions = $Permissions
                }
            }

            Now you could create a `SecurityUser.psd1`

            @{
                Domain = "HuddledMasses.org"
                Permissions = @{
                    Access = "Administrator"
                }
            }

            And run:

            PS> New-User Joel Bennett -Department Security
    #>
    [CmdletBinding()]
    param(
        # The folder the configuration should be read from. Defaults to the current working directory
        [string]$WorkingDirectory = $pwd,
        # The name of the configuration file.
        # The default value is your command's Noun, with the ".psd1" extention.
        # So if you call this from a command named Build-Module, the noun is "Module" and the config $FileName is "Module.psd1"
        [string]$FileName,

        # If set, considers configuration files in the parent, and it's parent recursively
        [switch]$Recurse,

        # Allows extending the valid variables which are allowed to be referenced in configuration
        # BEWARE: This exposes the value of these variables in the calling context to the configuration file
        # You are reponsible to only allow variables which you know are safe to share
        [String[]]$AllowedVariables
    )

    $CallersInvocation = $PSCmdlet.SessionState.PSVariable.GetValue("MyInvocation")
    $BoundParameters = @{} + $CallersInvocation.BoundParameters
    $AllParameters = $CallersInvocation.MyCommand.Parameters.Keys
    if (-not $PSBoundParameters.ContainsKey("FileName")) {
        $FileName = "$($CallersInvocation.MyCommand.Noun).psd1"
    }

    $MetadataOptions = @{
        AllowedVariables = $AllowedVariables
        PSVariable       = $PSCmdlet.SessionState.PSVariable
        ErrorAction      = "SilentlyContinue"
    }

    do {
        $FilePath = Join-Path $WorkingDirectory $FileName

        Write-Debug "Initializing parameters for $($CallersInvocation.InvocationName) from $(Join-Path $WorkingDirectory $FileName)"
        if (Test-Path $FilePath) {
            $ConfiguredDefaults = Import-Metadata $FilePath @MetadataOptions

            foreach ($Parameter in $AllParameters) {
                # If it's in the defaults AND it was not already set at a higher precedence
                if ($ConfiguredDefaults.ContainsKey($Parameter) -and -not ($BoundParameters.ContainsKey($Parameter))) {
                    Write-Debug "Export $Parameter = $($ConfiguredDefaults[$Parameter])"
                    $BoundParameters.Add($Parameter, $ConfiguredDefaults[$Parameter])
                    # This "SessionState" is the _callers_ SessionState, not ours
                    $PSCmdlet.SessionState.PSVariable.Set($Parameter, $ConfiguredDefaults[$Parameter])
                }
            }
        }
        Write-Debug "Recurse:$Recurse -and $($BoundParameters.Count) of $($AllParameters.Count) Parameters and $WorkingDirectory"
    } while ($Recurse -and ($AllParameters.Count -gt $BoundParameters.Count) -and ($WorkingDirectory = Split-Path $WorkingDirectory))
}

================================================
FILE: Specs/Configuration.Steps.ps1
================================================
#requires -Module Configuration
#using module Configuration

$PSModuleAutoLoadingPreference = "None"
# Fix IsLinux on Windows PowerShell 5.x
if (!(Test-Path Variable:Global:IsLinux -ErrorAction SilentlyContinue)){
    $Global:IsLinux = $False
}

# NOTE THIS FAKE IMPLEMENTS THE INTERFACE DuckType style, WITHOUT SAYING SO
# Unfortunately, this C# class is what's actually used by the tests
# When it SHOULD be the PowerShell class below
Add-Type -TypeDefinition @'
using System;
using System.Collections;
public class TestClass : Hashtable {
    public string Name { get; set; }
    public string TestMetadata { get; set; }

    public TestClass() {
        TestMetadata = "@{ Values = @{ User = 'Jaykul' } Name = 'Joel' }";
    }

    public string ToPsMetadata() {
        return TestMetadata;
    }

    public void FromPsMetadata(string Metadata) {
        Metadata = System.Text.RegularExpressions.Regex.Replace(Metadata.Trim(), @"\s+", " ");
        if (Metadata != TestMetadata) {
            throw new ArgumentException("Metadata doesn't match expected value: [" + Metadata + "] [" + TestMetadata + "]");
        }
        Name = "Joel";
        Add("User", "Jaykul");
    }
}
'@

InModuleScope Pester {
    Import-Module Configuration
    class TestClass : Hashtable, IPsMetadataSerializable {
        [string]$Name

        [string] ToPsMetadata() {
            return ConvertTo-Metadata -InputObject @{
                Name   = $this.Name
                Values = @{ } + $this
            }
        }

        [void] FromPsMetadata([string]$Metadata) {
            $self = ConvertFrom-Metadata -InputObject $Metadata
            $this.PSBase.Name = $self.Name
            foreach ($key in $self.Values.Keys) {
                $null = $this.Add($key, $self.Values[$key])
            }
        }
    }
}

function global:GetModuleBase {
    $Module = Get-Module "Configuration" -ListAvailable |
        Sort-Object Version -Descending |
        Select-Object -First 1

    Import-Module "$($Module.ModuleBase)/Configuration.psd1" -Scope Global

    $Module.ModuleBase
}


Given 'the configuration module is imported on Linux:' {
    $ModuleBase = GetModuleBase
    Remove-Module "Configuration" -ErrorAction Ignore -Force
    if (!(Test-Path Variable:IsLinux -ErrorAction SilentlyContinue)){
        $Global:IsLinux = $True
        Import-Module $ModuleBase/Configuration.psd1 -Scope Global
        Remove-Variable IsLinux -Scope Global
    } elseif (!$IsLinux) {
        Set-Variable IsLinux $True -Force -Option ReadOnly, AllScope -Scope Global
        Import-Module $ModuleBase/Configuration.psd1 -Scope Global
        Set-Variable IsLinux $False -Force -Option ReadOnly, AllScope -Scope Global
    }
}

Given 'the configuration module is imported with testing paths on Linux:' {
    param($Table)
    $ModuleBase = GetModuleBase

    Copy-Item $ModuleBase/Configuration.psd1 -Destination $ModuleBase/Configuration.psd1.backup

    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.MachineData' -Value $Table.Machine
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.EnterpriseData' -Value $Table.Enterprise
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.UserData' -Value $Table.User

    Remove-Module "Configuration" -ErrorAction Ignore -Force
    if (!(Test-Path Variable:IsLinux -ErrorAction SilentlyContinue)) {
        $Global:IsLinux = $True
        Import-Module $ModuleBase/Configuration.psd1 -Scope Global
        Remove-Variable IsLinux
    } elseif (!$IsLinux) {
        Set-Variable IsLinux $True -Force -Option ReadOnly, AllScope -Scope Global
        Import-Module $ModuleBase/Configuration.psd1 -Scope Global
        Set-Variable IsLinux $False -Force -Option ReadOnly, AllScope -Scope Global
    } else {
        Import-Module $ModuleBase/Configuration.psd1 -Scope Global
    }

    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.MachineData' -Value ""
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.EnterpriseData' -Value ""
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.UserData' -Value ""

}

Given 'the configuration module is imported with testing paths:' {
    param($Table)
    $ModuleBase = GetModuleBase

    Copy-Item $ModuleBase/Configuration.psd1 -Destination $ModuleBase/Configuration.psd1.backup

    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.MachineData' -Value $Table.Machine
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.EnterpriseData' -Value $Table.Enterprise
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.UserData' -Value $Table.User

    Remove-Module "Configuration" -ErrorAction Ignore -Force
    Import-Module $ModuleBase/Configuration.psd1 -Scope Global

    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.MachineData' -Value ""
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.EnterpriseData' -Value ""
    Update-Metadata -Path $ModuleBase/Configuration.psd1 -PropertyName 'PrivateData.PathOverride.UserData' -Value ""
}

Given 'the configuration module is imported with a URL converter' {
    param($Table)
    $ModuleBase = GetModuleBase
    Remove-Module "Configuration" -ErrorAction Ignore -Force
    Import-Module $ModuleBase/Configuration.psd1 -Args @{
                [Uri] = { "Uri '$_' " }
                "Uri" = {
                    param([string]$Value)
                    [Uri]$Value
                }
            } -Scope Global
}

Given 'the configuration module is imported' {
    param($Table)
    $ModuleBase = GetModuleBase
    Remove-Module "Configuration" -ErrorAction Ignore -Force
    Import-Module $ModuleBase/Configuration.psd1 -Scope Global
}

Given 'the manifest module is imported' {
    param($Table)
    $ModuleBase = GetModuleBase
    Remove-Module "Configuration", Manifest
    Import-Module $ModuleBase/Manifest.psm1 -Scope Global
}

Given "a module with(?:\s+\w+ name '(?<name>.+?)'|\s+\w+ the company '(?<company>.+?)'|\s+\w+ the author '(?<author>.+?)')+" {
    param($name, $Company = "", $Author = "")

    $ModulePath = "TestDrive:/Modules/$name"
    Remove-Module $name -ErrorAction Ignore
    Remove-Item $ModulePath -Recurse -ErrorAction Ignore

    if(Test-Path $ModulePath -PathType Leaf) {
        throw "Cannot create folder for Configuration because there's a file in the way at '$ModulePath'"
    }
    if(!(Test-Path $ModulePath -PathType Container)) {
        $null = New-Item $ModulePath -Type Directory -Force
    }
    $Env:PSModulePath = $Env:PSModulePath + ";TestDrive:/Modules" -replace "(;TestDrive:/Modules)+?$", ";TestDrive:/Modules"

    Set-Content $ModulePath/${Name}.psm1 "
    `$Script:ConfigurationPath = Get-ConfigurationPath -Scope User -ErrorAction SilentlyContinue
    `$Script:Configuration = Import-Configuration -ErrorAction SilentlyContinue
    function GetConfiguration { `$Script:Configuration }
    function GetConfigurationPath { `$Script:ConfigurationPath }

    function GetStoragePath { Get-ConfigurationPath @Args }
    function ImportConfiguration { Import-Configuration }
    function ImportConfigVersion { Import-Configuration -Version 2.0 }
    filter ExportConfiguration { `$_ | Export-Configuration }
    filter ExportConfigVersion { `$_ | Export-Configuration -Version 2.0 }
    "

    New-ModuleManifest $ModulePath/${Name}.psd1 -RootModule ./${Name}.psm1 -Description "A Super Test Module" -Company $Company -Author $Author

    # New-ModuleManifest sets things even when we don't want it to:
    if(!$Author) {
        Set-Content $ModulePath/${Name}.psd1 ((Get-Content $ModulePath/${Name}.psd1) -Replace "^(Author.*)$", '#$1')
    }
    if(!$Company) {
        Set-Content $ModulePath/${Name}.psd1 ((Get-Content $ModulePath/${Name}.psd1) -Replace "^(Company.*)$", '#$1')
    }

    Import-Module $ModulePath/${Name}.psd1
}

Then "the user configuration path at load time should (\w+) (.+)$" {
    param($Comparator, $Path)

    [string[]]$Path = $Path -split "\s*and\s*" | %{ $_.Trim("['`"]") }

    $LocalStoragePath = GetConfigurationPath
    foreach($PathAssertion in $Path) {
        $LocalStoragePath -replace "\\", "/" | Should $Comparator $PathAssertion
    }
}

Then "the module's user path at load time should (\w+) (.+)$" {
    param($Comparator, $Path)

    [string[]]$Path = $Path -split "\s*and\s*" | %{ $_.Trim("['`"]") }

    $LocalStoragePath = GetConfigurationPath
    $LocalStoragePath = $LocalStoragePath -replace "C:[\\\/]etc", "/etc"
    $LocalStoragePath = $LocalStoragePath -replace "^$([regex]::escape($Home.TrimEnd("/\")))", "~"
    foreach ($PathAssertion in $Path) {
        $LocalStoragePath -replace "\\", "/" | Should $Comparator $PathAssertion
    }
}

When "the module's (\w+) path should (\w+) (.+)$" {
    param($Scope, $Comparator, $Path)

    [string[]]$Path = $Path -split "\s*and\s*" | %{ $_.Trim("['`"]") }

    foreach($PathAssertion in $Path) {
        try {
            # if you're not an administrator, you're going to get Access ... denied
            $LocalStoragePath = GetStoragePath -Scope $Scope
        } catch {
            # this would make most tests fail, because the folder won't exist
            $LocalStoragePath = GetStoragePath -Scope $Scope -SkipCreatingFolder
        }

        # This is because of the mock I wrote to test the linux logic on Windows
        if(!$IsLinux -and $PathAssertion -match "\^~?/") {
            $LocalStoragePath = $LocalStoragePath -replace "C:[\\\/]etc","/etc"
        }
        # This is just because I want to be able to write ~/ in the paths in tests instead of $Home/
        if ($PathAssertion -match "\^~?/") {
            $LocalStoragePath = $LocalStoragePath -replace "^$([regex]::escape($Home.TrimEnd("/\")))","~"
        }
        #Write-Host $LocalStoragePath -ForegroundColor Yellow
        $LocalStoragePath -replace "\\", "/" | Should $Comparator $PathAssertion
    }
}

Then "the script's (\w+) path should (\w+) (.+)$" {
    param($Scope, $Comparator, $Path)

    [string[]]$Path = $Path -split "\s*and\s*" | % { $_.Trim("['`"]") }

    $LocalStoragePath = iex "TestDrive:/${ScriptName}.ps1"
    foreach ($PathAssertion in $Path) {
        $LocalStoragePath -replace "\\","/" | Should $Comparator $PathAssertion
    }
}

When "the resulting path should (\w+) (.+)$" {
    param($Comparator, $Path)

    [string[]]$Path = $Path -split "\s*and\s*" | %{ $_.Trim("['`"]") }

    foreach($PathAssertion in $Path) {
        $folder -replace "\\", "/" | Should $Comparator $PathAssertion
    }
}

Given "a script with the name '(.+)' that calls Get-ConfigurationPath with no parameters" {
    param($name)
    Set-Content "TestDrive:/${name}.ps1" "Get-ConfigurationPath"
    $ScriptName = $Name
}

Given "a script with the name '(?<File>.+)' that calls Get-ConfigurationPath (?:-Name (?<Name>\w*) ?|-Author (?<Author>\w*) ?){2}" {
    param($File, $Name, $Author)
    Set-Content "TestDrive:/${File}.ps1" "Get-ConfigurationPath -Name $Name -Author $Author"
    $ScriptName = $File
}

Then "the script should throw an exception$" {
    { $LocalStoragePath = iex "TestDrive:/${ScriptName}.ps1" } | Should throw
}

When "the module's storage path should end with a version number if one is passed in" {
    (GetStoragePath -Version "2.0") -replace "\\", "/" | Should Match "/2.0$"
    (GetStoragePath -Version "4.0") -replace "\\", "/" | Should Match "/4.0$"
}

When "a settings hashtable" {
    param($hashtable)
    $Settings = iex "[ordered]$hashtable"
}

Given "a settings object" {
    param($hashtable)
    $Settings = iex "[PSCustomObject]$hashtable"
}

When "we update the settings with" {
    param($hashtable)
    $Update = if($hashtable) {
        iex $hashtable
    } else {
        $null
    }

    $Settings = $Settings | Update-Object $Update
}

When "we say (?<property>.*) is important and update with" {
    param([string[]]$property, $hashtable)
    $Update = if ($hashtable) {
        iex $hashtable
    }

    $Settings = $Settings | Update-Object -UpdateObject $Update -Important $property
}

Given "a (?:settings file|module manifest) named (\S+)(?:(?: in the (?<Scope>\S+) folder)|(?: for version (?<Version>[0-9.]+)))*" {
    param($fileName, $hashtable, $Scope = $null, $Version = $null)

    if ($Scope -in "current","parent") {
        $folder = "TestDrive:/Level1/Level2/"
    } elseif ($Scope -and $Version) {
        $folder = GetStoragePath -Scope $Scope -Version $Version
    } elseif ($Scope) {
        $folder = GetStoragePath -Scope $Scope
    } elseif ($Version) {
        $folder = GetStoragePath -Version $Version
    } elseif ($ModulePath -and (Test-Path "$ModulePath")) {
        $folder = $ModulePath
    } else {
        $folder = "TestDrive:/"
    }
    $SettingsFile = Join-Path $folder $fileName

    $Parent = Split-Path $SettingsFile
    if(Test-Path $Parent -PathType Leaf) {
        throw "Cannot create folder for Configuration because there's a file in the way at '$Parent'"
    }
    if(!(Test-Path $Parent -PathType Container)) {
        $null = New-Item $Parent -Type Directory -Force
    }
    if ($Scope -in "current","parent") {
        Push-Location "TestDrive:/Level1/Level2/"
    }
    if ($Scope -eq "parent") {
        $Parent = Split-Path $Parent
        $SettingsFile = Join-Path $Parent $fileName
    }
    # Write-Verbose "Creating $SettingsFile" -Verbose
    Set-Content $SettingsFile -Value $hashtable
}

Then "the settings object MyPath should match the file's path" {
    $Settings.MyPath | Convert-Path | Should Be (Convert-Path ${SettingsFile})
}

When "a settings hashtable with an? (.+) in it" {
    param($type)
    $Settings = @{
        UserName = $Env:UserName
    }

    switch($type) {
        "NULL" {
            $Settings.TestCase = $Null
        }
        "Enum" {
            $Settings.TestCase = [Security.PolicyLevelType]::Enterprise
        }
        "String" {
            $Settings.TestCase = "Test"
        }
        "Number" {
            $Settings.OneTestCase = 42
            $Settings.TwoTestCase = 42.9
        }
        "Array"  {
            $Settings.TestCase = "One", "Two", "Three"
        }
        "Boolean"  {
            $Settings.OneTestCase = $True
            $Settings.TwoTestCase = $False
        }
        "DateTime" {
            $Settings.TestCase = Get-Date
        }
        "DateTimeOffset" {
            $Settings.TestCase = [DateTimeOffset](Get-Date)
        }
        "GUID" {
            $Settings.TestCase = [GUID]::NewGuid()
        }
        "PSObject" {
            $Settings.TestCase = New-Object PSObject -Property @{ Name = $Env:UserName }
        }
        "PSCredential" {
            $Settings.TestCase = New-Object PSCredential @("UserName", (ConvertTo-SecureString -AsPlainText -Force -String "Password"))
        }
        "SecureString" {
            $Settings.TestCase = ConvertTo-SecureString -AsPlainText -Force -String "Password"
        }
        "ScriptBlock" {
            $Settings.TestCase = { Get-ChildItem }
        }
        "SwitchParameter" {
            $Settings.TestCase = [switch]$true
        }
        "Uri" {
            $Settings.TestCase = [Uri]"http://HuddledMasses.org"
        }
        "Hashtable" {
            $Settings.TestCase = @{ Key = "Value"; ANother = "Value" }
        }
        "ConsoleColor" {
            $Settings.TestCase = [ConsoleColor]::Red
        }
        default {
            throw "missing test type"
        }
    }
}

When "we add a converter for (.*) types" {
    param($Type)
    switch ($Type) {
        "Uri" {
            Add-MetadataConverter @{
                [Uri] = { "Uri '$_' " }
                "Uri" = {
                    param([string]$Value)
                    [Uri]$Value
                }
            }
        }
        default {
            throw "missing converter type"
        }
    }
}

When "we convert the settings to metadata" {
    $SettingsMetadata = ConvertTo-Metadata $Settings

    # # Write-Debug $SettingsMetadata
    $Wide = $Host.UI.RawUI.WindowSize.Width
    # Write-Verbose $SettingsMetadata
}

When "we export to a settings file named (.*)" {
    param($fileName)
    if(!$ModulePath -or !(Test-Path $ModulePath)) {
        $ModulePath = "TestDrive:/"
    }
    $SettingsFile = Join-Path $ModulePath $fileName
    $File = $Settings | Export-Metadata ${SettingsFile} -Passthru
    $File.FullName | Should Be (Convert-Path $SettingsFile)
}


When "we convert the metadata to an object" {
    $Settings = ConvertFrom-Metadata $SettingsMetadata

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}


When "we import the file to an object" {
    $Settings = Import-Metadata ${SettingsFile}

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}


When "we import the file with ordered" {
    $Settings = Import-Metadata ${SettingsFile} -Ordered

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}

When "we import the folder path" {
    $Settings = Import-Metadata (Split-Path ${SettingsFile})

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}

When "trying to import the file to an object should throw(.*)" {
    param([string]$Message)
    { $Settings = Import-Metadata ${SettingsFile} } | Should Throw $Message.trim()
}

When "the string version should (\w+)\s*(.*)?" {
    param($operator, $data)
    # Normalize line endings, because the module does:
    $meta = ($SettingsMetadata -replace "\r?\n","`n")
    $data = $data.trim('"''')  -replace "\r?\n","`n"
    # And then actually test it
    $meta | Should $operator $data
}

When "the settings file should (\w+)\s*(.*)?" {
    param($operator, $data)
    # Normalize line endings, because the module does:
    $data = [regex]::escape(($data -replace "\r?\n","`n")) -replace '\\n','\r?\n'
    if($operator -eq "Contain"){
        (Get-Content ${SettingsFile} -raw) -match $data | Should Be $True
    } else {
        ${SettingsFile} | Should $operator $data
    }
}

Given "the settings file does not exist" {
    #
    if(!$ModulePath -or !(Test-Path $ModulePath)) {
        $ModulePath = "TestDrive:/"
    }
    if(!${SettingsFile}) {
        $SettingsFile = Join-Path $ModulePath "NoSuchFile.psd1"
    }
    if(Test-Path $SettingsFile) {
        Remove-Item $SettingsFile
    }
}

Given "the configuration module exports IPsMetadataSerializable" {
    "IPsMetadataSerializable" -as [Type] | Should -Not -BeNullOrEmpty
    [IPsMetadataSerializable].IsInterface | Should -Be $true
}

Given "a TestClass that implements IPsMetadataSerializable" {
    # We're duck typing it, so no interface ...
    # [TestClass].ImplementedInterfaces |
    #     Where Name -eq IPsMetadataSerializable |
    #     Should -Not -BeNullOrEmpty
}

# This step will create verifiable/counting loggable mocks for Write-Warning, Write-Error, Write-Verbose
Given "we expect an? (?<type>warning|error|verbose) in the (?<module>.*) module" {
    param($type, $module)
    $ErrorModule = $module

    # The Metadata module hides itself a little bit
    if($Type -eq "Error" -and ($ErrorModule -eq "Metadata")) {
        Mock -Module $ErrorModule WriteError  { Write-Host "        WriteError: $Message" -Foreground Red } -Verifiable
    } else {
        Mock -Module $ErrorModule Write-$type { Write-Host "       Write-Error: $Message" -Foreground Red } -Verifiable
    }
}
# The error is logged exactly 1 time
# Then the error is logged exactly 2 times
# Then the warning is logged 3 times
# Then the error is logged
# this step lets us verify the number of calls to those three mocks
When "the (?<type>warning|error|verbose) is logged(?: (?<exactly>exactly) (\d+) times?)?" {
    param($count, $exactly, $type)
    $param = @{}
    if($count) {
        $param.Exactly = $Exactly -eq "Exactly"
        $param.Times = $count
    }

    if($Type -eq "Error" -and ($ErrorModule -eq "Metadata")) {
        Assert-MockCalled -Module $ErrorModule -Command WriteError @param
    } else {
        Assert-MockCalled -Module $ErrorModule -Command Write-$type @param
    }
}

When "we add a converter that's not a scriptblock" {
    Add-MetadataConverter @{
        "Uri" = "
            param([string]$Value)
            [Uri]$Value
        "
    }
}

When "we add a converter with a number as a key" {
    Add-MetadataConverter @{
        42 = {
            param([string]$Value)
            $Value
        }
    }
}

Then "the (?:settings|output) object should be of type (.*)" {
    param([Type]$Type)
    $Settings | Should BeOfType $Type
}

Then "the (?:settings|output) object should have (.*) in the PSTypeNames" {
    param([string]$Type)
    $Settings.PSTypeNames -eq $Type | Should Be $Type
}

Then "the (?:settings|output) object's (.*) should (be of type|be) (.*)" {
    param([String]$Parameter, [String]$operator, $Expected)
    $Value = $Settings
    Set-StrictMode -Off

    foreach($property in $Parameter.Split(".")) {
        $value = $value.$property
    }

    $operator = $operator -replace " "

    if($Operator -eq "be" -and $Expected -eq "null") {
        $value | Should BeNullOrEmpty
    } else {
        $value | Should $operator $Expected
    }
}

Then "Key (\d+) is (\w+)" {
    param([int]$index, [string]$name)
    $Settings.Keys | Select -Index $index | Should Be $Name
}

Given "a mock PowerShell version (.*)" {
    param($version)
    $PSVersion = [Version]$version
    $PSDefaultParameterValues."Test-PSVersion:Version" = $PSVersion
}

When "we fake version 2.0 in the Metadata module" {
    &(Get-Module Configuration) {
        &(Get-Module Metadata) {
            $PSDefaultParameterValues."Test-PSVersion:Version" = [Version]"2.0"
        }
    }
}

When "we're using PowerShell 4 or higher in the Metadata module" {
    &(Get-Module Configuration) {
        &(Get-Module Metadata) {
            $null = $PSDefaultParameterValues.Remove("Test-PSVersion:Version")
            $PSVersionTable.PSVersion -ge ([Version]"4.0") | Should Be $True
        }
    }
}

Given "the actual PowerShell version" {
    $PSVersion = $PSVersionTable.PSVersion
    $null = $PSDefaultParameterValues.Remove("Test-PSVersion:Version")
}

Then "the Version -(..) (.*)" {
    param($comparator, $version)

    if($version -eq "the version") {
        [Version]$version = $PSVersion
    } else {
        [Version]$version = $version
    }

    $test = @{ $comparator = $version }
    Test-PSVersion @test | Should Be $True
}

When "I call Import-Configuration" {
    $Settings = ImportConfiguration

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}

When "the ModuleInfo is piped to Import-Configuration" {
    $Settings = Get-Module SuperTestModule | Import-Configuration -ErrorAction Stop

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}

When "the ModuleInfo is piped to Get-ConfigurationPath" {
    $folder = Get-Module SuperTestModule | Get-ConfigurationPath -ErrorAction Stop
}

When "I call Import-Configuration with a Version" {
    $Settings = ImportConfigVersion

    Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n")
}

When "I call Export-Configuration with" {
    param($configuration)
    iex "$configuration" | ExportConfiguration
}

When "I call Export-Configuration with a Version" {
    param($configuration)
    iex "$configuration" | ExportConfigVersion
}

When "I call Get-Metadata (\S+)(?: (\S+))?" {
    param($path, $name)
    Push-Location $ModulePath
    try {
        if($name) {
            $Result = Get-Metadata $path $name
        } else {
            $Result = Get-Metadata $path
        }
    } finally {
        Pop-Location
    }
}

When "I call Update-Metadata (\S+)(?: (\S+))?" {
    param($path, $name)
    Push-Location $ModulePath
    try {
        if($name) {
            $Result = Update-Metadata $path $name
        } else {
            $Result = Update-Metadata $path
        }
    } finally {
        Pop-Location
    }
}

When "I call Update-Metadata (\S+) -Increment (\S+)" {
    param($path, $name)
    Push-Location $ModulePath
    try {
        $Result = Update-Metadata $path -Increment $name
    } finally {
        Pop-Location
    }
}

Then "the result should be @\((.*)\)" {
    param($value)
    @($Result).ForEach{ "'$_'" } -join ", " | Should Be $value
}

Then "the result should be (?!@|`")(.*)" {
    param($value)
    $Result | Should Be $value
}

Then "the string result should be \`"(.*)\`"" {
    param($value)
    "$Result" | Should Be $value
}

Then "a settings file named (\S+) should exist(?:(?: in the (?<Scope>\S+) folder)|(?: for version (?<Version>[0-9.]+)))*" {
    param($fileName, $hashtable, $Scope = $null, $Version = $null)

    if($Scope -and $Version) {
        $folder = GetStoragePath -Scope $Scope -Version $Version
    } elseif($Scope) {
        $folder = GetStoragePath -Scope $Scope
    } elseif($Version) {
        $folder = GetStoragePath -Version $Version
    } elseif(Test-Path "${ModulePath}") {
        $folder = $ModulePath
    } else {
        $folder = "TestDrive:/"
    }
    $SettingsFile = Join-Path $folder $fileName
    $SettingsFile | Should Exist
}

Given "a passthru command '(?<Command>[A-Z][a-z]+-[A-Z][a-z]+)' with (?<Parameters>.*) parameters" {
    param($Command, $Parameters)

    [string[]]$Parameters = $Parameters -split "\s*and\s*" | % { $_.Trim("['`"]") }

    $Function = "
    function $Command {
        param(`$$($Parameters -join ", `$"))
        `$global:DebugPreference = 'Continue'
        Import-ParameterConfiguration
        @{  $(foreach ($name in $Parameters) {
                "`n            $Name = `$$Name"
            })
        }
        `$global:DebugPreference = 'SilentlyContinue'
    }
    "
    Invoke-Expression $Function
}

Given "a passthru command '(?<Command>[A-Z][a-z]+-[A-Z][a-z]+)' with (?<Parameters>.*) parameters that calls Get-ParameterValue(?<FromFile> with a file config)?" {
    param($Command, $Parameters, $FromFile)

    [string[]]$Parameters = $Parameters -split "\s*and\s*"

    $Function = "
    function $Command {
        param(
            `$$($Parameters -join ", `$"),
            [Alias('Alias')]
            `$ExtraParameter
        )
        `$global:DebugPreference = 'Continue'
        Get-ParameterValue $(if($FromFile){ "-FromFile Verb.psd1" })
        `$global:DebugPreference = 'SilentlyContinue'
    }
    "
    Invoke-Expression $Function
}

Given "an example New-User command" {
    function New-User {
        [CmdletBinding()]
        param(
            $FirstName,
            $LastName,
            $UserName,
            $Domain,
            $EMail,
            $Department,
            [hashtable]$Permissions
        )
        Import-ParameterConfiguration -Recurse -FileName "${Department}User.psd1"
        # Possibly calculated based on (default) parameter values
        if (-not $UserName) { $UserName = "$FirstName.$LastName" }
        if (-not $EMail)    { $EMail = "$UserName@$Domain" }

        # Lots of work to create the user's AD account, email, set permissions etc.

        # Output an object:
        [PSCustomObject]@{
            PSTypeName  = "MagicUser"
            FirstName   = $FirstName
            LastName    = $LastName
            EMail       = $EMail
            Department  = $Department
            Permissions = $Permissions
        }
    }
}


When "I call (Test-Verb|New-User)(?<Parameters> .*)?" {
    param($Command, $Parameters)
    try {
        # Write-Verbose "$Command $Parameters" -Verbose
        # $global:DebugPreference = 'Continue'

        $Settings = Invoke-Command ([ScriptBlock]::Create("$Command $Parameters"))

        # $global:DebugPreference = 'SilentlyContinue'
        # Write-Verbose (($Settings | Out-String -Stream | % TrimEnd) -join "`n") -Verbose
    } finally {
        Pop-Location
    }
}

Given "we define (?<Name>[\:\w]+) = (?<Value>.*)" {
    param($Name, $Value)
    if ($Name -match "^env:") {
        Set-Content $Name $Value
    } else {
        # we shouldn't have to set it global, but because of pester ...
        Set-Variable $Name $Value -Scope Global
    }
}

When "we import the file allowing variables (?<Variables>.*)" {
    param($Variables)
    $Variables = $Variables.Trim() -split "\s*,\s*"
    $Settings = Import-Metadata ${SettingsFile} -AllowedVariables $Variables

    Write-Verbose (($Settings | Out-String -Stream | ForEach-Object TrimEnd) -join "`n")
}

================================================
FILE: Specs/Configuration.feature
================================================
Feature: Module Configuration
    As a PowerShell Module Author
    I need to be able to store settings
    And override them per-user

    Background:
        Given the configuration module is imported with testing paths:
        | Enterprise                | User                | Machine                |
        | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |

    @Modules @Import
    Scenario: Loading Default Settings
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be of type String
        And the settings object's Age should be of type Int32


    @Modules @EndUsers
    Scenario: End users should be able to read the configuration data for a module
        Given a module with the name 'SuperTestModule' by the company 'PoshCode' and the author 'Jaykul'
        And a settings file named Configuration.psd1
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        When the ModuleInfo is piped to Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be of type String
        And the settings object's Age should be of type Int32

    @Modules @Import @EndUsers
    Scenario: End users should be able to work with configuration data outside the module
        Given a module with the name 'SuperTestModule'
        And a settings file named Configuration.psd1 in the Enterprise folder
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        When the ModuleInfo is piped to Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be of type String
        And the settings object's Age should be of type Int32

    @Modules @Import
    Scenario: SxS Versions
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1 in the Enterprise folder
            """
            @{
              FullName = 'John Smith'
              BirthDay = @{
                Month = 'December'
                Day = 22
              }
            }
            """
        And a settings file named Configuration.psd1 in the Enterprise folder for version 2.0
            """
            @{
              FullName = 'Joel Bennett'
              UserName = 'Jaykul'
              Birthday = @{
                Month = 'May'
              }
            }
            """
        When I call Import-Configuration with a version
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's BirthDay should be of type hashtable
        And the settings object's BirthDay.Month should be May
        And the settings object's BirthDay.Day should be null

    @Modules @Export
    Scenario: Exporting creates the expected files
        Given a module with the name 'MyModule1' and the author 'Bob'
        And a settings file named Configuration.psd1
            """
            @{
              FullName = 'John Smith'
              UserName = 'Jaykul'
              BirthDay = @{
                Month = 'December'
                Day = 22
              }
            }
            """
        When I call Export-Configuration with
            """
            @{
              FullName = 'Joel Bennett'
              Birthday = @{
                Month = 'May'
              }
            }
            """
        Then a settings file named Configuration.psd1 should exist in the Enterprise folder
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's BirthDay should be of type hashtable
        And the settings object's BirthDay.Month should be May
        And the settings object's BirthDay.Day should be 22


    @Modules @Export
    Scenario: Exporting supports versions
        Given a module with the name 'MyModule1' and the author 'Bob'
        And a settings file named Configuration.psd1
            """
            @{
              FullName = 'John Smith'
              UserName = 'Jaykul'
              BirthDay = @{
                Month = 'December'
                Day = 22
              }
            }
            """
        When I call Export-Configuration with a version
            """
            @{
              FullName = 'Joel Bennett'
              Birthday = @{
                Month = 'May'
              }
            }
            """
        Then a settings file named Configuration.psd1 should exist in the Enterprise folder for version 2.0

        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's FullName should be John Smith
        And the settings object's BirthDay.Month should be December

        When I call Import-Configuration with a version
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's BirthDay should be of type hashtable
        And the settings object's BirthDay.Month should be May
        And the settings object's BirthDay.Day should be 22

    # @WIP
    # Scenario: Migrate settings only once
    #     Given MyModule has a new version
    #     And I have some settings from an old version
    #     When I load the settings in the new module
    #     Then the settings from the old version should be copied
    #     And MyModule should be able to migrate them
    #     But they should save only to the new version




================================================
FILE: Specs/ConfiguredParameters.feature
================================================
Feature: Configure Command From Working Directory

    There is a command to support loading default parameter values from the working directory

    Background:
        Given the configuration module is imported with testing paths:
            | Enterprise                | User                | Machine                |
            | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |

    @Functions @Import
    Scenario: Loading Default Settings
        Given a passthru command 'Test-Verb' with UserName and Age parameters
        And a settings file named Verb.psd1 in the current folder
            """
            @{
            UserName = 'Joel'
            Age = 42
            }
            """
        When I call Test-Verb
        Then the output object's userName should be Joel
        And the output object's Age should be 42

    @Functions @Import
    Scenario: Overriding Default Settings
        Given a passthru command 'Test-Verb' with UserName and Age parameters
        And a settings file named Verb.psd1 in the current folder
            """
            @{
            UserName = 'Joel'
            Age = 42
            }
            """
        When I call Test-Verb Mark
        Then the output object's userName should be Mark
        And the output object's Age should be 42

    @Functions @Import
    Scenario: Overriding Default Settings Works on any Parameter
        Given a passthru command 'Test-Verb' with UserName and Age parameters
        And a settings file named Verb.psd1 in the current folder
            """
            @{
            UserName = 'Joel'
            Age = 42
            }
            """
        When I call Test-Verb -Age 10
        Then the output object's userName should be Joel
        And the output object's Age should be 10

    @Functions @Import
    Scenario: New-User Example
        Given an example New-User command
        And a settings file named User.psd1 in the current folder
            """
            @{
                Domain = 'HuddledMasses.org'
            }
            """
        When I call New-User Joel Bennett
        Then the output object's EMail should be Joel.Bennett@HuddledMasses.org

    @Functions @Import
    Scenario: New-User Example Two (overwriting)
        Given an example New-User command
        And a settings file named User.psd1 in the current folder
            """
            @{
                Permissions = @{
                    Access = "Administrator"
                }
            }
            """
        And a settings file named User.psd1 in the parent folder
            """
            @{
                Department = "Security"
                Permissions = @{
                    Access = "User"
                }
            }
            """
        And a settings file named User.psd1
            """
            @{
                Domain = "HuddledMasses.org"
            }
            """
        When I call New-User Joel Bennett
        Then the output object's EMail should be Joel.Bennett@HuddledMasses.org
        And the output object's Department should be Security
        And the output object's Permissions should be of type [hashtable]
        And the output object's Permissions.Access should be Administrator


    @Functions @Import
    Scenario: New-User Example Three
        Given an example New-User command
        And a settings file named SecurityUser.psd1 in the current folder
            """
            @{
                Domain = 'HuddledMasses.org'
                Permissions = @{
                    Access = "Administrator"
                }
            }
            """
        When I call New-User Joel Bennett -Department Security
        Then the output object's EMail should be Joel.Bennett@HuddledMasses.org
        And the output object's Permissions should be of type [hashtable]
        And the output object's Permissions.Access should be of type [string]
        And the output object's Permissions.Access should be Administrator

================================================
FILE: Specs/DefaultParameters.feature
================================================
Feature: Get PSBoundParameters plus default values plus a config file

    There is a command to support merging PSBoundParameters with parmeter default values
    That command supports overwriting the default values with values from a config file

    Background:
        Given the configuration module is imported

    @Functions @ParameterValue
    Scenario: Loading Default Settings
        Given a passthru command 'Test-Verb' with UserName and Age=42 parameters that calls Get-ParameterValue
        When I call Test-Verb Joel
        Then the output object's UserName should be Joel
        And the output object's Age should be 42

    @Functions @ParameterValue
    Scenario: Loading Default Settings
        Given a passthru command 'Test-Verb' with UserName and Age=12 parameters that calls Get-ParameterValue
        When I call Test-Verb Joel
        Then the output object's UserName should be Joel
        And the output object's Age should be 12

    @Functions @ParameterValue
    Scenario: Overriding Default Settings
        Given a passthru command 'Test-Verb' with UserName='Sarah' and Age=12 parameters that calls Get-ParameterValue
        When I call Test-Verb Joel 24
        Then the output object's UserName should be Joel
        And the output object's Age should be 24

    @Functions @ParameterValue
    Scenario: Configuration file
        Given a passthru command 'Test-Verb' with UserName='Sarah' and Age=12 parameters that calls Get-ParameterValue with a file config
        And a settings file named Verb.psd1 in the current folder
            """
            @{
            UserName = 'Joel'
            Age = 42
            }
            """
        When I call Test-Verb -Age 10
        Then the output object's userName should be Joel
        And the output object's Age should be 10

    @Functions @ParameterValue
    Scenario: Configuration file with aliases
        Given a passthru command 'Test-Verb' with UserName='Sarah' and Age=12 parameters that calls Get-ParameterValue with a file config
        And a settings file named Verb.psd1 in the current folder
            """
            @{
            UserName = 'Joel'
            Age = 42
            Alias = 'Supports Aliases'
            }
            """
        When I call Test-Verb -Age 10
        Then the output object's userName should be Joel
        And the output object's Age should be 10
        And the output object's ExtraParameter should be Supports Aliases


================================================
FILE: Specs/Layering.feature
================================================
Feature: Multiple settings files should layer
    As a module author, I want to distribute a default config with my module so that by default it has settings
    As a machine administrator, I want to save corporate defaults so that users start with the right configuration
    As a user I want to save my own preferences because those other guys are frequently wrong
    And our custom settings shouldn't be overwritten on upgrade

    Background:
        Given the configuration module is imported with testing paths:
        | Enterprise                | User                | Machine                |
        | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |


    @Modules @Import @Layering
    Scenario: Loading LocalMachine Overrides
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        And a settings file named Configuration.psd1 in the Machine folder
            """
            @{
              UserName = 'Joel Bennett'
            }
            """
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Joel Bennett
        And the settings object's Age should be 42

    @Modules @Import @Layering
    Scenario: Multi-level Overrides
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1
            """
            @{
              FullName = 'John Smith'
              BirthDay = @{
                Month = 'December'
                Day = 22
              }
            }
            """
        And a settings file named Configuration.psd1 in the Enterprise folder
            """
            @{
              FullName = 'Joel Bennett'
              UserName = 'Jaykul'
              Birthday = @{
                Month = 'May'
              }
            }
            """
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's BirthDay should be of type hashtable
        And the settings object's BirthDay.Month should be May
        And the settings object's BirthDay.Day should be 22

    @Modules @Import @Layering
    Scenario: Object Property Overrides
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1
            """
            @{
              FullName = 'John Smith'
              BirthDay = PSObject @{
                Month = 'December'
                Day = 25
              }
            }
            """
        And a settings file named Configuration.psd1 in the Enterprise folder
            """
            @{
              FullName = 'Joel Bennett'
              UserName = 'Jaykul'
              Birthday = @{
                Month = 'May'
              }
            }
            """
        And a settings file named Configuration.psd1 in the User folder
            """
            @{
              FullName = 'Joel Bennett'
              UserName = 'Jaykul'
              Birthday = PSObject @{
                Day = 22
              }
            }
            """
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's BirthDay should be of type PSCustomObject
        And the settings object's BirthDay.Month should be May
        And the settings object's BirthDay.Day should be 22

    @Modules @Import @Layering
    Scenario: Loading User Overrides
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1
            """
            @{
              UserName = 'John Smith'
              Age = 24
            }
            """
        And a settings file named Configuration.psd1 in the Machine folder
            """
            @{
              UserName = 'Jaykul'
            }
            """
        And a settings file named Configuration.psd1 in the Enterprise folder
            """
            @{
              Age = 42
            }
            """
        And a settings file named Configuration.psd1 in the User folder
            """
            @{
              FullName = 'Joel Bennett'
            }
            """
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's Age should be 42

    @Modules @Import @Layering
    Scenario: Multi-level Overrides
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1 in the Enterprise folder
            """
            @{
              FullName = 'Joel Bennett'
              UserName = 'Jaykul'
              Birthday = @{
                Month = 'May'
                Day = 22
              }
            }
            """
        When I call Import-Configuration
        Then the settings object should be of type hashtable
        And the settings object's UserName should be Jaykul
        And the settings object's FullName should be Joel Bennett
        And the settings object's BirthDay should be of type hashtable
        And the settings object's BirthDay.Month should be May
        And the settings object's BirthDay.Day should be 22

    @Layering @WIP
    Scenario: Save shouldn't overwrite default settings
        Given a module with the name 'MyModule1'
        And a settings file named Configuration.psd1
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        When I call Export-Configuration with
            """
            @{
              UserName = 'Joel Bennett'
            }
            """
        # The settings file should not be changed:
        Then the settings file should contain
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """



================================================
FILE: Specs/LocalStoragePath.feature
================================================
@StoragePath
Feature: Automatically Calculate Local Storage Paths
    In order for module settings to survive upgrades
    A PowerShell Module Author
    Needs a place outside their module to save settings
    For developer guidelines, see: http://msdn.microsoft.com/en-us/library/windows/apps/hh465094.aspx
    We create a module-specific storage location inside the operating-system specified data paths:
    By default, we store in the $Env:AppData user roaming path that Windows synchronizes (C:/Users/USERNAME/AppData/Roaming)
    But we  support using the $Env:ProgramData machine-local path instead (C:/ProgramData)
    As well as the machine-specific $Env:LocalAppData user data path (C:/Users/USERNAME/AppData/Local)

    Background:
        Given the configuration module is imported with testing paths:
        | Enterprise                | User                | Machine                |
        | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |

    @Scripts
    Scenario: Scripts will fail unless they specify the names
         Given a script with the name 'SuperTestScript' that calls Get-ConfigurationPath with no parameters
         Then the script should throw an exception
         Given a script with the name 'SuperTestScript' that calls Get-ConfigurationPath -Name TestScript -Author Author
         Then the script's Enterprise path should match '^TestDrive:/EnterprisePath/' and 'Author/TestScript'

    @Modules
    Scenario Outline: Modules storage paths work at load time
        Given a module with the name 'SimpleTest' by the author 'Joel Bennett'
        Then the module's user path at load time should match '^TestDrive:/UserPath/' and '/Joel Bennett/SimpleTest$'
        And the module's user path should exist already

    @Modules
    Scenario Outline: Modules get automatic storage paths
        Given a module with the name '<modulename>' by the author 'Joel Bennett'
        Then the module's Enterprise path should match '^TestDrive:/EnterprisePath/' and '/Joel Bennett/<modulename>$'
        And the module's Enterprise path should exist already
        """
        There is a <modulename>
        """

        Examples: A few different module names
            | modulename        |
            | SuperTestModule   |
            | AnotherTestModule |
            | ThirdModuleName   |

    @Modules
    Scenario Outline: Modules get automatic storage paths on Linux
        Given a module with the name '<modulename>' by the author 'Joel Bennett'
        Then the module's Enterprise path should match '^TestDrive:/EnterprisePath/' and '/Joel Bennett/<modulename>$'
        And the module's Enterprise path should exist already
        """
        There is a <modulename>
        """

        Examples: A few different module names
            | modulename        |
            | SuperTestModule   |
            | AnotherTestModule |
            | ThirdModuleName   |

    @Modules
    Scenario Outline: There should be a way to store settings at the Machine and User scope too
        Given a module with the name '<modulename>' with the author ''
        Then the module's <scope> path should match '^<rootpattern>' and '/AnonymousModules/<modulename>$'
        And the module's <scope> path should exist already

        Examples:
            | scope      | modulename      | rootpattern               |
            | Enterprise | SuperTestModule | TestDrive:/EnterprisePath |
            | Machine    | SuperTestModule | TestDrive:/MachinePath    |
            | User       | SuperTestModule | TestDrive:/UserPath       |

    @Modules
    Scenario Outline: To allow us to upgrade, settings should be versionable
        Given a module with the name 'SuperTestModule' by the author 'Joel'
        Then the module's Enterprise path should match '^TestDrive:/EnterprisePath/' and '/Joel/SuperTestModule$'
        But the module's storage path should end with a version number if one is passed in

    @Modules
    Scenario Outline: To support differentiation, settings should support a company name instead
        Given a module with the name 'SuperTestModule' by the company 'PoshCode' and the author 'Jaykul'
        Then the module's Enterprise path should match '^TestDrive:/EnterprisePath/' and '/PoshCode/SuperTestModule$'
        And the module's storage path should end with a version number if one is passed in

    @Modules @EndUsers
    Scenario: End users should be able to find the storage path for a module
        Given a module with the name 'SuperTestModule' by the company 'PoshCode' and the author 'Jaykul'
        When the ModuleInfo is piped to Get-ConfigurationPath
        Then the resulting path should match '^TestDrive:/EnterprisePath/' and '/PoshCode/SuperTestModule$'


================================================
FILE: Specs/LocalStoragePathLinux.feature
================================================
@StoragePath
Feature: Automatically Calculate Local Storage Paths on Linux
    In order for module settings to survive upgrades
    A PowerShell Module Author
    Needs a place outside their module to save settings
    For developer guidelines, see: http://msdn.microsoft.com/en-us/library/windows/apps/hh465094.aspx
    We create a module-specific storage location inside the operating-system specified data paths:
    By default, we store in the $Env:AppData user roaming path that Windows synchronizes (C:/Users/USERNAME/AppData/Roaming)
    But we  support using the $Env:ProgramData machine-local path instead (C:/ProgramData)
    As well as the machine-specific $Env:LocalAppData user data path (C:/Users/USERNAME/AppData/Local)

    @Modules @Linux
    Scenario Outline: On Linux the default configuration paths are different
        Given the configuration module is imported on Linux:
        Given a module with the name '<modulename>' with the author 'Jaykul'
        Then the module's <scope> path should match '^<rootpattern>' and '/Jaykul/<modulename>$'
        # And the module's <scope> path should exist already

        Examples:
            | scope      | modulename      | rootpattern    |
            | Enterprise | SuperTestModule | ~/.local/share |
            | Machine    | SuperTestModule | /etc/xdg       |
            | User       | SuperTestModule | ~/.config      |

    @Modules @Linux
    Scenario Outline: Modules storage paths work at load time on Linux
        Given the configuration module is imported on Linux:
        Given a module with the name 'SimpleTest' by the author 'Joel Bennett'
        Then the module's user path at load time should match '^~/.config' and '/Joel Bennett/SimpleTest$'
        And the module's user path should exist already

    @Modules @Linux
    Scenario Outline: There should be a way to store settings at the Machine and User scope on Linux too
        Given the configuration module is imported with testing paths on Linux:
        | Enterprise                | User                | Machine                |
        | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |
        Given a module with the name '<modulename>' with the author ''
        Then the module's <scope> path should match '^<rootpattern>' and '/AnonymousModules/<modulename>$'
        And the module's <scope> path should exist already

        Examples:
            | scope      | modulename      | rootpattern               |
            | Enterprise | SuperTestModule | TestDrive:/EnterprisePath |
            | Machine    | SuperTestModule | TestDrive:/MachinePath    |
            | User       | SuperTestModule | TestDrive:/UserPath       |


================================================
FILE: Specs/Manifest.feature
================================================
Feature: Manifest Read and Write
    As a PowerShell Module Author
    I want to easily edit my manifest as part of my build script

    Background:
        Given the configuration module is imported with testing paths:
        | Enterprise                | User                | Machine                |
        | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |

    @Modules @Import
    Scenario: Read ModuleVersion from a module manifest by default
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'

                # ID used to uniquely identify this module
                GUID = 'e56e5bec-4d97-4dfd-b138-abbaa14464a6'
            }
            """
        When I call Get-Metadata ModuleName.psd1
        Then the result should be 0.4

    Scenario: Read a named value from a module manifest
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'

                Description = 'This is the day'
            }
            """
        When I call Get-Metadata ModuleName.psd1 Description
        Then the result should be This is the day

    Scenario: Read a named value from a module manifest PrivateData
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'

                PrivateData = @{
                    MyVeryOwnKey = "Test Value"
                }
            }
            """
        When I call Get-Metadata ModuleName.psd1 MyVeryOwnKey
        Then the result should be Test Value

    Scenario: Read the release notes from a module manifest PSData
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'

                PrivateData = @{
                    MyVeryOwnKey = "Test Value"
                    PSData = @{
                        ReleaseNotes = "Nothing has changed"
                    }
                }
            }
            """
        When I call Get-Metadata ModuleName.psd1 ReleaseNotes
        Then the result should be Nothing has changed


    Scenario: Attempt to read a non-existent value
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'
            }
            """
        Given we expect an error in the Metadata module
        When I call Get-Metadata ModuleName.psd1 NoSuchThing
        Then the error is logged exactly 1 time


    Scenario: Update the module version by default
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'
            }
            """
        When I call Update-Metadata ModuleName.psd1
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 0.4.1

    Scenario: Update the module major version
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Major
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 1.0

    Scenario: Update the module minor version
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Minor
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 0.5

    Scenario: Update the module minor version when it's 0
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '1.0'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Minor
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 1.1

    Scenario: Update the module build version
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4.1'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Build
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 0.4.2

    Scenario: Update the module build version when it's 0
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Build
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 0.4.1

    Scenario: Update the module revision
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '4.3.2.1'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Revision
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 4.3.2.2

    Scenario: Update the module revision when the build isn't set
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'
            }
            """
        When I call Update-Metadata ModuleName.psd1 -Increment Revision
        And I call Get-Metadata ModuleName.psd1
        Then the result should be 0.4.0.1


    @Regression
    Scenario: Get Arrays from a metadata file
        Given a module with the name 'ModuleName'
        And a module manifest named ModuleName.psd1
            """
            @{
                # Script module or binary module file associated with this manifest.
                ModuleToProcess = './Configuration.psm1'

                # Version number of this module.
                ModuleVersion = '0.4'

                AliasesToExport = @('Get-StoragePath', 'Get-ManifestValue', 'Update-Manifest')
            }
            """
        When I call Get-Metadata ModuleName.psd1 AliasesToExport
        Then the result should be @('Get-StoragePath', 'Get-ManifestValue', 'Update-Manifest')

================================================
FILE: Specs/ScriptAnalyzer.Steps.ps1
================================================
# Generate ScriptAnalyzer.feature
$Path = GetModuleBase

# The name (or path) of a settings file to be used.
[string]$Settings = "PSScriptAnalyzerSettings.psd1"
Write-Verbose "Resolve settings '$Settings'"
if (Test-Path $Settings) {
    $Settings = Resolve-Path $Settings
} else {
    Set-Location $PSScriptRoot\..
    if (Test-Path $Settings) {
        $Settings = Resolve-Path $Settings
    } else {
        foreach ($directory in Get-ChildItem -Directory) {
            $search = Join-Path $directory.FullName $Settings
            if (Test-Path $search) {
                $Settings = Resolve-Path $search
                break
            }
        }
    }
}

$ExcludeRules = @()
$Rules = @(
    if (!(Test-Path $Settings)) {
        Write-Warning "Could not find a 'PSScriptAnalyzerSettings.psd1'"
    } else {
        Write-Verbose "Loading $Settings"
        $Config = Import-LocalizedData -BaseDirectory ([IO.Path]::GetDirectoryName($Settings)) -FileName ([IO.Path]::GetFileName($Settings))
        $ExcludeRules = @($Config.ExcludeRules)
        if ($Config.CustomRulePath -and (Test-Path $Config.CustomRulePath)) {
            $CustomRules = @{
                CustomRulePath = @($Config.CustomRulePath)
                RecurseCustomRulePath = [bool]$Config.RecurseCustomRulePath
            }
            Get-ScriptAnalyzerRule @CustomRules
        }
    }

    Get-ScriptAnalyzerRule
) | Where-Object RuleName -notin $ExcludeRules

Set-Content "$PSScriptRoot\ScriptAnalyzer.feature" @"
# This feature file is re-generated by ScriptAnalyzer.Steps.ps1 whenever the tests are run
@ScriptAnalyzer
Feature: Passes Script Analyzer
    This module should pass Invoke-ScriptAnalyzer with flying colors

    Scenario: ScriptAnalyzer on the compiled module output
        Given the configuration module is imported
        When we run ScriptAnalyzer on '$Path' with '$Settings'
$(  foreach ($Rule in $Rules.RuleName) {"
        Then it passes the ScriptAnalyzer rule $Rule"
    })
"@


When "we run ScriptAnalyzer on '(?<Path>.*)' with '(?<Settings>.*)'" {
    param($Path, $Settings)
    try {
        $script:ScriptAnalyzerResults = Invoke-ScriptAnalyzer @PSBoundParameters
    } catch {
        Write-Warning "Exception running script analyzer on $($_.TargetObject)"
        Write-Warning $($_.Exception.StackTrace)
        throw $_
    }
}

Then "it passes the ScriptAnalyzer rule (?<RuleName>.*)" {
    param($RuleName)
    $rule = $script:ScriptAnalyzerResults.Where({$_.RuleName -eq $RuleName})
    if ($rule) { # ScriptAnalyzer only has results for failed tests
        throw ([Management.Automation.ErrorRecord]::new(
            ([Exception]::new(($rule.ForEach{$_.ScriptName + ":" + $_.Line + " " + $_.Message} -join "`n"))),
            "ScriptAnalyzerViolation",
            "SyntaxError",
            $rule))
    }
}


================================================
FILE: Specs/ScriptAnalyzer.feature
================================================
# This feature file is re-generated by ScriptAnalyzer.Steps.ps1 whenever the tests are run
@ScriptAnalyzer
Feature: Passes Script Analyzer
    This module should pass Invoke-ScriptAnalyzer with flying colors

    Scenario: ScriptAnalyzer on the compiled module output
        Given the configuration module is imported
        When we run ScriptAnalyzer on 'C:\Users\Jaykul\Projects\Modules\Configuration\1.6.0' with 'C:\Users\Jaykul\Projects\Modules\Configuration\PSScriptAnalyzerSettings.psd1'

        Then it passes the ScriptAnalyzer rule PSAlignAssignmentStatement 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingCmdletAliases 
        Then it passes the ScriptAnalyzer rule PSAvoidAssignmentToAutomaticVariable 
        Then it passes the ScriptAnalyzer rule PSAvoidDefaultValueSwitchParameter 
        Then it passes the ScriptAnalyzer rule PSAvoidDefaultValueForMandatoryParameter 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingEmptyCatchBlock 
        Then it passes the ScriptAnalyzer rule PSAvoidGlobalAliases 
        Then it passes the ScriptAnalyzer rule PSAvoidGlobalFunctions 
        Then it passes the ScriptAnalyzer rule PSAvoidGlobalVars 
        Then it passes the ScriptAnalyzer rule PSAvoidInvokingEmptyMembers 
        Then it passes the ScriptAnalyzer rule PSAvoidLongLines 
        Then it passes the ScriptAnalyzer rule PSAvoidMultipleTypeAttributes 
        Then it passes the ScriptAnalyzer rule PSAvoidNullOrEmptyHelpMessageAttribute 
        Then it passes the ScriptAnalyzer rule PSAvoidOverwritingBuiltInCmdlets 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingPositionalParameters 
        Then it passes the ScriptAnalyzer rule PSReservedCmdletChar 
        Then it passes the ScriptAnalyzer rule PSReservedParams 
        Then it passes the ScriptAnalyzer rule PSAvoidSemicolonsAsLineTerminators 
        Then it passes the ScriptAnalyzer rule PSAvoidShouldContinueWithoutForce 
        Then it passes the ScriptAnalyzer rule PSAvoidTrailingWhitespace 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingUsernameAndPasswordParams 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingBrokenHashAlgorithms 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingComputerNameHardcoded 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingConvertToSecureStringWithPlainText 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingDoubleQuotesForConstantString 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingInvokeExpression 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingPlainTextForPassword 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingWMICmdlet 
        Then it passes the ScriptAnalyzer rule PSAvoidUsingWriteHost 
        Then it passes the ScriptAnalyzer rule PSUseCompatibleCommands 
        Then it passes the ScriptAnalyzer rule PSUseCompatibleSyntax 
        Then it passes the ScriptAnalyzer rule PSUseCompatibleTypes 
        Then it passes the ScriptAnalyzer rule PSMisleadingBacktick 
        Then it passes the ScriptAnalyzer rule PSMissingModuleManifestField 
        Then it passes the ScriptAnalyzer rule PSPlaceCloseBrace 
        Then it passes the ScriptAnalyzer rule PSPlaceOpenBrace 
        Then it passes the ScriptAnalyzer rule PSPossibleIncorrectComparisonWithNull 
        Then it passes the ScriptAnalyzer rule PSPossibleIncorrectUsageOfRedirectionOperator 
        Then it passes the ScriptAnalyzer rule PSProvideCommentHelp 
        Then it passes the ScriptAnalyzer rule PSReviewUnusedParameter 
        Then it passes the ScriptAnalyzer rule PSUseApprovedVerbs 
        Then it passes the ScriptAnalyzer rule PSUseBOMForUnicodeEncodedFile 
        Then it passes the ScriptAnalyzer rule PSUseCmdletCorrectly 
        Then it passes the ScriptAnalyzer rule PSUseCompatibleCmdlets 
        Then it passes the ScriptAnalyzer rule PSUseConsistentIndentation 
        Then it passes the ScriptAnalyzer rule PSUseConsistentWhitespace 
        Then it passes the ScriptAnalyzer rule PSUseCorrectCasing 
        Then it passes the ScriptAnalyzer rule PSUseDeclaredVarsMoreThanAssignments 
        Then it passes the ScriptAnalyzer rule PSUseLiteralInitializerForHashtable 
        Then it passes the ScriptAnalyzer rule PSUseOutputTypeCorrectly 
        Then it passes the ScriptAnalyzer rule PSUseProcessBlockForPipelineCommand 
        Then it passes the ScriptAnalyzer rule PSUsePSCredentialType 
        Then it passes the ScriptAnalyzer rule PSShouldProcess 
        Then it passes the ScriptAnalyzer rule PSUseShouldProcessForStateChangingFunctions 
        Then it passes the ScriptAnalyzer rule PSUseSingularNouns 
        Then it passes the ScriptAnalyzer rule PSUseSupportsShouldProcess 
        Then it passes the ScriptAnalyzer rule PSUseToExportFieldsInManifest 
        Then it passes the ScriptAnalyzer rule PSUseUsingScopeModifierInNewRunspaces 
        Then it passes the ScriptAnalyzer rule PSUseUTF8EncodingForHelpFile 
        Then it passes the ScriptAnalyzer rule PSDSCDscExamplesPresent 
        Then it passes the ScriptAnalyzer rule PSDSCDscTestsPresent 
        Then it passes the ScriptAnalyzer rule PSDSCReturnCorrectTypesForDSCFunctions 
        Then it passes the ScriptAnalyzer rule PSDSCUseIdenticalMandatoryParametersForDSC 
        Then it passes the ScriptAnalyzer rule PSDSCUseIdenticalParametersForDSC 
        Then it passes the ScriptAnalyzer rule PSDSCStandardDSCFunctionsInResource 
        Then it passes the ScriptAnalyzer rule PSDSCUseVerboseMessageInDSCResource


================================================
FILE: Specs/Serialization.feature
================================================
Feature: Serialize Hashtables or Custom Objects
    To allow users to configure module preferences without editing their profiles
    A PowerShell Module Author
    Needs to serialize a preferences object in a user-editable format we call metadata

    Background:
        Given the configuration module is imported with testing paths:
        | Enterprise                | User                | Machine                |
        | TestDrive:/EnterprisePath | TestDrive:/UserPath | TestDrive:/MachinePath |

    @Serialization
    Scenario: Serialize a hashtable to string
        Given a settings hashtable
            """
            @{ UserName = "Joel"; BackgroundColor = "Black"}
            """
        When we convert the settings to metadata
        Then the string version should be
            """
            @{
              UserName = 'Joel'
              BackgroundColor = 'Black'
            }
            """

    @Serialization @ConsoleColor
    Scenario: Serialize a ConsoleColor to string
        Given a settings hashtable
            """
            @{ UserName = "Joel"; BackgroundColor = [ConsoleColor]::Black }
            """
        When we convert the settings to metadata
        Then the string version should be
            """
            @{
              UserName = 'Joel'
              BackgroundColor = (ConsoleColor Black)
            }
            """

    @Serialization
    Scenario: Should be able to serialize core types:
        Given a settings hashtable with a String in it
        When we convert the settings to metadata
        Then the string version should match 'TestCase = ([''"])[^\1]+\1'

        Given a settings hashtable with a Boolean in it
        When we convert the settings to metadata
        Then the string version should match 'TestCase = \`$(True|False)'

        Given a settings hashtable with a NULL in it
        When we convert the settings to metadata
        Then the string version should match 'TestCase = ""'

        Given a settings hashtable with a Number in it
        When we convert the settings to metadata
        Then the string version should match 'TestCase = \d+'

    @Serialization
    Scenario: Should be able to serialize a array
        Given a settings hashtable with an Array in it
        When we convert the settings to metadata
        Then the string version should match 'TestCase = ([^,]*,)+[^,]*'

    @Serialization
    Scenario: Should be able to serialize nested hashtables
        Given a settings hashtable with a hashtable in it
        When we convert the settings to metadata
        Then the string version should match 'TestCase = @{'


    @Serialization @SecureString @PSCredential @CRYPT32
    Scenario Outline: Should be able to serialize PSCredential
        Given a settings hashtable with a PSCredential in it
        When we convert the settings to metadata
        Then the string version should match "TestCase = \(?PSCredential"

    @Serialization @SecureString @CRYPT32
    Scenario Outline: Should be able to serialize SecureStrings
        Given a settings hashtable with a SecureString in it
        When we convert the settings to metadata
        Then the string version should match "TestCase = \(?ConvertTo-SecureString [a-z0-9]+"


    @Serialization @CRYPT32
    Scenario Outline: Should support a few additional types
        Given a settings hashtable with a <type> in it
        When we convert the settings to metadata
        Then the string version should match "TestCase = \(?<type> "

        Examples:
            | type           |
            | DateTime       |
            | DateTimeOffset |
            | GUID           |
            | PSObject       |
            | PSCredential   |
            | ConsoleColor   |

    @Serialization
    Scenario: PSCustomObject preserves PSTypeNames
        Given a settings object
            """
            @{
                PSTypeName = 'Whatever.User'
                FirstName = 'Joel'
                LastName = 'Bennett'
                UserName = 'Jaykul'
                Homepage = [Uri]"http://HuddledMasses.org"
            }
            """
        When we export to a settings file named Configuration.psd1
        And we import the file to an object
        Then the settings object should have Whatever.User in the PSTypeNames

    @Serialization @Enum
    Scenario: Unsupported types should be serialized as strings
        Given a settings hashtable with an Enum in it
        Then we expect a warning in the Metadata module
        When we convert the settings to metadata
        And the warning is logged

    @Serialization @Error @Converter
    Scenario: Invalid converters should write non-terminating errors
        Given we expect an error in the Metadata module
        When we add a converter that's not a scriptblock
        And we add a converter with a number as a key
        Then the error is logged exactly 2 times

    @Serialization @Uri @Converter
    Scenario: Developers should be able to add support for other types
        Given a settings hashtable with a Uri in it
        When we add a converter for Uri types
        And we convert the settings to metadata
        Then the string version should match "TestCase = \(?Uri '.*'"


    @Serialization @File
    Scenario: Developers should be able to export straight to file
        Given a settings hashtable
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        When we export to a settings file named Configuration.psd1
        Then the settings file should contain
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """

    @Deserialization @Uri @Converter
    Scenario: I should be able to import serialized data
        Given a settings hashtable
            """
            @{
              UserName = 'Joel'
              Age = 42
              LastUpdated = (Get-Date).Date
              Homepage = [Uri]"http://HuddledMasses.org"
            }
            """
        Then the settings object's Homepage should be of type Uri
        And we add a converter for Uri types
        And we convert the settings to metadata
        When we convert the metadata to an object
        Then the settings object should be of type hashtable
        Then the settings object's UserName should be of type String
        Then the settings object's Age should be of type Int32
        Then the settings object's LastUpdated should be of type DateTime
        Then the settings object's Homepage should be of type Uri

    @DeSerialization @SecureString @PSCredential @CRYPT32
    Scenario Outline: I should be able to import serialized credentials and secure strings
        Given a settings hashtable
            """
            @{
              Credential = [PSCredential]::new("UserName",(ConvertTo-SecureString Password -AsPlainText -Force))
              Password = ConvertTo-SecureString Password -AsPlainText -Force
            }
            """
        When we convert the settings to metadata
        Then the string version should match "Credential = \(?PSCredential"
        And the string version should match "Password = \(?ConvertTo-SecureString [\"a-z0-9]*"
        When we convert the metadata to an object
        Then the settings object should be of type hashtable
        Then the settings object's Credential should be of type PSCredential
        Then the settings object's Password should be of type SecureString

    @Serialization @SecureString @CRYPT32
    Scenario Outline: Should be able to serialize SecureStrings
        Given a settings hashtable with a SecureString in it
        When we convert the settings to metadata
        Then the string version should match "TestCase = \(?ConvertTo-SecureString [a-z0-9]+"

    @Deserialization @Uri @Converter
    Scenario: I should be able to import serialized data even in PowerShell 2
        Given a settings hashtable
            """
            @{
              UserName = New-Object PSObject -Property @{ FirstName = 'Joel'; LastName = 'Bennett' }
              Age = [Version]4.2
              LastUpdated = [DateTimeOffset](Get-Date).Date
              GUID = [GUID]::NewGuid()
              Color = [ConsoleColor]::Red
            }
            """
        And we fake version 2.0 in the Metadata module
        And we add a converter for Uri types
        And we convert the settings to metadata
        When we convert the metadata to an object
        Then the settings object should be of type hashtable
        And the settings object's UserName should be of type PSObject
        And the settings object's Age should be of type String
        And the settings object's LastUpdated should be of type DateTimeOffset
        And the settings object's GUID should be of type GUID
        And the settings object's Color should be of type ConsoleColor

    @Deserialization @Uri @Converter
    Scenario: I should be able to add converters at import time
        Given the configuration module is imported with a URL converter
        And a settings hashtable
            """
            @{
              UserName = 'Joel'
              Age = 42
              Homepage = [Uri]"http://HuddledMasses.org"
            }
            """
        Then the settings object's Homepage should be of type Uri
        And we convert the settings to metadata
        Then the string version should match
            """
              Homepage = \(?Uri 'http://HuddledMasses.org/'
            """
        When we convert the metadata to an object
        Then the settings object should be of type hashtable
        And the settings object's UserName should be of type String
        And the settings object's Age should be of type Int32
        And the settings object's Homepage should be of type Uri


    @Deserialization @File
    Scenario: I should be able to import serialized data from files even in PowerShell 2
        Given a module with the name 'TestModule1'
        Given a settings file named Configuration.psd1
            """
            @{
              UserName = 'Joel'
              Age = 42
            }
            """
        And we fake version 2.0 in the Metadata module
        When we import the file to an object
        Then the settings object should be of type hashtable
        And the settings object's UserName should be of type
Download .txt
gitextract_4wdl1uu2/

├── .config/
│   └── dotnet-tools.json
├── .github/
│   └── workflows/
│       ├── Merge-Module.ps1
│       └── build.yml
├── .gitignore
├── .vscode/
│   ├── launch.json
│   ├── settings.json
│   ├── taskmarks.json
│   └── tasks.json
├── Benchmark/
│   ├── Benchmark.ps1
│   └── Data/
│       └── Configuration.psd1
├── Build.ps1
├── CHANGELOG.md
├── Examples/
│   ├── TestModuleOne/
│   │   ├── Configuration.psd1
│   │   ├── TestModuleOne.psd1
│   │   └── TestModuleOne.psm1
│   └── TestModuleTwo/
│       ├── Configuration.psd1
│       ├── TestModuleTwo.psd1
│       └── TestModuleTwo.psm1
├── GitVersion.yml
├── LICENSE
├── PSScriptAnalyzerSettings.psd1
├── README.md
├── ReBuild.ps1
├── RequiredModules.psd1
├── Source/
│   ├── Configuration.psd1
│   ├── Header/
│   │   └── param.ps1
│   ├── Private/
│   │   ├── InitializeStoragePaths.ps1
│   │   └── ParameterBinder.ps1
│   └── Public/
│       ├── Export-Configuration.ps1
│       ├── Get-ConfigurationPath.ps1
│       ├── Get-ParameterValue.ps1
│       ├── Import-Configuration.ps1
│       └── Import-ParameterConfiguration.ps1
├── Specs/
│   ├── Configuration.Steps.ps1
│   ├── Configuration.feature
│   ├── ConfiguredParameters.feature
│   ├── DefaultParameters.feature
│   ├── Layering.feature
│   ├── LocalStoragePath.feature
│   ├── LocalStoragePathLinux.feature
│   ├── Manifest.feature
│   ├── ScriptAnalyzer.Steps.ps1
│   ├── ScriptAnalyzer.feature
│   ├── Serialization.feature
│   └── TestVersion.feature
├── Test.ps1
├── bootstrap.ps1
└── build.psd1
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (221K chars).
[
  {
    "path": ".config/dotnet-tools.json",
    "chars": 167,
    "preview": "{\n  \"version\": 1,\n  \"isRoot\": true,\n  \"tools\": {\n    \"gitversion.tool\": {\n      \"version\": \"5.6.0\",\n      \"commands\": [\n"
  },
  {
    "path": ".github/workflows/Merge-Module.ps1",
    "chars": 1262,
    "preview": "#requires -Module Configuration\n[CmdletBinding()]\nparam(\n    $OutputModulePath,\n    $NestedModulePath\n)\n$OutputModule = "
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 1943,
    "preview": "name: Build on push\non: [push]\njobs:\n  build:\n    runs-on: windows-latest\n    steps:\n      - name: Checkout\n        uses"
  },
  {
    "path": ".gitignore",
    "chars": 81,
    "preview": "/output/\n/[0-9]*/\n\n/.vs/\nRequiredModules/\nnode_modules/\n\nresults.xml\ncoverage.xml"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 1460,
    "preview": "{\r\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\r\n    \"version\": \"0.2.0\",\r\n    \"con"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 371,
    "preview": "{\n    \"files.defaultLanguage\": \"powershell\",\n    \"powershell.codeFormatting.preset\": \"OTBS\",\n    \"powershell.codeFormatt"
  },
  {
    "path": ".vscode/taskmarks.json",
    "chars": 93,
    "preview": "{\n\t\"activeTaskName\": \"default\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"name\": \"default\",\n\t\t\t\"files\": []\n\t\t}\n\t]\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 2271,
    "preview": "{\r\n    // See https://go.microsoft.com/fwlink/?LinkId=733558\r\n    // for the documentation about the tasks.json format\r\n"
  },
  {
    "path": "Benchmark/Benchmark.ps1",
    "chars": 1474,
    "preview": "[CmdletBinding()]\nparam([int]$Count = 10)\n$dataPath = Join-Path $PSScriptRoot \"../Benchmark/Data/Configuration.psd1\"\n\nfo"
  },
  {
    "path": "Benchmark/Data/Configuration.psd1",
    "chars": 24486,
    "preview": "\n@{\n  CurrentColorTheme = 'devblackops'\n  CurrentIconTheme = 'devblackops'\n  Themes = @{\n    Color = @{\n      devblackop"
  },
  {
    "path": "Build.ps1",
    "chars": 650,
    "preview": "#requires -Module @{ModuleName = \"ModuleBuilder\"; ModuleVersion = \"2.0.0\"}, Configuration\r\n[CmdletBinding()]\r\nparam(\r\n  "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 950,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "Examples/TestModuleOne/Configuration.psd1",
    "chars": 63,
    "preview": "@{\n    Address = \"http://PoshCode.org\"\n    Credential = $null\n}"
  },
  {
    "path": "Examples/TestModuleOne/TestModuleOne.psm1",
    "chars": 1250,
    "preview": "# If your default configuration has some blank settings, you can do something like this:\n# Assume I have a mandatory cre"
  },
  {
    "path": "Examples/TestModuleTwo/Configuration.psd1",
    "chars": 40,
    "preview": "@{\n    Address = \"http://PoshCode.org\"\n}"
  },
  {
    "path": "Examples/TestModuleTwo/TestModuleTwo.psm1",
    "chars": 996,
    "preview": "# If your default configuration has valid defaults, but you still want them to review it,\n# Provide a public Get-Configu"
  },
  {
    "path": "GitVersion.yml",
    "chars": 713,
    "preview": "mode: Mainline\r\ncommit-message-incrementing: MergeMessageOnly\r\n\r\nassembly-versioning-format: '{Major}.{Minor}.{Patch}.{e"
  },
  {
    "path": "LICENSE",
    "chars": 1056,
    "preview": "Copyright (c) 2015 Joel Bennett\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this so"
  },
  {
    "path": "PSScriptAnalyzerSettings.psd1",
    "chars": 154,
    "preview": "@{\n    Severity     = @('Error', 'Warning')\n    ExcludeRules = @('PSAvoidUsingDeprecatedManifestFields', 'PSPossibleInco"
  },
  {
    "path": "README.md",
    "chars": 10334,
    "preview": "[![Build Status](https://github.com/PoshCode/Configuration/actions/workflows/build.yml/badge.svg)](https://github.com/Po"
  },
  {
    "path": "ReBuild.ps1",
    "chars": 22502,
    "preview": "#requires -Version \"4.0\" -Module PackageManagement, Pester\r\n[CmdletBinding()]\r\nparam(\r\n    # The step(s) to run. Default"
  },
  {
    "path": "RequiredModules.psd1",
    "chars": 167,
    "preview": "@{\n    Configuration    = \"[1.3.1,2.0)\"\n    Metadata         = \"1.5.*\"\n    Pester           = \"4.10.*\"\n    ModuleBuilder"
  },
  {
    "path": "Source/Configuration.psd1",
    "chars": 3126,
    "preview": "@{\n\n# Script module or binary module file associated with this manifest.\nModuleToProcess = 'Configuration.psm1'\n\n# Vers"
  },
  {
    "path": "Source/Header/param.ps1",
    "chars": 219,
    "preview": "# Allows you to override the Scope storage paths (e.g. for testing)\nparam(\n    $Converters = @{},\n    $EnterpriseData,\n "
  },
  {
    "path": "Source/Private/InitializeStoragePaths.ps1",
    "chars": 3031,
    "preview": "function InitializeStoragePaths {\n    [CmdletBinding()]\n    param(\n        $EnterpriseData,\n        $UserData,\n        $"
  },
  {
    "path": "Source/Private/ParameterBinder.ps1",
    "chars": 1613,
    "preview": "function ParameterBinder {\n    if (!$Module) {\n        [System.Management.Automation.PSModuleInfo]$Module = . {\n        "
  },
  {
    "path": "Source/Public/Export-Configuration.ps1",
    "chars": 4593,
    "preview": "function Export-Configuration {\n    <#\n        .Synopsis\n            Exports a configuration object to a specified path."
  },
  {
    "path": "Source/Public/Get-ConfigurationPath.ps1",
    "chars": 5247,
    "preview": "function Get-ConfigurationPath {\n    #.Synopsis\n    #   Gets an storage path for configuration files and data\n    #.Desc"
  },
  {
    "path": "Source/Public/Get-ParameterValue.ps1",
    "chars": 5320,
    "preview": "function Get-ParameterValue {\n    <#\n        .SYNOPSIS\n            Get parameter values from PSBoundParameters + Default"
  },
  {
    "path": "Source/Public/Import-Configuration.ps1",
    "chars": 5959,
    "preview": "function Import-Configuration {\n    #.Synopsis\n    #   Import the full, layered configuration for the module.\n    #.Desc"
  },
  {
    "path": "Source/Public/Import-ParameterConfiguration.ps1",
    "chars": 8312,
    "preview": "function Import-ParameterConfiguration {\n    <#\n        .SYNOPSIS\n            Loads a metadata file based on the calling"
  },
  {
    "path": "Specs/Configuration.Steps.ps1",
    "chars": 28759,
    "preview": "#requires -Module Configuration\n#using module Configuration\n\n$PSModuleAutoLoadingPreference = \"None\"\n# Fix IsLinux on Wi"
  },
  {
    "path": "Specs/Configuration.feature",
    "chars": 6270,
    "preview": "Feature: Module Configuration\n    As a PowerShell Module Author\n    I need to be able to store settings\n    And override"
  },
  {
    "path": "Specs/ConfiguredParameters.feature",
    "chars": 4032,
    "preview": "Feature: Configure Command From Working Directory\n\n    There is a command to support loading default parameter values fr"
  },
  {
    "path": "Specs/DefaultParameters.feature",
    "chars": 2533,
    "preview": "Feature: Get PSBoundParameters plus default values plus a config file\r\n\r\n    There is a command to support merging PSBou"
  },
  {
    "path": "Specs/Layering.feature",
    "chars": 6616,
    "preview": "Feature: Multiple settings files should layer\r\n    As a module author, I want to distribute a default config with my mod"
  },
  {
    "path": "Specs/LocalStoragePath.feature",
    "chars": 4855,
    "preview": "@StoragePath\r\nFeature: Automatically Calculate Local Storage Paths\r\n    In order for module settings to survive upgrades"
  },
  {
    "path": "Specs/LocalStoragePathLinux.feature",
    "chars": 2760,
    "preview": "@StoragePath\r\nFeature: Automatically Calculate Local Storage Paths on Linux\r\n    In order for module settings to survive"
  },
  {
    "path": "Specs/Manifest.feature",
    "chars": 9538,
    "preview": "Feature: Manifest Read and Write\r\n    As a PowerShell Module Author\r\n    I want to easily edit my manifest as part of my"
  },
  {
    "path": "Specs/ScriptAnalyzer.Steps.ps1",
    "chars": 2833,
    "preview": "# Generate ScriptAnalyzer.feature\n$Path = GetModuleBase\n\n# The name (or path) of a settings file to be used.\n[string]$Se"
  },
  {
    "path": "Specs/ScriptAnalyzer.feature",
    "chars": 5571,
    "preview": "# This feature file is re-generated by ScriptAnalyzer.Steps.ps1 whenever the tests are run\n@ScriptAnalyzer\nFeature: Pass"
  },
  {
    "path": "Specs/Serialization.feature",
    "chars": 20307,
    "preview": "Feature: Serialize Hashtables or Custom Objects\r\n    To allow users to configure module preferences without editing thei"
  },
  {
    "path": "Specs/TestVersion.feature",
    "chars": 1030,
    "preview": "@Version\nFeature: A Mockable PowerShell Version test\n    To allow testing for the version of PowerShell while mocking th"
  },
  {
    "path": "Test.ps1",
    "chars": 1994,
    "preview": "<#\r\n    .SYNOPSIS\r\n        Invoke-Gherkin against a specific version in output\r\n#>\r\n[CmdletBinding()]\r\nparam(\r\n    # A s"
  },
  {
    "path": "bootstrap.ps1",
    "chars": 1816,
    "preview": "using namespace Microsoft.PowerShell.Commands\r\n<#\r\n.SYNOPSIS\r\n    Installs and imports modules listed in RequiredModules"
  },
  {
    "path": "build.psd1",
    "chars": 241,
    "preview": "@{\n    ModuleManifest           = \"Source/Configuration.psd1\"\n    OutputDirectory          = \"../\"\n    SourceDirectories"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the PoshCode/Configuration GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (204.2 KB), approximately 50.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!