├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── Modules └── ScriptConfig │ ├── Functions │ └── Get-ScriptConfig.ps1 │ ├── Helpers │ ├── ConvertFrom-ScriptConfigIni.ps1 │ ├── ConvertFrom-ScriptConfigJson.ps1 │ └── ConvertFrom-ScriptConfigXml.ps1 │ ├── Resources │ ├── ScriptConfig.Formats.ps1xml │ └── ScriptConfig.Types.ps1xml │ ├── ScriptConfig.psd1 │ ├── ScriptConfig.psm1 │ ├── Tests │ ├── Integration │ │ ├── IniConfigDemo.ps1 │ │ ├── IniConfigDemo.ps1.config │ │ ├── JsonConfigDemo.ps1 │ │ ├── JsonConfigDemo.ps1.config │ │ ├── XmlConfigDemo.ps1 │ │ └── XmlConfigDemo.ps1.config │ └── Unit │ │ ├── ConvertFrom-ScriptConfigIni.Tests.ps1 │ │ ├── ConvertFrom-ScriptConfigJson.Tests.ps1 │ │ ├── ConvertFrom-ScriptConfigXml.Tests.ps1 │ │ ├── Get-ScriptConfig.Tests.ps1 │ │ └── TestData │ │ ├── config.ini │ │ ├── config.json │ │ └── config.xml │ └── en-US │ └── about_ScriptConfig.help.txt ├── README.md ├── appveyor.yml ├── build.debug.ps1 ├── build.psake.ps1 └── build.settings.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore project files 2 | /bin/ 3 | /tst/ 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "PowerShell", 6 | "request": "launch", 7 | "name": "PowerShell Launch Debug File", 8 | "script": "${workspaceRoot}\\build.debug.ps1", 9 | "args": [], 10 | "cwd": "${workspaceRoot}", 11 | "createTemporaryIntegratedConsole": true 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Trim trailing whitespaces, but not for markdown 3 | "files.trimTrailingWhitespace": true, 4 | "[markdown]": { 5 | "files.trimTrailingWhitespace": false 6 | }, 7 | 8 | // Exclude files form the explorer tree 9 | "files.exclude": { 10 | "bin/**": true, 11 | "tst/**": true, 12 | "Sources/*/.vs/**": true, 13 | "Sources/*/packages/**": true, 14 | "Sources/*/TestResults/**": true 15 | }, 16 | "search.exclude": { 17 | "bin": true, 18 | "tst": true 19 | }, 20 | 21 | // PowerShell console behaviour 22 | "powershell.debugging.createTemporaryIntegratedConsole": true, 23 | "powershell.integratedConsole.showOnStartup": true, 24 | "powershell.integratedConsole.focusConsoleOnExecute": false, 25 | 26 | // PowerShell code formatting 27 | "powershell.codeFormatting.openBraceOnSameLine": false, 28 | "powershell.codeFormatting.newLineAfterOpenBrace": true, 29 | "powershell.codeFormatting.newLineAfterCloseBrace": true, 30 | "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, 31 | "powershell.codeFormatting.whitespaceBeforeOpenParen": true, 32 | "powershell.codeFormatting.whitespaceAroundOperator": true, 33 | "powershell.codeFormatting.whitespaceAfterSeparator": true, 34 | "powershell.codeFormatting.ignoreOneLineBlock": true, 35 | "powershell.codeFormatting.alignPropertyValuePairs": true 36 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | 4 | "windows": { 5 | "type": "shell", 6 | "options": { 7 | "shell": { 8 | "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", 9 | "args": [ "-NoProfile", "-Command" ] 10 | } 11 | }, 12 | 13 | "command": "\"& { Invoke-psake build.psake.ps1 -taskList $args -notr }\"", 14 | 15 | "presentation": { 16 | "echo": false, 17 | "reveal": "always", 18 | "focus": false, 19 | "panel": "new" 20 | } 21 | }, 22 | 23 | "tasks": [ 24 | { 25 | "label": "Test", 26 | "group": { 27 | "kind": "test", 28 | "isDefault": true 29 | } 30 | }, 31 | { 32 | "label": "Build", 33 | "group": { 34 | "kind": "build", 35 | "isDefault": true 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is mainly based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## 3.1.0 - 2019-09-16 9 | 10 | * Added: Auto-detect the config file if -Path was not specified 11 | 12 | ## 3.0.0 - 2019-02-18 13 | 14 | * Changed: Get the default config path from the PS call stack 15 | * Changed: The default configuration file type is now JSON (BREAKING CHANGE) 16 | 17 | ## 2.0.0 - 2016-12-12 18 | 19 | * Changed: Convert module to new deployment model 20 | * Changed: Rework code against high quality module guidelines by Microsoft 21 | * Changed: Remove positional parameters (BREAKING CHANGE) 22 | 23 | ## 1.0.3 - 2016-02-09 24 | 25 | * Added: Formats and types resources 26 | 27 | ## 1.0.2 - 2016-02-03 28 | 29 | * Fixed: Default path issue for Get-ScriptConfig 30 | 31 | ## 1.0.1 - 2016-01-29 32 | 33 | * Updated: File encoding to UTF8 w/o BOM 34 | * Updated: Demo and help 35 | * Added: Enhance parameters for Get-ScriptConfig function 36 | 37 | ## 1.0.0 - 2016-01-21 38 | 39 | * Added: Initial public release 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Claudio Spizzi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Functions/Get-ScriptConfig.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Load a script configuration from a config file. 4 | 5 | .DESCRIPTION 6 | Load a script configuration from a config file. By default, the config 7 | file next to the script is loaded. So for the script MyScript.ps1, the 8 | config file MyScript.ps1.config will be loaded. The config file format 9 | will be detected by it's extension (.xml, .ini, .json). If the file 10 | format can't be detected, the default format (JSON) will be used. 11 | 12 | .EXAMPLE 13 | PS C:\> $config = Get-ScriptConfig 14 | Loads the default JSON formatted configuration file. 15 | 16 | .EXAMPLE 17 | PS C:\> $config = Get-ScriptConfig -Path 'C:\MyApp\global.config' 18 | Loads a custom configuration file, with default JSON format. 19 | 20 | .EXAMPLE 21 | PS C:\> $config = Get-ScriptConfig -Format XML 22 | Loads the default configuration file but in XML format. 23 | 24 | .NOTES 25 | Author : Claudio Spizzi 26 | License : MIT License 27 | 28 | .LINK 29 | https://github.com/claudiospizzi/ScriptConfig 30 | #> 31 | function Get-ScriptConfig 32 | { 33 | [CmdletBinding()] 34 | param 35 | ( 36 | # Specify the path to the configuration file. By default, the current 37 | # script file path will be used with an appended '.config' extension. 38 | [Parameter(Mandatory = $false)] 39 | [AllowEmptyString()] 40 | [System.String] 41 | $Path, 42 | 43 | # Override the format detection and default value. 44 | [Parameter(Mandatory = $false)] 45 | [ValidateSet('XML', 'JSON', 'INI')] 46 | [System.String] 47 | $Format 48 | ) 49 | 50 | # If the Path parameter was not specified, add a default value. If possible, 51 | # use the last script called this function. Else throw an exception. 52 | if (-not $PSBoundParameters.ContainsKey('Path') -or [System.String]::IsNullOrEmpty($Path)) 53 | { 54 | $lastScriptPath = Get-PSCallStack | Select-Object -Skip 1 -First 1 -ExpandProperty 'ScriptName' 55 | 56 | if (-not [System.String]::IsNullOrEmpty($lastScriptPath)) 57 | { 58 | if (Test-Path -Path "$lastScriptPath.ini") 59 | { 60 | $Path = "$lastScriptPath.ini" 61 | } 62 | elseif (Test-Path -Path "$lastScriptPath.json") 63 | { 64 | $Path = "$lastScriptPath.json" 65 | } 66 | elseif (Test-Path -Path "$lastScriptPath.xml") 67 | { 68 | $Path = "$lastScriptPath.xml" 69 | } 70 | else 71 | { 72 | $Path = "$lastScriptPath.config" 73 | } 74 | } 75 | else 76 | { 77 | throw "Configuration file not specified" 78 | } 79 | } 80 | 81 | # Now check, if the configuration file exists 82 | if (-not (Test-Path -Path $Path)) 83 | { 84 | throw "Configuration file not found: $Path" 85 | } 86 | 87 | # If the Format parameter was not specified, try to detect by the file 88 | # extension or use the default format JSON. 89 | if (-not $PSBoundParameters.ContainsKey('Format')) 90 | { 91 | switch -Wildcard ($Path) 92 | { 93 | '*.ini' { $Format = 'INI' } 94 | '*.json' { $Format = 'JSON' } 95 | '*.xml' { $Format = 'XML' } 96 | default { $Format = 'JSON' } 97 | } 98 | } 99 | 100 | Write-Verbose "Load script configuration from file $Path with format $Format" 101 | 102 | # Load raw content, parse it later 103 | $content = Get-Content -Path $Path -ErrorAction Stop 104 | 105 | # Use custom functions to parse the files and return the config object 106 | switch ($Format) 107 | { 108 | 'XML' { ConvertFrom-ScriptConfigXml -Content $content } 109 | 'JSON' { ConvertFrom-ScriptConfigJson -Content $content } 110 | 'INI' { ConvertFrom-ScriptConfigIni -Content $content } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Helpers/ConvertFrom-ScriptConfigIni.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert the INI file content to a hashtable containing the 4 | configuration. 5 | 6 | .EXAMPLE 7 | PS C:\> Get-Content -Path 'config.ini' | ConvertFrom-ScriptConfigIni 8 | Use the pipeline input to parse the INI file content. 9 | 10 | .NOTES 11 | Author : Claudio Spizzi 12 | License : MIT License 13 | 14 | .LINK 15 | https://github.com/claudiospizzi/ScriptConfig 16 | #> 17 | function ConvertFrom-ScriptConfigIni 18 | { 19 | [CmdletBinding()] 20 | param 21 | ( 22 | # An array of strings with the INI file content. 23 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 24 | [AllowEmptyString()] 25 | [System.String[]] 26 | $Content 27 | ) 28 | 29 | $config = @{ 30 | PSTypeName = 'ScriptConfig.Configuration' 31 | } 32 | 33 | try 34 | { 35 | # Iterating each line and parse the setting 36 | foreach ($line in $Content) 37 | { 38 | switch -Wildcard ($line) 39 | { 40 | # Comment 41 | ';*' { 42 | break 43 | } 44 | 45 | # Section 46 | '`[*`]*' { 47 | break 48 | } 49 | 50 | # Array 51 | '*`[`]=*'{ 52 | $key = $line.Split('[]=', 4)[0] 53 | $value = $line.Split('[]=', 4)[3] 54 | 55 | if ($null -eq $config[$key]) 56 | { 57 | $config[$key] = @() 58 | } 59 | 60 | $config[$key] += $value 61 | 62 | break 63 | } 64 | 65 | # Hashtable 66 | '*`[*`]=*' { 67 | $key = $line.Split('[]=', 4)[0] 68 | $hash = $line.Split('[]=', 4)[1] 69 | $value = $line.Split('[]=', 4)[3] 70 | 71 | if ($null -eq $config[$key]) 72 | { 73 | $config[$key] = @{} 74 | } 75 | 76 | $config[$key][$hash] = $value 77 | 78 | break 79 | } 80 | 81 | # String, Integer or Boolean 82 | '*=*' { 83 | $key = $line.Split('=', 2)[0] 84 | $value = $line.Split('=', 2)[1] 85 | 86 | [Int32] $valueInt = $null 87 | if (([Int32]::TryParse($value, [ref] $valueInt))) 88 | { 89 | $config[$key] = $valueInt 90 | } 91 | else 92 | { 93 | if ('True'.Equals($value)) { $value = $true } 94 | if ('False'.Equals($value)) { $value = $false } 95 | 96 | $config[$key] = $value 97 | } 98 | 99 | break 100 | } 101 | } 102 | } 103 | 104 | [PSCustomObject] $config 105 | } 106 | catch 107 | { 108 | throw "The INI configuration file content was in an invalid format: $_" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Helpers/ConvertFrom-ScriptConfigJson.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert the JSON file content to a hashtable containing the 4 | configuration. 5 | 6 | .EXAMPLE 7 | PS C:\> Get-Content -Path 'config.json' | ConvertFrom-ScriptConfigJson 8 | Use the pipeline input to parse the JSON file content. 9 | 10 | .NOTES 11 | Author : Claudio Spizzi 12 | License : MIT License 13 | 14 | .LINK 15 | https://github.com/claudiospizzi/ScriptConfig 16 | #> 17 | function ConvertFrom-ScriptConfigJson 18 | { 19 | [CmdletBinding()] 20 | param 21 | ( 22 | # An array of strings with the JSON file content. 23 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 24 | [AllowEmptyString()] 25 | [System.String[]] 26 | $Content 27 | ) 28 | 29 | $config = @{} 30 | 31 | try 32 | { 33 | # Join all lines into one string and parse the JSON content 34 | $jsonContent = ($Content -join '') | ConvertFrom-Json 35 | 36 | # Extract all propeties from the json content 37 | $jsonNodes = $jsonContent | Get-Member -MemberType NoteProperty 38 | 39 | foreach ($jsonNode in $jsonNodes) 40 | { 41 | $Key = $jsonNode.Name 42 | $value = $jsonContent.$Key 43 | 44 | if ($value -is [System.Management.Automation.PSCustomObject]) 45 | { 46 | $config[$Key] = @{} 47 | 48 | foreach ($property in $value.PSObject.Properties) 49 | { 50 | $config[$Key][$property.Name] = $property.Value 51 | } 52 | } 53 | else 54 | { 55 | $config[$Key] = $value 56 | } 57 | } 58 | 59 | Write-Output $config 60 | } 61 | catch 62 | { 63 | throw "The JSON configuration file content was in an invalid format: $_" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Helpers/ConvertFrom-ScriptConfigXml.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert the XML file content to a hashtable containing the 4 | configuration. 5 | 6 | .EXAMPLE 7 | PS C:\> Get-Content -Path 'config.xml' | ConvertFrom-ScriptConfigXml 8 | Use the pipeline input to parse the XML file content. 9 | 10 | .NOTES 11 | Author : Claudio Spizzi 12 | License : MIT License 13 | 14 | .LINK 15 | https://github.com/claudiospizzi/ScriptConfig 16 | #> 17 | function ConvertFrom-ScriptConfigXml 18 | { 19 | [CmdletBinding()] 20 | param 21 | ( 22 | # An array of strings with the XML file content. 23 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 24 | [AllowEmptyString()] 25 | [System.String[]] 26 | $Content 27 | ) 28 | 29 | $config = @{} 30 | 31 | try 32 | { 33 | # Try to cast the content into an XmlDocument 34 | $xmlContent = [Xml] $Content 35 | 36 | # Extract all setting objects 37 | $settings = $xmlContent.Configuration.Settings.Setting 38 | 39 | foreach ($setting in $settings) 40 | { 41 | switch ($setting.Type) 42 | { 43 | # String 44 | 'string' { 45 | $config[$setting.Key] = $setting.Value 46 | } 47 | 48 | # Integer 49 | 'integer' { 50 | $config[$setting.Key] = [Int32]::Parse($setting.Value) 51 | } 52 | 53 | # Boolean 54 | 'boolean' { 55 | $config[$setting.Key] = 'True' -eq $setting.Value 56 | } 57 | 58 | # Array 59 | 'array' { 60 | $config[$setting.Key] = @() 61 | foreach ($item in $setting.Item) 62 | { 63 | $config[$setting.Key] += $item.Value 64 | } 65 | } 66 | 67 | # Hashtable 68 | 'hashtable' { 69 | $config[$setting.Key] = @{} 70 | foreach ($item in $setting.Item) 71 | { 72 | $config[$setting.Key][$item.Key] = $item.Value 73 | } 74 | } 75 | } 76 | } 77 | 78 | Write-Output $config 79 | } 80 | catch 81 | { 82 | throw "The XML configuration file content was in an invalid format: $_" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Resources/ScriptConfig.Formats.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Resources/ScriptConfig.Types.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/ScriptConfig.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | # Script module or binary module file associated with this manifest. 3 | RootModule = 'ScriptConfig.psm1' 4 | 5 | # Version number of this module. 6 | ModuleVersion = '3.1.0' 7 | 8 | # Supported PSEditions 9 | # CompatiblePSEditions = @() 10 | 11 | # ID used to uniquely identify this module 12 | GUID = '0464897C-2F37-489F-93B2-7F6B9B582761' 13 | 14 | # Author of this module 15 | Author = 'Claudio Spizzi' 16 | 17 | # Company or vendor of this module 18 | # CompanyName = '' 19 | 20 | # Copyright statement for this module 21 | Copyright = 'Copyright (c) 2019 by Claudio Spizzi. Licensed under MIT license.' 22 | 23 | # Description of the functionality provided by this module 24 | Description = 'PowerShell Module to handle configuration files for PowerShell controller scripts.' 25 | 26 | # Minimum version of the Windows PowerShell engine required by this module 27 | PowerShellVersion = '3.0' 28 | 29 | # Name of the Windows PowerShell host required by this module 30 | # PowerShellHostName = '' 31 | 32 | # Minimum version of the Windows PowerShell host required by this module 33 | # PowerShellHostVersion = '' 34 | 35 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 36 | # DotNetFrameworkVersion = '' 37 | 38 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 39 | # CLRVersion = '' 40 | 41 | # Processor architecture (None, X86, Amd64) required by this module 42 | # ProcessorArchitecture = '' 43 | 44 | # Modules that must be imported into the global environment prior to importing this module 45 | # RequiredModules = @() 46 | 47 | # Assemblies that must be loaded prior to importing this module 48 | # RequiredAssemblies = @() 49 | 50 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 51 | # ScriptsToProcess = @() 52 | 53 | # Type files (.ps1xml) to be loaded when importing this module 54 | TypesToProcess = @( 55 | 'Resources\ScriptConfig.Types.ps1xml' 56 | ) 57 | 58 | # Format files (.ps1xml) to be loaded when importing this module 59 | FormatsToProcess = @( 60 | 'Resources\ScriptConfig.Formats.ps1xml' 61 | ) 62 | 63 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 64 | # NestedModules = @() 65 | 66 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 67 | FunctionsToExport = @( 68 | 'Get-ScriptConfig' 69 | ) 70 | 71 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 72 | # CmdletsToExport = @() 73 | 74 | # Variables to export from this module 75 | # VariablesToExport = @() 76 | 77 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 78 | # AliasesToExport = @() 79 | 80 | # DSC resources to export from this module 81 | # DscResourcesToExport = @() 82 | 83 | # List of all modules packaged with this module 84 | # ModuleList = @() 85 | 86 | # List of all files packaged with this module 87 | # FileList = @() 88 | 89 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 90 | PrivateData = @{ 91 | 92 | PSData = @{ 93 | 94 | # Tags applied to this module. These help with module discovery in online galleries. 95 | Tags = @('PSModule', 'Config', 'Configuration', 'Script', 'Controller') 96 | 97 | # A URL to the license for this module. 98 | LicenseUri = 'https://raw.githubusercontent.com/claudiospizzi/ScriptConfig/master/LICENSE' 99 | 100 | # A URL to the main website for this project. 101 | ProjectUri = 'https://github.com/claudiospizzi/ScriptConfig' 102 | 103 | # A URL to an icon representing this module. 104 | # IconUri = '' 105 | 106 | # ReleaseNotes of this module 107 | # ReleaseNotes = '' 108 | 109 | } # End of PSData hashtable 110 | 111 | } # End of PrivateData hashtable 112 | 113 | # HelpInfo URI of this module 114 | # HelpInfoURI = '' 115 | 116 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 117 | # DefaultCommandPrefix = '' 118 | } 119 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/ScriptConfig.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Root module file. 4 | 5 | .DESCRIPTION 6 | The root module file loads all classes, helpers and functions into the 7 | module context. 8 | #> 9 | 10 | 11 | ## Module loader 12 | 13 | # Get and dot source all classes (internal) 14 | Split-Path -Path $PSCommandPath | 15 | Get-ChildItem -Filter 'Classes' -Directory | 16 | Get-ChildItem -Include '*.ps1' -File -Recurse | 17 | ForEach-Object { . $_.FullName } 18 | 19 | # Get and dot source all helper functions (internal) 20 | Split-Path -Path $PSCommandPath | 21 | Get-ChildItem -Filter 'Helpers' -Directory | 22 | Get-ChildItem -Include '*.ps1' -File -Recurse | 23 | ForEach-Object { . $_.FullName } 24 | 25 | # Get and dot source all external functions (public) 26 | Split-Path -Path $PSCommandPath | 27 | Get-ChildItem -Filter 'Functions' -Directory | 28 | Get-ChildItem -Include '*.ps1' -File -Recurse | 29 | ForEach-Object { . $_.FullName } 30 | 31 | 32 | ## Module configuration 33 | 34 | # Module path 35 | New-Variable -Name 'ModulePath' -Value $PSScriptRoot 36 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Integration/IniConfigDemo.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Load the default configuration file in INI format 3 | $Config = Get-ScriptConfig -Format 'INI' 4 | 5 | # Access the configuration settings 6 | Write-Verbose "String :" $Config.MyString 7 | Write-Verbose "Integer Positive :" $Config.MyIntegerPositive 8 | Write-Verbose "Integer Negative :" $Config.MyIntegerNegative 9 | Write-Verbose "Boolean True :" $Config.MyBooleanTrue 10 | Write-Verbose "Boolean False :" $Config.MyBooleanFalse 11 | Write-Verbose "Array :" $Config.MyArray 12 | Write-Verbose "Array Item :" $Config.MyArray[0] 13 | Write-Verbose "Hashtable :" $Config.MyHashtable 14 | Write-Verbose "Hashtable Item :" $Config.MyHashtable['Hello'] 15 | Write-Verbose "Hashtable Keys :" $Config.MyHashtable.Keys 16 | Write-Verbose "Hashtable Values :" $Config.MyHashtable.Values 17 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Integration/IniConfigDemo.ps1.config: -------------------------------------------------------------------------------- 1 | MyString=This is a test INI config file! 2 | MyIntegerPositive=42 3 | MyIntegerNegative=-153 4 | MyBooleanTrue=True 5 | MyBooleanFalse=False 6 | MyArray[]=Lorem 7 | MyArray[]=Ipsum 8 | MyHashtable[Foo]=Bar 9 | MyHashtable[Hello]=World 10 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Integration/JsonConfigDemo.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Load the default configuration file in JSON format 3 | $Config = Get-ScriptConfig -Format 'JSON' 4 | 5 | # Access the configuration settings 6 | Write-Verbose "String :" $Config.MyString 7 | Write-Verbose "Integer Positive :" $Config.MyIntegerPositive 8 | Write-Verbose "Integer Negative :" $Config.MyIntegerNegative 9 | Write-Verbose "Boolean True :" $Config.MyBooleanTrue 10 | Write-Verbose "Boolean False :" $Config.MyBooleanFalse 11 | Write-Verbose "Array :" $Config.MyArray 12 | Write-Verbose "Array Item :" $Config.MyArray[0] 13 | Write-Verbose "Hashtable :" $Config.MyHashtable 14 | Write-Verbose "Hashtable Item :" $Config.MyHashtable['Hello'] 15 | Write-Verbose "Hashtable Keys :" $Config.MyHashtable.Keys 16 | Write-Verbose "Hashtable Values :" $Config.MyHashtable.Values 17 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Integration/JsonConfigDemo.ps1.config: -------------------------------------------------------------------------------- 1 | { 2 | "MyString": "This is a test JSON config file!", 3 | "MyIntegerPositive": 42, 4 | "MyIntegerNegative": -153, 5 | "MyBooleanTrue": true, 6 | "MyBooleanFalse": false, 7 | "MyArray": [ 8 | "Lorem", 9 | "Ipsum" 10 | ], 11 | "MyHashtable": { 12 | "Foo": "Bar", 13 | "Hello": "World" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Integration/XmlConfigDemo.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Load the default configuration file in XML format 3 | $Config = Get-ScriptConfig -Format 'XML' 4 | 5 | # Access the configuration settings 6 | Write-Verbose "String :" $Config.MyString 7 | Write-Verbose "Integer Positive :" $Config.MyIntegerPositive 8 | Write-Verbose "Integer Negative :" $Config.MyIntegerNegative 9 | Write-Verbose "Boolean True :" $Config.MyBooleanTrue 10 | Write-Verbose "Boolean False :" $Config.MyBooleanFalse 11 | Write-Verbose "Array :" $Config.MyArray 12 | Write-Verbose "Array Item :" $Config.MyArray[0] 13 | Write-Verbose "Hashtable :" $Config.MyHashtable 14 | Write-Verbose "Hashtable Item :" $Config.MyHashtable['Hello'] 15 | Write-Verbose "Hashtable Keys :" $Config.MyHashtable.Keys 16 | Write-Verbose "Hashtable Values :" $Config.MyHashtable.Values 17 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Integration/XmlConfigDemo.ps1.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/ConvertFrom-ScriptConfigIni.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | $modulePath = Resolve-Path -Path "$PSScriptRoot\..\..\.." | Select-Object -ExpandProperty Path 3 | $moduleName = Resolve-Path -Path "$PSScriptRoot\..\.." | Get-Item | Select-Object -ExpandProperty BaseName 4 | 5 | Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue 6 | Import-Module -Name "$modulePath\$moduleName" -Force 7 | 8 | InModuleScope ScriptConfig { 9 | 10 | Describe 'ConvertFrom-ScriptConfigIni' { 11 | 12 | It 'should be able to convert the example config file' { 13 | 14 | # Arrange 15 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.ini" 16 | 17 | # Act 18 | $config = ConvertFrom-ScriptConfigIni -Content $content 19 | 20 | # Assert 21 | $config | Should -Not -BeNullOrEmpty 22 | } 23 | 24 | It 'shloud be able to parse a string' { 25 | 26 | # Arrange 27 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.ini" 28 | 29 | # Act 30 | $config = ConvertFrom-ScriptConfigIni -Content $content 31 | 32 | # Assert 33 | $config.MyString | Should -Be 'This is a test INI config file!' 34 | $config.MyString.GetType() | Should -Be ([System.String]) 35 | } 36 | 37 | It 'shloud be able to parse an integer' { 38 | 39 | # Arrange 40 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.ini" 41 | 42 | # Act 43 | $config = ConvertFrom-ScriptConfigIni -Content $content 44 | 45 | # Assert 46 | $config.MyIntegerPositive | Should -Be 42 47 | $config.MyIntegerPositive.GetType() | Should -Be ([System.Int32]) 48 | $config.MyIntegerNegative | Should -Be -153 49 | $config.MyIntegerNegative.GetType() | Should -Be ([System.Int32]) 50 | } 51 | 52 | It 'shloud be able to parse an boolean' { 53 | 54 | # Arrange 55 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.ini" 56 | 57 | # Act 58 | $config = ConvertFrom-ScriptConfigIni -Content $content 59 | 60 | # Assert 61 | $config.MyBooleanTrue | Should -BeTrue 62 | $config.MyBooleanTrue.GetType() | Should -Be ([System.Boolean]) 63 | $config.MyBooleanFalse | Should -BeFalse 64 | $config.MyBooleanFalse.GetType() | Should -Be ([System.Boolean]) 65 | } 66 | 67 | It 'shloud be able to parse an array' { 68 | 69 | # Arrange 70 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.ini" 71 | $expectedArray = @( 'Lorem', 'Ipsum' ) 72 | 73 | # Act 74 | $config = ConvertFrom-ScriptConfigIni -Content $content 75 | 76 | # Assert 77 | $config.MyArray | Should -Not -BeNullOrEmpty 78 | $config.MyArray | Should -Be $expectedArray 79 | $config.MyArray.GetType() | Should -Be ([System.Object[]]) 80 | } 81 | 82 | It 'shloud be able to parse an hashtable' { 83 | 84 | # Arrange 85 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.ini" 86 | $expectedHashtable = @{ Foo = 'Bar'; Hello = 'World' } 87 | 88 | # Act 89 | $config = ConvertFrom-ScriptConfigIni -Content $content 90 | 91 | # Assert 92 | $config.MyHashtable | Should -Not -BeNullOrEmpty 93 | $config.MyHashtable.Keys -as [string[]] | Should -Be ($expectedHashtable.Keys -as [string[]]) 94 | $config.MyHashtable.Values -as [string[]] | Should -Be ($expectedHashtable.Values -as [string[]]) 95 | $config.MyHashtable.GetType() | Should -Be ([System.Collections.Hashtable]) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/ConvertFrom-ScriptConfigJson.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | $modulePath = Resolve-Path -Path "$PSScriptRoot\..\..\.." | Select-Object -ExpandProperty Path 3 | $moduleName = Resolve-Path -Path "$PSScriptRoot\..\.." | Get-Item | Select-Object -ExpandProperty BaseName 4 | 5 | Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue 6 | Import-Module -Name "$modulePath\$moduleName" -Force 7 | 8 | InModuleScope ScriptConfig { 9 | 10 | Describe 'ConvertFrom-ScriptConfigJson' { 11 | 12 | It 'should be able to convert the example config file' { 13 | 14 | # Arrange 15 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.json" 16 | 17 | # Act 18 | $config = ConvertFrom-ScriptConfigJson -Content $content 19 | 20 | # Assert 21 | $config | Should -Not -BeNullOrEmpty 22 | } 23 | 24 | It 'shloud be able to parse a string' { 25 | 26 | # Arrange 27 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.json" 28 | 29 | # Act 30 | $config = ConvertFrom-ScriptConfigJson -Content $content 31 | 32 | # Assert 33 | $config.MyString | Should -Be 'This is a test JSON config file!' 34 | $config.MyString.GetType() | Should -Be ([System.String]) 35 | } 36 | 37 | It 'shloud be able to parse an integer' { 38 | 39 | # Arrange 40 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.json" 41 | 42 | # Act 43 | $config = ConvertFrom-ScriptConfigJson -Content $content 44 | 45 | # Assert 46 | $config.MyIntegerPositive | Should -Be 42 47 | $config.MyIntegerPositive.GetType() | Should -Be ([System.Int32]) 48 | $config.MyIntegerNegative | Should -Be -153 49 | $config.MyIntegerNegative.GetType() | Should -Be ([System.Int32]) 50 | } 51 | 52 | It 'shloud be able to parse an boolean' { 53 | 54 | # Arrange 55 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.json" 56 | 57 | # Act 58 | $config = ConvertFrom-ScriptConfigJson -Content $content 59 | 60 | # Assert 61 | $config.MyBooleanTrue | Should -BeTrue 62 | $config.MyBooleanTrue.GetType() | Should -Be ([System.Boolean]) 63 | $config.MyBooleanFalse | Should -BeFalse 64 | $config.MyBooleanFalse.GetType() | Should -Be ([System.Boolean]) 65 | } 66 | 67 | It 'shloud be able to parse an array' { 68 | 69 | # Arrange 70 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.json" 71 | $expectedArray = @( 'Lorem', 'Ipsum' ) 72 | 73 | # Act 74 | $config = ConvertFrom-ScriptConfigJson -Content $content 75 | 76 | # Assert 77 | $config.MyArray | Should -Not -BeNullOrEmpty 78 | $config.MyArray | Should -Be $expectedArray 79 | $config.MyArray.GetType() | Should -Be ([System.Object[]]) 80 | } 81 | 82 | It 'shloud be able to parse an hashtable' { 83 | 84 | # Arrange 85 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.json" 86 | $expectedHashtable = @{ Foo = 'Bar'; Hello = 'World' } 87 | 88 | # Act 89 | $config = ConvertFrom-ScriptConfigJson -Content $content 90 | 91 | # Assert 92 | $config.MyHashtable | Should -Not -BeNullOrEmpty 93 | $config.MyHashtable.Keys -as [string[]] | Should -Be ($expectedHashtable.Keys -as [string[]]) 94 | $config.MyHashtable.Values -as [string[]] | Should -Be ($expectedHashtable.Values -as [string[]]) 95 | $config.MyHashtable.GetType() | Should -Be ([System.Collections.Hashtable]) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/ConvertFrom-ScriptConfigXml.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | $modulePath = Resolve-Path -Path "$PSScriptRoot\..\..\.." | Select-Object -ExpandProperty Path 3 | $moduleName = Resolve-Path -Path "$PSScriptRoot\..\.." | Get-Item | Select-Object -ExpandProperty BaseName 4 | 5 | Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue 6 | Import-Module -Name "$modulePath\$moduleName" -Force 7 | 8 | InModuleScope ScriptConfig { 9 | 10 | Describe 'ConvertFrom-ScriptConfigXml' { 11 | 12 | It 'should be able to convert the example config file' { 13 | 14 | # Arrange 15 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.xml" 16 | 17 | # Act 18 | $config = ConvertFrom-ScriptConfigXml -Content $content 19 | 20 | # Assert 21 | $config | Should -Not -BeNullOrEmpty 22 | } 23 | 24 | It 'shloud be able to parse a string' { 25 | 26 | # Arrange 27 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.xml" 28 | 29 | # Act 30 | $config = ConvertFrom-ScriptConfigXml -Content $content 31 | 32 | # Assert 33 | $config.MyString | Should -Be 'This is a test XML config file!' 34 | $config.MyString.GetType() | Should -Be ([System.String]) 35 | } 36 | 37 | It 'shloud be able to parse an integer' { 38 | 39 | # Arrange 40 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.xml" 41 | 42 | # Act 43 | $config = ConvertFrom-ScriptConfigXml -Content $content 44 | 45 | # Assert 46 | $config.MyIntegerPositive | Should -Be 42 47 | $config.MyIntegerPositive.GetType() | Should -Be ([System.Int32]) 48 | $config.MyIntegerNegative | Should -Be -153 49 | $config.MyIntegerNegative.GetType() | Should -Be ([System.Int32]) 50 | } 51 | 52 | It 'shloud be able to parse an boolean' { 53 | 54 | # Arrange 55 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.xml" 56 | 57 | # Act 58 | $config = ConvertFrom-ScriptConfigXml -Content $content 59 | 60 | # Assert 61 | $config.MyBooleanTrue | Should -BeTrue 62 | $config.MyBooleanTrue.GetType() | Should -Be ([System.Boolean]) 63 | $config.MyBooleanFalse | Should -BeFalse 64 | $config.MyBooleanFalse.GetType() | Should -Be ([System.Boolean]) 65 | } 66 | 67 | It 'shloud be able to parse an array' { 68 | 69 | # Arrange 70 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.xml" 71 | $expectedArray = @( 'Lorem', 'Ipsum' ) 72 | 73 | # Act 74 | $config = ConvertFrom-ScriptConfigXml -Content $content 75 | 76 | # Assert 77 | $config.MyArray | Should -Not -BeNullOrEmpty 78 | $config.MyArray | Should -Be $expectedArray 79 | $config.MyArray.GetType() | Should -Be ([System.Object[]]) 80 | } 81 | 82 | It 'shloud be able to parse an hashtable' { 83 | 84 | # Arrange 85 | $content = Get-Content -Path "$PSScriptRoot\TestData\config.xml" 86 | $expectedHashtable = @{ Foo = 'Bar'; Hello = 'World' } 87 | 88 | # Act 89 | $config = ConvertFrom-ScriptConfigXml -Content $content 90 | 91 | # Assert 92 | $config.MyHashtable | Should -Not -BeNullOrEmpty 93 | $config.MyHashtable.Keys -as [string[]] | Should -Be ($expectedHashtable.Keys -as [string[]]) 94 | $config.MyHashtable.Values -as [string[]] | Should -Be ($expectedHashtable.Values -as [string[]]) 95 | $config.MyHashtable.GetType() | Should -Be ([System.Collections.Hashtable]) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/Get-ScriptConfig.Tests.ps1: -------------------------------------------------------------------------------- 1 | 2 | $modulePath = Resolve-Path -Path "$PSScriptRoot\..\..\.." | Select-Object -ExpandProperty Path 3 | $moduleName = Resolve-Path -Path "$PSScriptRoot\..\.." | Get-Item | Select-Object -ExpandProperty BaseName 4 | 5 | Remove-Module -Name $moduleName -Force -ErrorAction SilentlyContinue 6 | Import-Module -Name "$modulePath\$moduleName" -Force 7 | 8 | Describe 'Get-ScriptConfig' { 9 | 10 | Context 'Test Data' { 11 | 12 | It 'shloud be able to load a valid INI configuration file' { 13 | 14 | # Arrange 15 | $expectedArray = @( 'Lorem', 'Ipsum' ) 16 | $expectedHashtable = @{ Foo = 'Bar'; Hello = 'World' } 17 | 18 | # Act 19 | $config = Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.ini" -Format 'INI' 20 | 21 | # Assert 22 | $config | Should -Not -BeNullOrEmpty 23 | $config.MyString | Should -Be 'This is a test INI config file!' 24 | $config.MyIntegerPositive | Should -Be 42 25 | $config.MyIntegerNegative | Should -Be -153 26 | $config.MyBooleanTrue | Should -BeTrue 27 | $config.MyBooleanFalse | Should -BeFalse 28 | $config.MyArray | Should -Be $expectedArray 29 | $config.MyHashtable.Keys -as [string[]] | Should -Be ($expectedHashtable.Keys -as [string[]]) 30 | $config.MyHashtable.Values -as [string[]] | Should -Be ($expectedHashtable.Values -as [string[]]) 31 | } 32 | 33 | It 'shloud be able to load a valid JSON configuration file' { 34 | 35 | # Arrange 36 | $expectedArray = @( 'Lorem', 'Ipsum' ) 37 | $expectedHashtable = @{ Foo = 'Bar'; Hello = 'World' } 38 | 39 | # Act 40 | $config = Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.json" -Format 'JSON' 41 | 42 | # Assert 43 | $config | Should -Not -BeNullOrEmpty 44 | $config.MyString | Should -Be 'This is a test JSON config file!' 45 | $config.MyIntegerPositive | Should -Be 42 46 | $config.MyIntegerNegative | Should -Be -153 47 | $config.MyBooleanTrue | Should -BeTrue 48 | $config.MyBooleanFalse | Should -BeFalse 49 | $config.MyArray | Should -Be $expectedArray 50 | $config.MyHashtable.Keys -as [string[]] | Should -Be ($expectedHashtable.Keys -as [string[]]) 51 | $config.MyHashtable.Values -as [string[]] | Should -Be ($expectedHashtable.Values -as [string[]]) 52 | } 53 | 54 | It 'shloud be able to load a valid XML configuration file' { 55 | 56 | # Arrange 57 | $expectedArray = @( 'Lorem', 'Ipsum' ) 58 | $expectedHashtable = @{ Foo = 'Bar'; Hello = 'World' } 59 | 60 | # Act 61 | $config = Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.xml" -Format 'XML' 62 | 63 | # Assert 64 | $config | Should -Not -BeNullOrEmpty 65 | $config.MyString | Should -Be 'This is a test XML config file!' 66 | $config.MyIntegerPositive | Should -Be 42 67 | $config.MyIntegerNegative | Should -Be -153 68 | $config.MyBooleanTrue | Should -BeTrue 69 | $config.MyBooleanFalse | Should -BeFalse 70 | $config.MyArray | Should -Be $expectedArray 71 | $config.MyHashtable.Keys -as [string[]] | Should -Be ($expectedHashtable.Keys -as [string[]]) 72 | $config.MyHashtable.Values -as [string[]] | Should -Be ($expectedHashtable.Values -as [string[]]) 73 | } 74 | } 75 | 76 | Context 'Mocked Convert Script' { 77 | 78 | Mock ConvertFrom-ScriptConfigIni { return @{} } -ModuleName 'ScriptConfig' -Verifiable 79 | Mock ConvertFrom-ScriptConfigJson { return @{} } -ModuleName 'ScriptConfig' -Verifiable 80 | Mock ConvertFrom-ScriptConfigXml { return @{} } -ModuleName 'ScriptConfig' -Verifiable 81 | 82 | It 'should call the INI function if a .ini file is specified' { 83 | 84 | # Act 85 | Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.ini" | Out-Null 86 | 87 | # Assert 88 | Assert-MockCalled 'ConvertFrom-ScriptConfigIni' -ModuleName 'ScriptConfig' -Scope 'It' -Times 1 -Exactly 89 | } 90 | 91 | It 'should call the JSON function if a .json file is specified' { 92 | 93 | # Act 94 | Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.json" | Out-Null 95 | 96 | # Assert 97 | Assert-MockCalled 'ConvertFrom-ScriptConfigJson' -ModuleName 'ScriptConfig' -Scope 'It' -Times 1 -Exactly 98 | } 99 | 100 | It 'should call the XML function if a .xml file is specified' { 101 | 102 | # Act 103 | Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.xml" | Out-Null 104 | 105 | # Assert 106 | Assert-MockCalled 'ConvertFrom-ScriptConfigXml' -ModuleName 'ScriptConfig' -Scope 'It' -Times 1 -Exactly 107 | } 108 | 109 | It 'should call the INI function if INI format is specified' { 110 | 111 | # Act 112 | Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.ini" -Format 'INI' | Out-Null 113 | 114 | # Assert 115 | Assert-MockCalled 'ConvertFrom-ScriptConfigIni' -ModuleName 'ScriptConfig' -Scope 'It' -Times 1 -Exactly 116 | } 117 | 118 | It 'should call the JSON function if JSON format is specified' { 119 | 120 | # Act 121 | Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.json" -Format 'JSON' | Out-Null 122 | 123 | # Assert 124 | Assert-MockCalled 'ConvertFrom-ScriptConfigJson' -ModuleName 'ScriptConfig' -Scope 'It' -Times 1 -Exactly 125 | } 126 | 127 | It 'should call the XML function if XML format is specified' { 128 | 129 | # Act 130 | Get-ScriptConfig -Path "$PSScriptRoot\TestData\config.xml" -Format 'XML' | Out-Null 131 | 132 | # Assert 133 | Assert-MockCalled 'ConvertFrom-ScriptConfigXml' -ModuleName 'ScriptConfig' -Scope 'It' -Times 1 -Exactly 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/TestData/config.ini: -------------------------------------------------------------------------------- 1 | MyString=This is a test INI config file! 2 | MyIntegerPositive=42 3 | MyIntegerNegative=-153 4 | MyBooleanTrue=True 5 | MyBooleanFalse=False 6 | MyArray[]=Lorem 7 | MyArray[]=Ipsum 8 | MyHashtable[Foo]=Bar 9 | MyHashtable[Hello]=World 10 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/TestData/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "MyString": "This is a test JSON config file!", 3 | "MyIntegerPositive": 42, 4 | "MyIntegerNegative": -153, 5 | "MyBooleanTrue": true, 6 | "MyBooleanFalse": false, 7 | "MyArray": [ 8 | "Lorem", 9 | "Ipsum" 10 | ], 11 | "MyHashtable": { 12 | "Foo": "Bar", 13 | "Hello": "World" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/Tests/Unit/TestData/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Modules/ScriptConfig/en-US/about_ScriptConfig.help.txt: -------------------------------------------------------------------------------- 1 | 2 | TOPIC 3 | about_ScriptConfig 4 | 5 | SHORT DESCRIPTION 6 | PowerShell Module to handle configuration files for PowerShell controller 7 | scripts. 8 | 9 | LONG DESCRIPTION 10 | To get more information about this module, check the open source GitHub 11 | repository readme or the cmdlet based help for the module functions: 12 | 13 | LINK 14 | https://github.com/claudiospizzi/ScriptConfig 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PowerShell Gallery - ScriptConfig](https://img.shields.io/badge/PowerShell_Gallery-ScriptConfig-0072C6.svg)](https://www.powershellgallery.com/packages/ScriptConfig) 2 | [![GitHub - Release](https://img.shields.io/github/release/claudiospizzi/ScriptConfig.svg)](https://github.com/claudiospizzi/ScriptConfig/releases) 3 | [![AppVeyor - master](https://img.shields.io/appveyor/ci/claudiospizzi/ScriptConfig/master.svg)](https://ci.appveyor.com/project/claudiospizzi/ScriptConfig/branch/master) 4 | [![AppVeyor - dev](https://img.shields.io/appveyor/ci/claudiospizzi/ScriptConfig/dev.svg)](https://ci.appveyor.com/project/claudiospizzi/ScriptConfig/branch/dev) 5 | 6 | 7 | # ScriptConfig PowerShell Module 8 | 9 | PowerShell Module to handle configuration files for PowerShell controller 10 | scripts. 11 | 12 | 13 | ## Introduction 14 | 15 | With the ScriptConfig module, configuration data can be loaded into a PowerShell 16 | controller script from a dedicated config file. Thanks to the module, it is no 17 | longer necessary to hard-code or paramter-pass the configuration data. 18 | Especially useful for scripts, which run unattended. The module support `XML`, 19 | `JSON` and `INI` formatted config files. Works great in cooperation with the 20 | [ScriptLogger] module to improve controller scripts. 21 | 22 | 23 | ## Features 24 | 25 | * **Get-ScriptConfig** 26 | Loads the configuration from a config file. The path and format can be 27 | specified with parameters. 28 | 29 | 30 | ### Example 31 | 32 | In this example, a `JSON` configuration file is loaded into the script. You will 33 | find an example configuration file in the after next chapter. As soon as you 34 | have imported the configuration data, you will be able to use the returned 35 | object to access the settings with the `.` notation. All available formats for 36 | the config files are listed in the next chapter. 37 | 38 | ```powershell 39 | # Load the configuration from a config file 40 | $config = Get-ScriptConfig -Path 'C:\Scripts\config.json' -Format JSON 41 | 42 | # Access the configuration data from the config variable 43 | Write-Host "Config Data:" $config.MyString 44 | ``` 45 | 46 | 47 | ### Supported Types 48 | 49 | The cmdlet supports multiple types. Depending on the used format, the types have 50 | to be specified differently inside the config file. 51 | 52 | | Type | Description | 53 | | ------------- | ---------------------------------------------------------------------------------------- | 54 | | `String` | A settings is stored as a string by default. | 55 | | `Integer` | If the setting value is a valid integer, it will be casted into an integer. | 56 | | `Boolean` | By specifying the key words `True` or `False`, it will be casted to a boolean type. | 57 | | `Array` | An array of strings can be specified. | 58 | | `Hashtable` | A dictionary of key-value-pairs is supported too. The key and values will be a string. | 59 | 60 | 61 | ### XML Format 62 | 63 | Inside an `XML` formatted config file, it's mandatory to specify the type, the key and the value of each setting. Thanks to this, the config file is type-safe. 64 | 65 | ```xml 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | ``` 85 | 86 | 87 | ### JSON Format 88 | 89 | With the `JSON` format, it's easy to specify the configuraiton data with less overhead. Because of the special notation, the `JSON` format is also type-safe. 90 | 91 | ```json 92 | { 93 | "MyString": "This is a test JSON config file!", 94 | "MyIntegerPositive": 42, 95 | "MyIntegerNegative": -153, 96 | "MyBooleanTrue": true, 97 | "MyBooleanFalse": false, 98 | "MyArray": [ 99 | "Lorem", 100 | "Ipsum" 101 | ], 102 | "MyHashtable": { 103 | "Foo": "Bar", 104 | "Hello": "World" 105 | } 106 | } 107 | ``` 108 | 109 | 110 | ### INI Format 111 | 112 | The last supported format is the `INI` file. To support all types, it is necessary to extend the basic `INI` file convention with `[` and `]`, to specify arrays and hash tables. Sections are not supported and will be ignored. 113 | 114 | ```ini 115 | MyString=This is a test INI config file! 116 | MyIntegerPositive=42 117 | MyIntegerNegative=-153 118 | MyBooleanTrue=True 119 | MyBooleanFalse=False 120 | MyArray[]=Lorem 121 | MyArray[]=Ipsum 122 | MyHashtable[Foo]=Bar 123 | MyHashtable[Hello]=World 124 | ``` 125 | 126 | 127 | ## Versions 128 | 129 | Please find all versions in the [GitHub Releases] section and the release notes 130 | in the [CHANGELOG.md] file. 131 | 132 | 133 | ## Installation 134 | 135 | Use the following command to install the module from the [PowerShell Gallery], 136 | if the PackageManagement and PowerShellGet modules are available: 137 | 138 | ```powershell 139 | # Download and install the module 140 | Install-Module -Name 'ScriptConfig' 141 | ``` 142 | 143 | Alternatively, download the latest release from GitHub and install the module 144 | manually on your local system: 145 | 146 | 1. Download the latest release from GitHub as a ZIP file: [GitHub Releases] 147 | 2. Extract the module and install it: [Installing a PowerShell Module] 148 | 149 | 150 | ## Requirements 151 | 152 | The following minimum requirements are necessary to use this module: 153 | 154 | * Windows PowerShell 3.0 155 | * Windows Server 2008 R2 / Windows 7 156 | 157 | 158 | ## Contribute 159 | 160 | Please feel free to contribute by opening new issues or providing pull requests. 161 | For the best development experience, open this project as a folder in Visual 162 | Studio Code and ensure that the PowerShell extension is installed. 163 | 164 | * [Visual Studio Code] with the [PowerShell Extension] 165 | * [Pester], [PSScriptAnalyzer] and [psake] PowerShell Modules 166 | 167 | 168 | 169 | [ScriptLogger]: https://github.com/claudiospizzi/ScriptLogger 170 | 171 | [PowerShell Gallery]: https://www.powershellgallery.com/packages/ScriptConfig 172 | [GitHub Releases]: https://github.com/claudiospizzi/ScriptConfig/releases 173 | [Installing a PowerShell Module]: https://msdn.microsoft.com/en-us/library/dd878350 174 | 175 | [CHANGELOG.md]: CHANGELOG.md 176 | 177 | [Visual Studio Code]: https://code.visualstudio.com/ 178 | [PowerShell Extension]: https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell 179 | [Pester]: https://www.powershellgallery.com/packages/Pester 180 | [PSScriptAnalyzer]: https://www.powershellgallery.com/packages/PSScriptAnalyzer 181 | [psake]: https://www.powershellgallery.com/packages/psake 182 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | 2 | # PowerShell 5.0 build worker 3 | os: WMF 5 4 | 5 | # Install required Pester and PSScriptAnalyzer modules 6 | install: 7 | - ps: Install-PackageProvider NuGet -Force | Out-Null 8 | - ps: Install-Module posh-git -Force 9 | - ps: Install-Module SecurityFever -Force 10 | - ps: Install-Module psake -Force 11 | - ps: Install-Module Pester -Force 12 | - ps: Install-Module PSScriptAnalyzer -Force 13 | 14 | # Set version to build number 15 | version: '{build}' 16 | 17 | # Build configuration 18 | configuration: Release 19 | platform: Any CPU 20 | 21 | # Execute psake build task 22 | build_script: 23 | - ps: >- 24 | Invoke-psake build.psake.ps1 -taskList Build -notr 25 | 26 | # Execute psake test and analyze task 27 | test_script: 28 | - ps: >- 29 | Invoke-psake build.psake.ps1 -taskList Pester, ScriptAnalyzer -notr 30 | -------------------------------------------------------------------------------- /build.debug.ps1: -------------------------------------------------------------------------------- 1 | 2 | # Append module path for auto-loading 3 | $Env:PSModulePath = "$PSScriptRoot\Modules;$Env:PSModulePath" 4 | 5 | # Import all modules for the debugging session 6 | Get-ChildItem -Path "$PSScriptRoot\Modules" -Directory | ForEach-Object { Remove-Module $_.BaseName -ErrorAction 'SilentlyContinue' } 7 | Get-ChildItem -Path "$PSScriptRoot\Modules" -Directory | ForEach-Object { Import-Module $_.BaseName } 8 | 9 | <# ------------------ PLACE DEBUG COMMANDS AFTER THIS LINE ------------------ #> 10 | -------------------------------------------------------------------------------- /build.psake.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | PowerShell module build script based on psake. 4 | 5 | .DESCRIPTION 6 | This psake build script supports building PowerShell manifest modules 7 | which contain PowerShell script functions and optionally binary .NET C# 8 | libraries. The build script contains the following tasks. 9 | 10 | - Task Verify 11 | Before any build task runs, verify that the build scripts are current. 12 | 13 | - Task Init 14 | Create folders, which are used by the build system: /tst and /bin. 15 | 16 | - Task Clean 17 | Cleans the content of build paths to ensure no side effects of 18 | previous build with the current build. 19 | 20 | - Task Compile 21 | If required, compile the Visual Studio solutions. Ensure that the 22 | build system copies the result into the target module folder. 23 | 24 | - Task Stage 25 | Copy all module files to the build directory excluding the class, 26 | function, helper and test files. These files get merged in the .psm1 27 | file. 28 | 29 | - Task Merge 30 | Copy the content of all .ps1 files within the classes, functions and 31 | helpers folders to the .psm1 file. This ensures a faster loading time 32 | for the module, but still a nice development experience with one 33 | function per file. This is optional and can be controlled by the 34 | setting $ModuleMerge. 35 | 36 | - Task Pester 37 | Invoke all Pester tests within the module and ensure that all tests 38 | pass before the build script continues. 39 | 40 | - Task ScriptAnalyzer 41 | Invoke all Script Analyzer rules against the PowerShell script files 42 | and ensure, that they do not break any rule. 43 | 44 | - Task Gallery 45 | This task will publish the module to a PowerShell Gallery. The task is 46 | not part of the default tasks, it needs to be called manually if 47 | needed during a deployment. 48 | 49 | - Task GitHub 50 | This task will publish the module to the GitHub Releases. The task is 51 | not part of the default tasks, it needs to be called manually if 52 | needed during a deployment. 53 | 54 | The tasks are grouped to the following task groups. The deploy task is 55 | not part of the default tasks, this must be invoked manually. 56 | 57 | - Group Default 58 | Task to group the other groups Build and Test. This will be invoked by 59 | default, if Invoke-psake is invoked. 60 | 61 | - Group Build 62 | The build task group will invoke the tasks Init, Clean, Compile, Stage 63 | and Merge. The output is stored in /bin. 64 | 65 | - Group Test 66 | All tasks to verify the integrity of the module with the tasks Pester 67 | and ScriptAnalyzer. 68 | 69 | - Group Deploy 70 | Tasks to deploy the module to the PowerShell Gallery and/or GitHub. 71 | 72 | .NOTES 73 | Author : Claudio Spizzi 74 | License : MIT License 75 | 76 | .LINK 77 | https://github.com/claudiospizzi 78 | #> 79 | 80 | 81 | # Suppress some rules for this build file 82 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingCmdletAliases', '')] 83 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] 84 | param () 85 | 86 | 87 | ## Configuration and Default task 88 | 89 | # Load project configuration 90 | . "$PSScriptRoot\build.settings.ps1" 91 | 92 | # Default build configuration 93 | Properties { 94 | 95 | # Option to disbale the build script verification 96 | $VerifyBuildSystem = $true 97 | 98 | # Module configuration: Location and option to enable the merge 99 | $ModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules' 100 | $ModuleNames = '' 101 | $ModuleMerge = $false 102 | 103 | # Source configuration: Visual Studio projects to compile 104 | $SourcePath = Join-Path -Path $PSScriptRoot -ChildPath 'Sources' 105 | $SourceNames = '' 106 | $SourcePublish = '' 107 | 108 | # Path were the release files are stored 109 | $ReleasePath = Join-Path -Path $PSScriptRoot -ChildPath 'bin' 110 | 111 | # Configure the Pester test execution 112 | $PesterPath = Join-Path -Path $PSScriptRoot -ChildPath 'tst' 113 | $PesterFile = 'pester.xml' 114 | 115 | # Configure the Script Analyzer rules 116 | $ScriptAnalyzerPath = Join-Path -Path $PSScriptRoot -ChildPath 'tst' 117 | $ScriptAnalyzerFile = 'scriptanalyzer.json' 118 | $ScriptAnalyzerRules = Get-ScriptAnalyzerRule 119 | 120 | # Define if the module is published to the PowerShell Gallery 121 | $GalleryEnabled = $false 122 | $GalleryName = 'PSGallery' 123 | $GallerySource = 'https://www.powershellgallery.com/api/v2/' 124 | $GalleryPublish = 'https://www.powershellgallery.com/api/v2/package/' 125 | $GalleryKey = '' 126 | 127 | # Define if the module is published to the GitHub Releases section 128 | $GitHubEnabled = $false 129 | $GitHubRepoName = '' 130 | $GitHubToken = '' 131 | } 132 | 133 | # Default task 134 | Task Default -depends Build, Test 135 | 136 | 137 | ## Build tasks 138 | 139 | # Overall build task 140 | Task Build -depends Verify, Init, Clean, Compile, Stage, Merge 141 | 142 | # Verify the build system 143 | Task Verify -requiredVariables VerifyBuildSystem { 144 | 145 | if ($VerifyBuildSystem) 146 | { 147 | $files = 'build.psake.ps1' 148 | 149 | foreach ($file in $files) 150 | { 151 | # Download reference file 152 | Invoke-WebRequest -Uri "https://raw.githubusercontent.com/claudiospizzi/PSModuleTemplate/master/Template/$file" -OutFile "$Env:Temp\$file" 153 | 154 | # Get content (don't compare hashes, because of new line chars) 155 | $expected = Get-Content -Path "$Env:Temp\$file" 156 | $actual = Get-Content -Path "$PSScriptRoot\$file" 157 | 158 | # Compare objects 159 | Assert -conditionToCheck ($null -eq (Compare-Object -ReferenceObject $expected -DifferenceObject $actual)) -failureMessage "The file '$file' is not current. Please update the file and restart the build." 160 | } 161 | } 162 | else 163 | { 164 | Write-Warning 'Build system is not verified!' 165 | } 166 | } 167 | 168 | # Create release and test folders 169 | Task Init -requiredVariables ReleasePath, PesterPath, ScriptAnalyzerPath { 170 | 171 | if (!(Test-Path -Path $ReleasePath)) 172 | { 173 | New-Item -Path $ReleasePath -ItemType Directory -Verbose:$VerbosePreference > $null 174 | } 175 | 176 | if (!(Test-Path -Path $PesterPath)) 177 | { 178 | New-Item -Path $PesterPath -ItemType Directory -Verbose:$VerbosePreference > $null 179 | } 180 | 181 | if (!(Test-Path -Path $ScriptAnalyzerPath)) 182 | { 183 | New-Item -Path $ScriptAnalyzerPath -ItemType Directory -Verbose:$VerbosePreference > $null 184 | } 185 | } 186 | 187 | # Remove any items in the release and test folders 188 | Task Clean -depends Init -requiredVariables ReleasePath, PesterPath, ScriptAnalyzerPath, SourcePath, SourceNames { 189 | 190 | Get-ChildItem -Path $ReleasePath | Remove-Item -Recurse -Force -Verbose:$VerbosePreference 191 | 192 | Get-ChildItem -Path $PesterPath | Remove-Item -Recurse -Force -Verbose:$VerbosePreference 193 | 194 | Get-ChildItem -Path $ScriptAnalyzerPath | Remove-Item -Recurse -Force -Verbose:$VerbosePreference 195 | 196 | if ($null -ne $SourceNames -and -not [string]::IsNullOrEmpty($SourceNames)) 197 | { 198 | $msBuildPath = 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin' 199 | 200 | if ($Env:Path -notlike "*$msBuildPath*") 201 | { 202 | $Env:Path = "$msBuildPath;$Env:Path" 203 | } 204 | 205 | foreach ($sourceName in $SourceNames) 206 | { 207 | $msBuildLog = (MSBuild.exe "$SourcePath\$sourceName\$sourceName.sln" /target:Clean /p:Configuration=Release) 208 | 209 | $msBuildLog | ForEach-Object { Write-Verbose $_ } 210 | } 211 | } 212 | } 213 | 214 | # Compile C# solutions 215 | Task Compile -depends Clean -requiredVariables SourcePath, SourcePublish, SourceNames { 216 | 217 | if ($null -ne $SourceNames -and -not [string]::IsNullOrEmpty($SourceNames)) 218 | { 219 | $msBuildPath = 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin' 220 | 221 | if ($Env:Path -notlike "*$msBuildPath*") 222 | { 223 | $Env:Path = "$msBuildPath;$Env:Path" 224 | } 225 | 226 | foreach ($sourceName in $SourceNames) 227 | { 228 | $nuGetLog = (nuget.exe restore "Sources\$sourceName") 229 | 230 | $nuGetLog | ForEach-Object { Write-Verbose $_ } 231 | 232 | if ([String]::IsNullOrEmpty($SourcePublish)) 233 | { 234 | $msBuildLog = (MSBuild.exe "$SourcePath\$sourceName\$sourceName.sln" /target:Build /p:Configuration=Release /verbosity:m) 235 | } 236 | else 237 | { 238 | $msBuildLog = (MSBuild.exe "$SourcePath\$sourceName\$sourceName.sln" /target:Build /p:Configuration=Release /p:DeployOnBuild=true /p:PublishProfile=$SourcePublish /verbosity:m) 239 | } 240 | 241 | $msBuildLog | ForEach-Object { Write-Verbose $_ } 242 | } 243 | } 244 | } 245 | 246 | # Copy all required module files to the release folder 247 | Task Stage -depends Compile -requiredVariables ReleasePath, ModulePath, ModuleNames, ModuleMerge { 248 | 249 | if ($null -ne $ModuleNames -and -not [string]::IsNullOrEmpty($ModuleNames)) 250 | { 251 | foreach ($moduleName in $ModuleNames) 252 | { 253 | New-Item -Path "$ReleasePath\$moduleName" -ItemType 'Directory' | Out-Null 254 | 255 | # If the module is merged, exclude the module definition file and 256 | # all classes, functions and helpers. 257 | $excludePath = @() 258 | if ($ModuleMerge) 259 | { 260 | $excludePath += "$moduleName.psd1", 'Classes', 'Functions', 'Helpers' 261 | } 262 | 263 | foreach ($item in (Get-ChildItem -Path "$ModulePath\$moduleName" -Exclude $excludePath)) 264 | { 265 | Copy-Item -Path $item.FullName -Destination "$ReleasePath\$moduleName\$($item.Name)" -Recurse -Verbose:$VerbosePreference 266 | } 267 | } 268 | } 269 | } 270 | 271 | # Merge the module by copying all helper and cmdlet functions to the psm1 file 272 | Task Merge -depends Stage -requiredVariables ReleasePath, ModulePath, ModuleNames, ModuleMerge { 273 | 274 | if ($null -ne $ModuleNames -and -not [string]::IsNullOrEmpty($ModuleNames)) 275 | { 276 | foreach ($moduleName in $ModuleNames) 277 | { 278 | try 279 | { 280 | if ($ModuleMerge) 281 | { 282 | $moduleContent = New-Object -TypeName 'System.Collections.Generic.List[System.String]' 283 | $moduleDefinition = New-Object -TypeName 'System.Collections.Generic.List[System.String]' 284 | 285 | # Load code of the module namespace loader 286 | if ((Get-Content -Path "$ModulePath\$moduleName\$moduleName.psm1" -Raw) -match '#region Namepsace Loader[\r\n](?[\S\s]*)[\r\n]#endregion Namepsace Loader') 287 | { 288 | $moduleContent.Add($matches['NamespaceLoader']) 289 | } 290 | 291 | # Load code for all class files 292 | foreach ($file in (Get-ChildItem -Path "$ModulePath\$moduleName\Classes" -Filter '*.ps1' -Recurse -File -ErrorAction 'SilentlyContinue')) 293 | { 294 | $moduleContent.Add((Get-Content -Path $file.FullName -Raw)) 295 | } 296 | 297 | # Load code for all function files 298 | foreach ($file in (Get-ChildItem -Path "$ModulePath\$moduleName\Functions" -Filter '*.ps1' -Recurse -File -ErrorAction 'SilentlyContinue')) 299 | { 300 | $moduleContent.Add((Get-Content -Path $file.FullName -Raw)) 301 | } 302 | 303 | # Load code for all helpers files 304 | foreach ($file in (Get-ChildItem -Path "$ModulePath\$moduleName\Helpers" -Filter '*.ps1' -Recurse -File -ErrorAction 'SilentlyContinue')) 305 | { 306 | $moduleContent.Add((Get-Content -Path $file.FullName -Raw)) 307 | } 308 | 309 | # Load code of the module namespace loader 310 | if ((Get-Content -Path "$ModulePath\$moduleName\$moduleName.psm1" -Raw) -match '#region Module Configuration[\r\n](?[\S\s]*)#endregion Module Configuration') 311 | { 312 | $moduleContent.Add($matches['ModuleConfiguration']) 313 | } 314 | 315 | # Concatenate whole code into the module file 316 | $moduleContent | Set-Content -Path "$ReleasePath\$moduleName\$moduleName.psm1" -Encoding UTF8 -Verbose:$VerbosePreference 317 | 318 | # Load the current content of the mudile definition 319 | $moduleDefinitionProcess = $true 320 | foreach ($moduleDefinitionLine in (Get-Content -Path "$ModulePath\$moduleName\$moduleName.psd1")) 321 | { 322 | if ($moduleDefinitionLine -like '*ScriptsToProcess*') 323 | { 324 | $moduleDefinition.Add(' # ScriptsToProcess = @()') 325 | $moduleDefinitionProcess = $false 326 | } 327 | 328 | if ($moduleDefinitionProcess) 329 | { 330 | $moduleDefinition.Add($moduleDefinitionLine) 331 | } 332 | 333 | if ($moduleDefinitionLine -like '*)*') 334 | { 335 | $moduleDefinitionProcess = $true 336 | } 337 | } 338 | 339 | # Save the updated module definition 340 | $moduleDefinition | Set-Content -Path "$ReleasePath\$moduleName\$moduleName.psd1" -Encoding UTF8 -Verbose:$VerbosePreference 341 | } 342 | 343 | # Compress 344 | Compress-Archive -Path "$ReleasePath\$moduleName" -DestinationPath "$ReleasePath\$moduleName.zip" -Verbose:$VerbosePreference 345 | 346 | # Publish AppVeyor artifacts 347 | if ($env:APPVEYOR) 348 | { 349 | Push-AppveyorArtifact -Path "$ReleasePath\$moduleName.zip" -DeploymentName $moduleName -Verbose:$VerbosePreference 350 | } 351 | } 352 | catch 353 | { 354 | Assert -conditionToCheck $false -failureMessage "Build failed: $_" 355 | } 356 | } 357 | } 358 | } 359 | 360 | 361 | ## Test tasks 362 | 363 | # Overall test task 364 | Task Test -depends Build, Pester, ScriptAnalyzer 365 | 366 | # Invoke Pester tests and return result as NUnit XML file 367 | Task Pester -requiredVariables ReleasePath, ModuleNames, PesterPath, PesterFile { 368 | 369 | if (!(Get-Module -Name 'Pester' -ListAvailable)) 370 | { 371 | Write-Warning "Pester module is not installed. Skipping $($psake.context.currentTaskName) task." 372 | return 373 | } 374 | 375 | Import-Module -Name 'Pester' 376 | 377 | foreach ($moduleName in $ModuleNames) 378 | { 379 | $modulePesterFile = Join-Path -Path $PesterPath -ChildPath "$moduleName-$PesterFile" 380 | 381 | powershell.exe -NoLogo -NoProfile -NonInteractive -Command "Set-Location -Path '$ReleasePath\$moduleName'; Invoke-Pester -OutputFile '$modulePesterFile' -OutputFormat 'NUnitXml'" 382 | 383 | $testResults = [Xml] (Get-Content -Path $modulePesterFile) 384 | 385 | Assert -conditionToCheck ($testResults.'test-results'.failures -eq 0) -failureMessage "One or more Pester tests failed, build cannot continue." 386 | 387 | # Publish AppVeyor test results 388 | if ($env:APPVEYOR) 389 | { 390 | $webClient = New-Object -TypeName 'System.Net.WebClient' 391 | $webClient.UploadFile("https://ci.appveyor.com/api/testresults/nunit/$env:APPVEYOR_JOB_ID", $modulePesterFile) 392 | } 393 | } 394 | } 395 | 396 | # Invoke Script Analyzer tests and stop if any test fails 397 | Task ScriptAnalyzer -requiredVariables ReleasePath, ModulePath, ModuleNames, ScriptAnalyzerPath, ScriptAnalyzerFile, ScriptAnalyzerRules { 398 | 399 | if (!(Get-Module -Name 'PSScriptAnalyzer' -ListAvailable)) 400 | { 401 | Write-Warning "PSScriptAnalyzer module is not installed. Skipping $($psake.context.currentTaskName) task." 402 | return 403 | } 404 | 405 | Import-Module -Name 'PSScriptAnalyzer' 406 | 407 | foreach ($moduleName in $ModuleNames) 408 | { 409 | $moduleScriptAnalyzerFile = Join-Path -Path $ScriptAnalyzerPath -ChildPath "$moduleName-$ScriptAnalyzerFile" 410 | 411 | # Invoke script analyzer on the module but exclude all examples 412 | $analyzeResults = Invoke-ScriptAnalyzer -Path "$ReleasePath\$moduleName" -IncludeRule $ScriptAnalyzerRules -Recurse 413 | $analyzeResults = $analyzeResults | Where-Object { $_.ScriptPath -notlike "$releasePath\$moduleName\Examples\*" } 414 | $analyzeResults | ConvertTo-Json | Out-File -FilePath $moduleScriptAnalyzerFile -Encoding UTF8 415 | 416 | Show-ScriptAnalyzerResult -ModuleName $moduleName -Rule $ScriptAnalyzerRules -Result $analyzeResults 417 | 418 | Assert -conditionToCheck ($analyzeResults.Where({$_.Severity -ne 0}).Count -eq 0) -failureMessage "One or more Script Analyzer tests failed, build cannot continue." 419 | } 420 | } 421 | 422 | 423 | ## Deploy tasks 424 | 425 | # Overall deploy task 426 | Task Deploy -depends Test, GitHub, Gallery 427 | 428 | # Deploy a release to the GitHub repository 429 | Task GitHub -requiredVariables ReleasePath, ModuleNames, GitHubEnabled, GitHubRepoName, GitHubToken { 430 | 431 | if (!$GitHubEnabled) 432 | { 433 | return 434 | } 435 | 436 | if ([String]::IsNullOrEmpty($GitHubToken)) 437 | { 438 | throw 'GitHub key is null or empty!' 439 | } 440 | 441 | Test-GitRepo @($ModuleNames)[0] 442 | 443 | $plainGitHubToken = $GitHubToken | Unprotect-SecureString 444 | 445 | foreach ($moduleName in $ModuleNames) 446 | { 447 | $moduleVersion = (Import-PowerShellDataFile -Path "$ReleasePath\$moduleName\$moduleName.psd1").ModuleVersion 448 | $releaseNotes = Get-ReleaseNote -Version $moduleVersion 449 | 450 | # Add TLS 1.2 for GitHub 451 | if (([Net.ServicePointManager]::SecurityProtocol -band [Net.SecurityProtocolType]::Tls12) -ne [Net.SecurityProtocolType]::Tls12) 452 | { 453 | [Net.ServicePointManager]::SecurityProtocol += [Net.SecurityProtocolType]::Tls12 454 | } 455 | 456 | # Create GitHub release 457 | $releaseParams = @{ 458 | Method = 'Post' 459 | Uri = "https://api.github.com/repos/$GitHubRepoName/releases" 460 | Headers = @{ 461 | 'Accept' = 'application/vnd.github.v3+json' 462 | 'Authorization' = "token $plainGitHubToken" 463 | } 464 | Body = @{ 465 | tag_name = $moduleVersion 466 | target_commitish = 'master' 467 | name = "$moduleName v$moduleVersion" 468 | body = ($releaseNotes -join "`n") 469 | draft = $false 470 | prerelease = $false 471 | } | ConvertTo-Json 472 | } 473 | $release = Invoke-RestMethod @releaseParams -ErrorAction Stop 474 | 475 | # Upload artifact to GitHub 476 | $artifactParams = @{ 477 | Method = 'Post' 478 | Uri = "https://uploads.github.com/repos/$GitHubRepoName/releases/$($release.id)/assets?name=$moduleName-$moduleVersion.zip" 479 | Headers = @{ 480 | 'Accept' = 'application/vnd.github.v3+json' 481 | 'Authorization' = "token $plainGitHubToken" 482 | 'Content-Type' = 'application/zip' 483 | } 484 | InFile = "$ReleasePath\$ModuleName.zip" 485 | } 486 | $artifact = Invoke-RestMethod @artifactParams -ErrorAction Stop 487 | } 488 | } 489 | 490 | # Deploy to the public PowerShell Gallery 491 | Task Gallery -requiredVariables ReleasePath, ModuleNames, GalleryEnabled, GalleryName, GallerySource, GalleryPublish, GalleryKey { 492 | 493 | if (!$GalleryEnabled) 494 | { 495 | return 496 | } 497 | 498 | if ([String]::IsNullOrEmpty($GalleryKey)) 499 | { 500 | throw 'PowerShell Gallery key is null or empty!' 501 | } 502 | 503 | Test-GitRepo @($ModuleNames)[0] 504 | 505 | # Register the target PowerShell Gallery, if it does not exist 506 | if ($null -eq (Get-PSRepository -Name $GalleryName -ErrorAction SilentlyContinue)) 507 | { 508 | Register-PSRepository -Name $GalleryName -SourceLocation $GallerySource -PublishLocation $GalleryPublish 509 | } 510 | 511 | foreach ($moduleName in $ModuleNames) 512 | { 513 | $moduleVersion = (Import-PowerShellDataFile -Path "$ReleasePath\$moduleName\$moduleName.psd1").ModuleVersion 514 | $releaseNotes = Get-ReleaseNote -Version $moduleVersion 515 | 516 | $plainGalleryKey = $GalleryKey | Unprotect-SecureString 517 | 518 | Publish-Module -Path "$ReleasePath\$moduleName" -Repository $GalleryName -NuGetApiKey $plainGalleryKey -ReleaseNotes $releaseNotes 519 | } 520 | } 521 | 522 | 523 | ## Helper functions 524 | 525 | # Check if the git repo is ready for a deployment 526 | function Test-GitRepo($ModuleName) 527 | { 528 | $gitStatus = Get-GitStatus 529 | if ($gitStatus.Branch -ne 'master') 530 | { 531 | throw "Git Exception: $($gitStatus.Branch) is checked out, switch to master branch! (git checkout master)" 532 | } 533 | 534 | if ($gitStatus.AheadBy -ne 0) 535 | { 536 | throw "Git Exception: master branch is ahead by $($gitStatus.AheadBy)! (git push)" 537 | } 538 | 539 | $version = (Import-PowerShellDataFile -Path "$ReleasePath\$ModuleName\$ModuleName.psd1").ModuleVersion 540 | 541 | $localTag = (git describe --tags) 542 | if ($version -ne $localTag) 543 | { 544 | throw "Git Exception: Tag $localTag not matches module version $version! (git tag $version)" 545 | } 546 | 547 | $remoteTag = (git ls-remote origin "refs/tags/$version") 548 | if ($remoteTag -notlike "*refs/tags/$version") 549 | { 550 | throw "Git Exception: Local tag $localTag not found on origin remote! (git push --tag)" 551 | } 552 | } 553 | 554 | # Check if a source branch is merged to the target branch 555 | function Get-GitMergeStatus($Branch) 556 | { 557 | $mergedBranches = (git.exe branch --merged "$Branch") 558 | 559 | foreach ($mergedBranch in $mergedBranches) 560 | { 561 | $mergedBranch = $mergedBranch.Trim('* ') 562 | 563 | Write-Output $mergedBranch 564 | } 565 | } 566 | 567 | # Show the Script Analyzer results on the host 568 | function Show-ScriptAnalyzerResult($ModuleName, $Rule, $Result) 569 | { 570 | $colorMap = @{ 571 | ParseError = 'DarkRed' 572 | Error = 'Red' 573 | Warning = 'Yellow' 574 | Information = 'Cyan' 575 | } 576 | 577 | Write-Host "`nModule $ModuleName" -ForegroundColor Green 578 | 579 | foreach ($currentRule in $Rule) 580 | { 581 | Write-Host "`n Rule $($currentRule.RuleName)" -ForegroundColor Green 582 | 583 | $records = $Result.Where({$_.RuleName -eq $currentRule.RuleName}) 584 | 585 | if ($records.Count -eq 0) 586 | { 587 | Write-Host " [+] No rule violation found" -ForegroundColor DarkGreen 588 | } 589 | else 590 | { 591 | foreach ($record in $records) 592 | { 593 | Write-Host " [-] $($record.Severity): $($record.Message)" -ForegroundColor $colorMap[[String]$record.Severity] 594 | Write-Host " at $($record.ScriptPath): line $($record.Line)" -ForegroundColor $colorMap[[String]$record.Severity] 595 | } 596 | } 597 | } 598 | 599 | Write-Host "`nScript Analyzer completed" 600 | Write-Host "Rules: $($Rule.Count) Findings: $($analyzeResults.Count)" 601 | } 602 | 603 | # Extract the Release Notes from the CHANGELOG.md file 604 | function Get-ReleaseNote($Version) 605 | { 606 | $changelogFile = Join-Path -Path $PSScriptRoot -ChildPath 'CHANGELOG.md' 607 | 608 | $releaseNotes = @('Release Notes:') 609 | 610 | $isCurrentVersion = $false 611 | 612 | foreach ($line in (Get-Content -Path $changelogFile)) 613 | { 614 | if ($line -like "## $Version - ????-??-??") 615 | { 616 | $isCurrentVersion = $true 617 | } 618 | elseif ($line -like '## *') 619 | { 620 | $isCurrentVersion = $false 621 | } 622 | 623 | if ($isCurrentVersion -and ($line.StartsWith('* ') -or $line.StartsWith('- '))) 624 | { 625 | $releaseNotes += $line 626 | } 627 | } 628 | 629 | Write-Output $releaseNotes 630 | } 631 | -------------------------------------------------------------------------------- /build.settings.ps1: -------------------------------------------------------------------------------- 1 | 2 | Properties { 3 | 4 | $ModuleNames = 'ScriptConfig' 5 | 6 | $GalleryEnabled = $true 7 | $GalleryKey = Use-VaultSecureString -TargetName 'PowerShell Gallery Key (claudiospizzi)' 8 | 9 | $GitHubEnabled = $true 10 | $GitHubRepoName = 'claudiospizzi/ScriptConfig' 11 | $GitHubToken = Use-VaultSecureString -TargetName 'GitHub Token (claudiospizzi)' 12 | } 13 | --------------------------------------------------------------------------------