From 443e6414ec1ff5187548fcbed3980f4bd9aa31fe Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Wed, 10 Feb 2021 13:17:36 -0500 Subject: [PATCH 1/8] Copy the variables to local scope --- .../Metadata/Public/ConvertFrom-Metadata.ps1 | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 index c965c41..b02b515 100644 --- a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 +++ b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 @@ -135,18 +135,21 @@ function ConvertFrom-Metadata { $Tokens += $Tokens | Where-Object { "StringExpandable" -eq $_.Kind } | Select-Object -ExpandProperty NestedTokens # Work around PowerShell rules about magic variables - # Replace "PSScriptRoot" magic variables with the non-reserved "ScriptRoot" - if ($scriptroots = @($Tokens | Where-Object { ("Variable" -eq $_.Kind) -and ($_.Name -eq "PSScriptRoot") } | ForEach-Object { $_.Extent } )) { - $ScriptContent = $Ast.ToString() - for ($r = $scriptroots.count - 1; $r -ge 0; $r--) { - $ScriptContent = $ScriptContent.Remove($scriptroots[$r].StartOffset, ($scriptroots[$r].EndOffset - $scriptroots[$r].StartOffset)).Insert($scriptroots[$r].StartOffset, '$ScriptRoot') + # Change all the "ValidVariables" to use names like __ModuleBuilder__OriginalName__ + # Later, we'll try to make sure these are all set! + if (($UsedVariables = $Tokens | Where-Object { "Variable" -eq $_.Kind -and $_.Name -in $ValidVariables })) { + if ($scriptroots = @( $UsedVariables | ForEach-Object { $_.Extent | Add-Member NoteProperty Name $_.Name } )) { + $ScriptContent = $Ast.ToString() + for ($r = $scriptroots.count - 1; $r -ge 0; $r--) { + $ScriptContent = $ScriptContent.Remove($scriptroots[$r].StartOffset, ($scriptroots[$r].EndOffset - $scriptroots[$r].StartOffset)).Insert($scriptroots[$r].StartOffset, '$__ModuleBuilder__' + $_.Name + '__') + } } - $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) } + $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) $Script = $AST.GetScriptBlock() try { - $Script.CheckRestrictedLanguage( $ValidCommands, $ValidVariables, $true ) + $Script.CheckRestrictedLanguage( $ValidCommands, [string[]]($ValidVariables -replace ".*", '__ModuleBuilder__$0__'), $true ) } catch { ThrowError -Exception $_.Exception.InnerException -ErrorId "Metadata Error" -Category "InvalidData" -TargetObject $Script } @@ -163,6 +166,14 @@ function ConvertFrom-Metadata { foreach ($point in $Hashtables) { $ScriptContent = $ScriptContent.Insert($point.Position, $point.Type) } + + # Set the ValidVariables in our scope to allow using variables from the caller's scope + foreach ($name in $ValidVariables) { + if (!($Value = $PSCmdlet.SessionState.PSVariable.GetValue($Name))) { + $Value = $Name + } + Set-Variable "__ModuleBuilder__${Name}__" $Value + } $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) $Script = $AST.GetScriptBlock() } From 35365e592a4cef4a2bff82e89cef481f2847c26b Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Thu, 11 Feb 2021 11:12:44 -0500 Subject: [PATCH 2/8] Use PSVariable to get variable values from callers --- Source/Metadata/Public/Import-Metadata.ps1 | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Source/Metadata/Public/Import-Metadata.ps1 b/Source/Metadata/Public/Import-Metadata.ps1 index 4f02a8d..179d143 100644 --- a/Source/Metadata/Public/Import-Metadata.ps1 +++ b/Source/Metadata/Public/Import-Metadata.ps1 @@ -24,11 +24,18 @@ function Import-Metadata { [Switch]$Ordered, # Allows extending the valid variables which are allowed to be referenced in metadata - # BEWARE: This exposes the value of these variables in your context to the caller - # You ware reponsible to only allow variables which you know are safe to share - [String[]]$AllowedVariables + # BEWARE: This exposes the value of these variables in the calling context to the metadata file + # You are reponsible to only allow variables which you know are safe to share + [String[]]$AllowedVariables, + + # You should not pass this. + # The PSVariable parameter is for preserving variable scope within the Metadata commands + [System.Management.Automation.PSVariableIntrinsics]$PSVariable ) process { + if (!$PSVariable) { + $PSVariable = $PSCmdlet.SessionState.PSVariable + } if (Test-Path $Path) { # Write-Debug "Importing Metadata file from `$Path: $Path" if (!(Test-Path $Path -PathType Leaf)) { @@ -43,7 +50,7 @@ function Import-Metadata { return } try { - ConvertFrom-Metadata -InputObject $Path -Converters $Converters -Ordered:$Ordered -AllowedVariables $AllowedVariables + ConvertFrom-Metadata -InputObject $Path -Converters $Converters -Ordered:$Ordered -AllowedVariables $AllowedVariables -PSVariable $PSVariable } catch { ThrowError $_ } From 2879bdaad10d77004df5ac2ceca9a701a0ae02da Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Thu, 11 Feb 2021 11:13:33 -0500 Subject: [PATCH 3/8] Let AllowedVariables flow through the whole module --- .../Public/Import-Configuration.ps1 | 22 ++++++-- .../Public/Import-ParameterConfiguration.ps1 | 15 +++++- .../Metadata/Public/ConvertFrom-Metadata.ps1 | 53 ++++++++++++++----- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/Source/Configuration/Public/Import-Configuration.ps1 b/Source/Configuration/Public/Import-Configuration.ps1 index a3f1122..fdfce4d 100644 --- a/Source/Configuration/Public/Import-Configuration.ps1 +++ b/Source/Configuration/Public/Import-Configuration.ps1 @@ -50,7 +50,12 @@ function Import-Configuration { # 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 + [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" @@ -62,12 +67,19 @@ function Import-Configuration { 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 -ErrorAction Ignore -Ordered:$Ordered + Import-Metadata $DefaultPath @MetadataOptions } else { @{} } @@ -85,7 +97,7 @@ function Import-Configuration { $MachinePath = Get-ConfigurationPath @Parameters -Scope Machine -SkipCreatingFolder $MachinePath = Join-Path $MachinePath Configuration.psd1 $Machine = if (Test-Path $MachinePath) { - Import-Metadata $MachinePath -ErrorAction Ignore -Ordered:$Ordered + Import-Metadata $MachinePath @MetadataOptions } else { @{} } @@ -95,7 +107,7 @@ function Import-Configuration { $EnterprisePath = Get-ConfigurationPath @Parameters -Scope Enterprise -SkipCreatingFolder $EnterprisePath = Join-Path $EnterprisePath Configuration.psd1 $Enterprise = if (Test-Path $EnterprisePath) { - Import-Metadata $EnterprisePath -ErrorAction Ignore -Ordered:$Ordered + Import-Metadata $EnterprisePath @MetadataOptions } else { @{} } @@ -104,7 +116,7 @@ function Import-Configuration { $LocalUserPath = Get-ConfigurationPath @Parameters -Scope User -SkipCreatingFolder $LocalUserPath = Join-Path $LocalUserPath Configuration.psd1 $LocalUser = if (Test-Path $LocalUserPath) { - Import-Metadata $LocalUserPath -ErrorAction Ignore -Ordered:$Ordered + Import-Metadata $LocalUserPath @MetadataOptions } else { @{} } diff --git a/Source/Configuration/Public/Import-ParameterConfiguration.ps1 b/Source/Configuration/Public/Import-ParameterConfiguration.ps1 index 64615a2..ee32745 100644 --- a/Source/Configuration/Public/Import-ParameterConfiguration.ps1 +++ b/Source/Configuration/Public/Import-ParameterConfiguration.ps1 @@ -156,7 +156,12 @@ function Import-ParameterConfiguration { [string]$FileName, # If set, considers configuration files in the parent, and it's parent recursively - [switch]$Recurse + [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") @@ -166,12 +171,18 @@ function Import-ParameterConfiguration { $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 -ErrorAction SilentlyContinue + $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 diff --git a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 index b02b515..a0fa723 100644 --- a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 +++ b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 @@ -43,7 +43,7 @@ function ConvertFrom-Metadata { # The PSScriptRoot which the metadata should be evaluated from. # You do not normally need to pass this, and it has no effect unless - # you're referencing $PSScriptRoot or $ScriptRoot in your metadata + # you're referencing $ScriptRoot in your metadata $ScriptRoot = "$PSScriptRoot", # If set (and PowerShell version 4 or later) preserve the file order of configuration @@ -53,7 +53,11 @@ function ConvertFrom-Metadata { # Allows extending the valid variables which are allowed to be referenced in metadata # BEWARE: This exposes the value of these variables in your context to the caller # You ware reponsible to only allow variables which you know are safe to share - [String[]]$AllowedVariables + [String[]]$AllowedVariables, + + # You should not pass this. + # The PSVariable parameter is for preserving variable scope within the Metadata commands + [System.Management.Automation.PSVariableIntrinsics]$PSVariable ) begin { $OriginalMetadataSerializers = $Script:MetadataSerializers.Clone() @@ -64,6 +68,10 @@ function ConvertFrom-Metadata { ) + @($MetadataDeserializers.Keys) [string[]]$ValidVariables = $AllowedVariables + @( "PSScriptRoot", "ScriptRoot", "PoshCodeModuleRoot", "PSCulture", "PSUICulture", "True", "False", "Null") + + if (!$PSVariable) { + $PSVariable = $PSCmdlet.SessionState.PSVariable + } } end { $Script:MetadataSerializers = $OriginalMetadataSerializers.Clone() @@ -74,6 +82,7 @@ function ConvertFrom-Metadata { $Tokens = $Null; $ParseErrors = $Null if (Test-PSVersion -lt "3.0") { + # Write-Debug "Using Import-LocalizedData to support PowerShell $($PSVersionTable.PSVersion)" # Write-Debug "$InputObject" if (!(Test-Path $InputObject -ErrorAction SilentlyContinue)) { $Path = [IO.path]::ChangeExtension([IO.Path]::GetTempFileName(), $ModuleManifestExtension) @@ -90,6 +99,7 @@ function ConvertFrom-Metadata { } if (Test-Path $InputObject -ErrorAction SilentlyContinue) { + Write-Debug "Using ParseInput to support PowerShell $($PSVersionTable.PSVersion)" # ParseFile on PS5 (and older) doesn't handle utf8 encoding properly (treats it as ASCII) # Sometimes, that causes an avoidable error. So I'm avoiding it, if I can: $Path = Convert-Path $InputObject @@ -103,6 +113,7 @@ function ConvertFrom-Metadata { # But older versions of PowerShell, this will throw a MethodException because the overload is missing $AST = [System.Management.Automation.Language.Parser]::ParseInput($Content, $Path, [ref]$Tokens, [ref]$ParseErrors) } catch [System.Management.Automation.MethodException] { + Write-Debug "Using ParseFile as a backup for PowerShell $($PSVersionTable.PSVersion)" $AST = [System.Management.Automation.Language.Parser]::ParseFile( $Path, [ref]$Tokens, [ref]$ParseErrors) # If we got parse errors on older versions of PowerShell, test to see if the error is just encoding @@ -117,6 +128,7 @@ function ConvertFrom-Metadata { } } } else { + Write-Debug "Using ParseInput with loose metadata: $InputObject" if (!$PSBoundParameters.ContainsKey('ScriptRoot')) { $ScriptRoot = $PoshCodeModuleRoot } @@ -134,27 +146,45 @@ function ConvertFrom-Metadata { # Get the variables or subexpressions from strings which have them ("StringExpandable" vs "String") ... $Tokens += $Tokens | Where-Object { "StringExpandable" -eq $_.Kind } | Select-Object -ExpandProperty NestedTokens + Write-Debug "Searching $($Tokens.Count) variables for: $($ValidVariables -join ', '))" # Work around PowerShell rules about magic variables # Change all the "ValidVariables" to use names like __ModuleBuilder__OriginalName__ # Later, we'll try to make sure these are all set! - if (($UsedVariables = $Tokens | Where-Object { "Variable" -eq $_.Kind -and $_.Name -in $ValidVariables })) { - if ($scriptroots = @( $UsedVariables | ForEach-Object { $_.Extent | Add-Member NoteProperty Name $_.Name } )) { + if (($UsedVariables = $Tokens | Where-Object { ("Variable" -eq $_.Kind) -and ($_.Name -in $ValidVariables) })) { + # Write-Debug "Replacing $($UsedVariables.Name -join ', ')" + if (($scriptroots = @( $UsedVariables | ForEach-Object { $_.Extent | Add-Member NoteProperty Name $_.Name -PassThru } ))) { $ScriptContent = $Ast.ToString() + # Write-Debug "Replacing $($UsedVariables.Count) variables in metadata: $ScriptContent" for ($r = $scriptroots.count - 1; $r -ge 0; $r--) { - $ScriptContent = $ScriptContent.Remove($scriptroots[$r].StartOffset, ($scriptroots[$r].EndOffset - $scriptroots[$r].StartOffset)).Insert($scriptroots[$r].StartOffset, '$__ModuleBuilder__' + $_.Name + '__') + $ScriptContent = $ScriptContent.Remove($scriptroots[$r].StartOffset, ($scriptroots[$r].EndOffset - $scriptroots[$r].StartOffset)).Insert($scriptroots[$r].StartOffset, '${__ModuleBuilder__' + $scriptroots[$r].Name + '__}') } } } + + # Write-Debug "Replaced $($UsedVariables.Name -join ' and ') in metadata: $ScriptContent" $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) $Script = $AST.GetScriptBlock() try { - $Script.CheckRestrictedLanguage( $ValidCommands, [string[]]($ValidVariables -replace ".*", '__ModuleBuilder__$0__'), $true ) + [string[]]$PrivateVariables = $ValidVariables -replace "^.*$", '__ModuleBuilder__$0__' + # Write-Debug "Validating metadata: $Script against $PrivateVariables" + $Script.CheckRestrictedLanguage( $ValidCommands, $PrivateVariables, $true ) } catch { ThrowError -Exception $_.Exception.InnerException -ErrorId "Metadata Error" -Category "InvalidData" -TargetObject $Script } + # Set the __ModuleBuilder__ValidVariables__ in our scope but not for constant variables: + $ReplacementVariables = $ValidVariables | Where-Object { $_ -notin "PSCulture", "PSUICulture", "True", "False", "Null" } + foreach ($name in $ReplacementVariables) { + if (!($Value = $PSVariable.GetValue($Name))) { + $Value = "`${$Name}" + } + # Write-Debug "Setting __ModuleBuilder__${Name}__ = $Value" + Set-Variable "__ModuleBuilder__${Name}__" $Value + } + if ($Ordered -and (Test-PSVersion -gt "3.0")) { + # Write-Debug "Supporting [Ordered] on PowerShell $($PSVersionTable.PSVersion)" # Make all the hashtables ordered, so that the output objects make more sense to humans... if ($Tokens | Where-Object { "AtCurly" -eq $_.Kind }) { $ScriptContent = $AST.ToString() @@ -167,24 +197,19 @@ function ConvertFrom-Metadata { $ScriptContent = $ScriptContent.Insert($point.Position, $point.Type) } - # Set the ValidVariables in our scope to allow using variables from the caller's scope - foreach ($name in $ValidVariables) { - if (!($Value = $PSCmdlet.SessionState.PSVariable.GetValue($Name))) { - $Value = $Name - } - Set-Variable "__ModuleBuilder__${Name}__" $Value - } $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) $Script = $AST.GetScriptBlock() } } - + # Write-Debug "Metadata: $Script" + # Write-Debug "Switching to RestrictedLanguage mode" $Mode, $ExecutionContext.SessionState.LanguageMode = $ExecutionContext.SessionState.LanguageMode, "RestrictedLanguage" try { $Script.InvokeReturnAsIs(@()) } finally { $ExecutionContext.SessionState.LanguageMode = $Mode + # Write-Debug "Switching to $Mode mode" } } } From 285ace810c42c23213fc89c2350ccdcd25b42ba0 Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Fri, 12 Feb 2021 10:14:11 -0500 Subject: [PATCH 4/8] Clean up debug messages --- .../Metadata/Public/ConvertFrom-Metadata.ps1 | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 index a0fa723..2aa177f 100644 --- a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 +++ b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 @@ -82,8 +82,8 @@ function ConvertFrom-Metadata { $Tokens = $Null; $ParseErrors = $Null if (Test-PSVersion -lt "3.0") { - # Write-Debug "Using Import-LocalizedData to support PowerShell $($PSVersionTable.PSVersion)" - # Write-Debug "$InputObject" + # Write-Debug "ConvertFrom-Metadata: Using Import-LocalizedData to support PowerShell $($PSVersionTable.PSVersion)" + # Write-Debug "ConvertFrom-Metadata: $InputObject" if (!(Test-Path $InputObject -ErrorAction SilentlyContinue)) { $Path = [IO.path]::ChangeExtension([IO.Path]::GetTempFileName(), $ModuleManifestExtension) Set-Content -Encoding UTF8 -Path $Path $InputObject @@ -99,7 +99,7 @@ function ConvertFrom-Metadata { } if (Test-Path $InputObject -ErrorAction SilentlyContinue) { - Write-Debug "Using ParseInput to support PowerShell $($PSVersionTable.PSVersion)" + # Write-Debug "ConvertFrom-Metadata: Using ParseInput to support PowerShell $($PSVersionTable.PSVersion)" # ParseFile on PS5 (and older) doesn't handle utf8 encoding properly (treats it as ASCII) # Sometimes, that causes an avoidable error. So I'm avoiding it, if I can: $Path = Convert-Path $InputObject @@ -113,7 +113,7 @@ function ConvertFrom-Metadata { # But older versions of PowerShell, this will throw a MethodException because the overload is missing $AST = [System.Management.Automation.Language.Parser]::ParseInput($Content, $Path, [ref]$Tokens, [ref]$ParseErrors) } catch [System.Management.Automation.MethodException] { - Write-Debug "Using ParseFile as a backup for PowerShell $($PSVersionTable.PSVersion)" + # Write-Debug "ConvertFrom-Metadata: Using ParseFile as a backup for PowerShell $($PSVersionTable.PSVersion)" $AST = [System.Management.Automation.Language.Parser]::ParseFile( $Path, [ref]$Tokens, [ref]$ParseErrors) # If we got parse errors on older versions of PowerShell, test to see if the error is just encoding @@ -128,7 +128,7 @@ function ConvertFrom-Metadata { } } } else { - Write-Debug "Using ParseInput with loose metadata: $InputObject" + # Write-Debug "ConvertFrom-Metadata: Using ParseInput with loose metadata: $InputObject" if (!$PSBoundParameters.ContainsKey('ScriptRoot')) { $ScriptRoot = $PoshCodeModuleRoot } @@ -146,28 +146,35 @@ function ConvertFrom-Metadata { # Get the variables or subexpressions from strings which have them ("StringExpandable" vs "String") ... $Tokens += $Tokens | Where-Object { "StringExpandable" -eq $_.Kind } | Select-Object -ExpandProperty NestedTokens - Write-Debug "Searching $($Tokens.Count) variables for: $($ValidVariables -join ', '))" + # Write-Debug "ConvertFrom-Metadata: Searching $($Tokens.Count) variables for: $($ValidVariables -join ', '))" # Work around PowerShell rules about magic variables # Change all the "ValidVariables" to use names like __ModuleBuilder__OriginalName__ # Later, we'll try to make sure these are all set! if (($UsedVariables = $Tokens | Where-Object { ("Variable" -eq $_.Kind) -and ($_.Name -in $ValidVariables) })) { - # Write-Debug "Replacing $($UsedVariables.Name -join ', ')" + # Write-Debug "ConvertFrom-Metadata: Replacing $($UsedVariables.Name -join ', ')" if (($scriptroots = @( $UsedVariables | ForEach-Object { $_.Extent | Add-Member NoteProperty Name $_.Name -PassThru } ))) { $ScriptContent = $Ast.ToString() - # Write-Debug "Replacing $($UsedVariables.Count) variables in metadata: $ScriptContent" + # Write-Debug "ConvertFrom-Metadata: Replacing $($UsedVariables.Count) variables in metadata: $ScriptContent" for ($r = $scriptroots.count - 1; $r -ge 0; $r--) { - $ScriptContent = $ScriptContent.Remove($scriptroots[$r].StartOffset, ($scriptroots[$r].EndOffset - $scriptroots[$r].StartOffset)).Insert($scriptroots[$r].StartOffset, '${__ModuleBuilder__' + $scriptroots[$r].Name + '__}') + $VariableExtent = $scriptroots[$r] + $VariableName = if ($VariableExtent.Name -eq "PSScriptRoot") { + '${__ModuleBuilder__ScriptRoot__}' + } else { + '${__ModuleBuilder__' + $VariableExtent.Name + '__}' + } + $ScriptContent = $ScriptContent.Remove($VariableExtent.StartOffset, ($VariableExtent.EndOffset - $VariableExtent.StartOffset) + ).Insert($VariableExtent.StartOffset, $VariableName) } } } - # Write-Debug "Replaced $($UsedVariables.Name -join ' and ') in metadata: $ScriptContent" + # Write-Debug "ConvertFrom-Metadata: Replaced $($UsedVariables.Name -join ' and ') in metadata: $ScriptContent" $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) $Script = $AST.GetScriptBlock() try { [string[]]$PrivateVariables = $ValidVariables -replace "^.*$", '__ModuleBuilder__$0__' - # Write-Debug "Validating metadata: $Script against $PrivateVariables" + # Write-Debug "ConvertFrom-Metadata: Validating metadata: $Script against $PrivateVariables" $Script.CheckRestrictedLanguage( $ValidCommands, $PrivateVariables, $true ) } catch { ThrowError -Exception $_.Exception.InnerException -ErrorId "Metadata Error" -Category "InvalidData" -TargetObject $Script @@ -179,12 +186,12 @@ function ConvertFrom-Metadata { if (!($Value = $PSVariable.GetValue($Name))) { $Value = "`${$Name}" } - # Write-Debug "Setting __ModuleBuilder__${Name}__ = $Value" + # Write-Debug "ConvertFrom-Metadata: Setting __ModuleBuilder__${Name}__ = $Value" Set-Variable "__ModuleBuilder__${Name}__" $Value } if ($Ordered -and (Test-PSVersion -gt "3.0")) { - # Write-Debug "Supporting [Ordered] on PowerShell $($PSVersionTable.PSVersion)" + # Write-Debug "ConvertFrom-Metadata: Supporting [Ordered] on PowerShell $($PSVersionTable.PSVersion)" # Make all the hashtables ordered, so that the output objects make more sense to humans... if ($Tokens | Where-Object { "AtCurly" -eq $_.Kind }) { $ScriptContent = $AST.ToString() @@ -201,15 +208,15 @@ function ConvertFrom-Metadata { $Script = $AST.GetScriptBlock() } } - # Write-Debug "Metadata: $Script" - # Write-Debug "Switching to RestrictedLanguage mode" + # Write-Debug "ConvertFrom-Metadata: Metadata: $Script" + # Write-Debug "ConvertFrom-Metadata: Switching to RestrictedLanguage mode" $Mode, $ExecutionContext.SessionState.LanguageMode = $ExecutionContext.SessionState.LanguageMode, "RestrictedLanguage" try { $Script.InvokeReturnAsIs(@()) } finally { $ExecutionContext.SessionState.LanguageMode = $Mode - # Write-Debug "Switching to $Mode mode" + # Write-Debug "ConvertFrom-Metadata: Switching to $Mode mode" } } } From bac222efbb9b3d96d39b748feba84a61d256d6ea Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Fri, 2 Jul 2021 23:14:13 -0400 Subject: [PATCH 5/8] Extract Metadata --- Source/{Configuration => }/Configuration.psd1 | 166 ++++---- Source/{Configuration => }/Header/param.ps1 | 0 .../Footer/InitialMetadataConverters.ps1 | 70 ---- Source/Metadata/Header/00. param.ps1 | 5 - .../Header/01. IMetadataSerializable.ps1 | 6 - Source/Metadata/Metadata.psd1 | 29 -- Source/Metadata/Private/FindHashKeyValue.ps1 | 25 -- Source/Metadata/Private/ThrowError.ps1 | 59 --- Source/Metadata/Private/WriteError.ps1 | 56 --- .../Metadata/Public/Add-MetadataConverter.ps1 | 77 ---- .../Metadata/Public/ConvertFrom-Metadata.ps1 | 222 ---------- Source/Metadata/Public/ConvertTo-Metadata.ps1 | 118 ------ Source/Metadata/Public/Export-Metadata.ps1 | 59 --- Source/Metadata/Public/Get-Metadata.ps1 | 92 ---- Source/Metadata/Public/Import-Metadata.ps1 | 58 --- Source/Metadata/Public/Test-PSVersion.ps1 | 39 -- Source/Metadata/Public/Update-Metadata.ps1 | 126 ------ Source/Metadata/Public/Update-Object.ps1 | 106 ----- Source/Metadata/build.psd1 | 6 - .../Private/InitializeStoragePaths.ps1 | 0 .../Private/ParameterBinder.ps1 | 0 .../Public/Export-Configuration.ps1 | 0 .../Public/Get-ConfigurationPath.ps1 | 0 .../Public/Import-Configuration.ps1 | 0 .../Public/Import-ParameterConfiguration.ps1 | 396 +++++++++--------- Source/{Configuration => }/build.psd1 | 0 26 files changed, 281 insertions(+), 1434 deletions(-) rename Source/{Configuration => }/Configuration.psd1 (97%) rename Source/{Configuration => }/Header/param.ps1 (100%) delete mode 100644 Source/Metadata/Footer/InitialMetadataConverters.ps1 delete mode 100644 Source/Metadata/Header/00. param.ps1 delete mode 100644 Source/Metadata/Header/01. IMetadataSerializable.ps1 delete mode 100644 Source/Metadata/Metadata.psd1 delete mode 100644 Source/Metadata/Private/FindHashKeyValue.ps1 delete mode 100644 Source/Metadata/Private/ThrowError.ps1 delete mode 100644 Source/Metadata/Private/WriteError.ps1 delete mode 100644 Source/Metadata/Public/Add-MetadataConverter.ps1 delete mode 100644 Source/Metadata/Public/ConvertFrom-Metadata.ps1 delete mode 100644 Source/Metadata/Public/ConvertTo-Metadata.ps1 delete mode 100644 Source/Metadata/Public/Export-Metadata.ps1 delete mode 100644 Source/Metadata/Public/Get-Metadata.ps1 delete mode 100644 Source/Metadata/Public/Import-Metadata.ps1 delete mode 100644 Source/Metadata/Public/Test-PSVersion.ps1 delete mode 100644 Source/Metadata/Public/Update-Metadata.ps1 delete mode 100644 Source/Metadata/Public/Update-Object.ps1 delete mode 100644 Source/Metadata/build.psd1 rename Source/{Configuration => }/Private/InitializeStoragePaths.ps1 (100%) rename Source/{Configuration => }/Private/ParameterBinder.ps1 (100%) rename Source/{Configuration => }/Public/Export-Configuration.ps1 (100%) rename Source/{Configuration => }/Public/Get-ConfigurationPath.ps1 (100%) rename Source/{Configuration => }/Public/Import-Configuration.ps1 (100%) rename Source/{Configuration => }/Public/Import-ParameterConfiguration.ps1 (97%) rename Source/{Configuration => }/build.psd1 (100%) diff --git a/Source/Configuration/Configuration.psd1 b/Source/Configuration.psd1 similarity index 97% rename from Source/Configuration/Configuration.psd1 rename to Source/Configuration.psd1 index b435b1a..0be314d 100644 --- a/Source/Configuration/Configuration.psd1 +++ b/Source/Configuration.psd1 @@ -1,83 +1,83 @@ -@{ - -# Script module or binary module file associated with this manifest. -ModuleToProcess = 'Configuration.psm1' - -# Version number of this module. -ModuleVersion = '1.7.2' - -# 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-2019 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') - -# List of all files packaged with this module -FileList = @('.\Configuration.psd1','.\Configuration.psm1','.\Metadata.psm1','.\en-US\about_Configuration.help.txt') - -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 = ' - - Fix bug in Get-Metadata with complex values (#19) - - Fix postfix/suffix - - Fix serialization of scriptblocks with single quotes - - Convert the modules to ModuleBuilder format - - Switch build to azure pipelines - - Clean up extra output lines in psd1 files - - Clean up exports - ' - } -} - -} - - +@{ + +# Script module or binary module file associated with this manifest. +ModuleToProcess = 'Configuration.psm1' + +# Version number of this module. +ModuleVersion = '1.7.2' + +# 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-2019 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') + +# List of all files packaged with this module +FileList = @('.\Configuration.psd1','.\Configuration.psm1','.\Metadata.psm1','.\en-US\about_Configuration.help.txt') + +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 = ' + - Fix bug in Get-Metadata with complex values (#19) + - Fix postfix/suffix + - Fix serialization of scriptblocks with single quotes + - Convert the modules to ModuleBuilder format + - Switch build to azure pipelines + - Clean up extra output lines in psd1 files + - Clean up exports + ' + } +} + +} + + diff --git a/Source/Configuration/Header/param.ps1 b/Source/Header/param.ps1 similarity index 100% rename from Source/Configuration/Header/param.ps1 rename to Source/Header/param.ps1 diff --git a/Source/Metadata/Footer/InitialMetadataConverters.ps1 b/Source/Metadata/Footer/InitialMetadataConverters.ps1 deleted file mode 100644 index 738b3cf..0000000 --- a/Source/Metadata/Footer/InitialMetadataConverters.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -$MetadataSerializers = @{} -$MetadataDeserializers = @{} - -if ($Converters -is [Collections.IDictionary]) { - Add-MetadataConverter $Converters -} -function PSCredentialMetadataConverter { - <# - .Synopsis - Creates a new PSCredential with the specified properties - .Description - This is just a wrapper for the PSObject constructor with -Property $Value - It exists purely for the sake of psd1 serialization - .Parameter Value - The hashtable of properties to add to the created objects - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "EncodedPassword")] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPasswordParams", "")] - param( - # The UserName for this credential - [string]$UserName, - # The Password for this credential, encoded via ConvertFrom-SecureString - [string]$EncodedPassword - ) - New-Object PSCredential $UserName, (ConvertTo-SecureString $EncodedPassword) -} - -# The OriginalMetadataSerializers -Add-MetadataConverter @{ - [bool] = { if ($_) { '$True' } else { '$False' } } - [Version] = { "'$_'" } - [PSCredential] = { 'PSCredential "{0}" "{1}"' -f $_.UserName, (ConvertFrom-SecureString $_.Password) } - [SecureString] = { "ConvertTo-SecureString {0}" -f (ConvertFrom-SecureString $_) } - [Guid] = { "Guid '$_'" } - [DateTime] = { "DateTime '{0}'" -f $InputObject.ToString('o') } - [DateTimeOffset] = { "DateTimeOffset '{0}'" -f $InputObject.ToString('o') } - [ConsoleColor] = { "ConsoleColor {0}" -f $InputObject.ToString() } - - [System.Management.Automation.SwitchParameter] = { if ($_) { '$True' } else { '$False' } } - # Escape single-quotes by doubling them: - [System.Management.Automation.ScriptBlock] = { "(ScriptBlock '{0}')" -f ("$_" -replace "'", "''") } - # This GUID is here instead of as a function - # just to make sure the tests can validate the converter hashtables - "Guid" = { [Guid]$Args[0] } - "DateTime" = { [DateTime]$Args[0] } - "DateTimeOffset" = { [DateTimeOffset]$Args[0] } - "ConsoleColor" = { [ConsoleColor]$Args[0] } - "ScriptBlock" = { [scriptblock]::Create($Args[0]) } - "PSCredential" = (Get-Command PSCredentialMetadataConverter).ScriptBlock - "FromPsMetadata" = { - $TypeName, $Args = $Args - $Output = ([Type]$TypeName)::new() - $Output.FromPsMetadata($Args) - $Output - } - "PSObject" = { param([hashtable]$Properties, [string[]]$TypeName) - $Result = New-Object System.Management.Automation.PSObject -Property $Properties - $TypeName += @($Result.PSTypeNames) - $Result.PSTypeNames.Clear() - foreach ($Name in $TypeName) { - $Result.PSTypeNames.Add($Name) - } - $Result } - -} - -$Script:OriginalMetadataSerializers = $script:MetadataSerializers.Clone() -$Script:OriginalMetadataDeserializers = $script:MetadataDeserializers.Clone() - -Export-ModuleMember -Function *-* -Alias * \ No newline at end of file diff --git a/Source/Metadata/Header/00. param.ps1 b/Source/Metadata/Header/00. param.ps1 deleted file mode 100644 index ab7970c..0000000 --- a/Source/Metadata/Header/00. param.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -param( - $Converters = @{} -) - -$ModuleManifestExtension = ".psd1" \ No newline at end of file diff --git a/Source/Metadata/Header/01. IMetadataSerializable.ps1 b/Source/Metadata/Header/01. IMetadataSerializable.ps1 deleted file mode 100644 index d32c3f9..0000000 --- a/Source/Metadata/Header/01. IMetadataSerializable.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -Add-Type -TypeDefinition @' -public interface IPsMetadataSerializable { - string ToPsMetadata(); - void FromPsMetadata(string Metadata); -} -'@ \ No newline at end of file diff --git a/Source/Metadata/Metadata.psd1 b/Source/Metadata/Metadata.psd1 deleted file mode 100644 index c6af2b6..0000000 --- a/Source/Metadata/Metadata.psd1 +++ /dev/null @@ -1,29 +0,0 @@ -@{ - -# Script module or binary module file associated with this manifest. -ModuleToProcess = 'Metadata.psm1' - -# Version number of this module. -ModuleVersion = '1.3.1' - -# ID used to uniquely identify this module -GUID = 'c7505d40-646d-46b5-a440-8a81791c5d23' - -# 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-2018 by Joel Bennett, all rights reserved.' - -# Description of the functionality provided by this module -Description = 'A module for PowerShell data serialization' - -# This doesn't make it into the build output, so it's irrelevant -FunctionsToExport = '*-*' -AliasesToExport = '*' -PrivateData = @{ PSData = @{ Prerelease = "" } } - -} diff --git a/Source/Metadata/Private/FindHashKeyValue.ps1 b/Source/Metadata/Private/FindHashKeyValue.ps1 deleted file mode 100644 index 5356d4e..0000000 --- a/Source/Metadata/Private/FindHashKeyValue.ps1 +++ /dev/null @@ -1,25 +0,0 @@ -function FindHashKeyValue { - [CmdletBinding()] - param( - $SearchPath, - $Ast, - [string[]] - $CurrentPath = @() - ) - # Write-Debug "FindHashKeyValue: $SearchPath -eq $($CurrentPath -Join '.')" - if ($SearchPath -eq ($CurrentPath -Join '.') -or $SearchPath -eq $CurrentPath[-1]) { - return $Ast | - Add-Member NoteProperty HashKeyPath ($CurrentPath -join '.') -PassThru -Force | - Add-Member NoteProperty HashKeyName ($CurrentPath[-1]) -PassThru -Force - } - - if ($Ast.PipelineElements.Expression -is [System.Management.Automation.Language.HashtableAst] ) { - $KeyValue = $Ast.PipelineElements.Expression - foreach ($KV in $KeyValue.KeyValuePairs) { - $result = FindHashKeyValue $SearchPath -Ast $KV.Item2 -CurrentPath ($CurrentPath + $KV.Item1.Value) - if ($null -ne $result) { - $result - } - } - } -} diff --git a/Source/Metadata/Private/ThrowError.ps1 b/Source/Metadata/Private/ThrowError.ps1 deleted file mode 100644 index eec5e1d..0000000 --- a/Source/Metadata/Private/ThrowError.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -# Utility to throw an errorrecord -function ThrowError { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidOverwritingBuiltInCmdlets", "")] - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.Management.Automation.PSCmdlet] - $Cmdlet = $((Get-Variable -Scope 1 PSCmdlet).Value), - - [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [Parameter(ParameterSetName = "NewException")] - [ValidateNotNullOrEmpty()] - [System.Exception] - $Exception, - - [Parameter(ParameterSetName = "NewException", Position = 2)] - [ValidateNotNullOrEmpty()] - [System.String] - $ExceptionType = "System.Management.Automation.RuntimeException", - - [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 3)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter(Mandatory = $false)] - [System.Object] - $TargetObject, - - [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 10)] - [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 10)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 11)] - [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 11)] - [ValidateNotNull()] - [System.Management.Automation.ErrorCategory] - $Category, - - [Parameter(Mandatory = $true, ParameterSetName = "Rethrow", Position = 1)] - [System.Management.Automation.ErrorRecord]$ErrorRecord - ) - process { - if (!$ErrorRecord) { - if ($PSCmdlet.ParameterSetName -eq "NewException") { - if ($Exception) { - $Exception = New-Object $ExceptionType $Message, $Exception - } else { - $Exception = New-Object $ExceptionType $Message - } - } - $errorRecord = New-Object System.Management.Automation.ErrorRecord $Exception, $ErrorId, $Category, $TargetObject - } - $Cmdlet.ThrowTerminatingError($errorRecord) - } -} diff --git a/Source/Metadata/Private/WriteError.ps1 b/Source/Metadata/Private/WriteError.ps1 deleted file mode 100644 index 9fc3e02..0000000 --- a/Source/Metadata/Private/WriteError.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -# Utility to throw an errorrecord -function WriteError { - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.Management.Automation.PSCmdlet] - $Cmdlet = $((Get-Variable -Scope 1 PSCmdlet).Value), - - [Parameter(Mandatory = $true, ParameterSetName = "ExistingException", Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] - [Parameter(ParameterSetName = "NewException")] - [ValidateNotNullOrEmpty()] - [System.Exception] - $Exception, - - [Parameter(ParameterSetName = "NewException", Position = 2)] - [ValidateNotNullOrEmpty()] - [System.String] - $ExceptionType = "System.Management.Automation.RuntimeException", - - [Parameter(Mandatory = $true, ParameterSetName = "NewException", Position = 3)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter(Mandatory = $false)] - [System.Object] - $TargetObject, - - [Parameter(Mandatory = $true, Position = 10)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [Parameter(Mandatory = $true, Position = 11)] - [ValidateNotNull()] - [System.Management.Automation.ErrorCategory] - $Category, - - [Parameter(Mandatory = $true, ParameterSetName = "Rethrow", Position = 1)] - [System.Management.Automation.ErrorRecord]$ErrorRecord - ) - process { - if (!$ErrorRecord) { - if ($PSCmdlet.ParameterSetName -eq "NewException") { - if ($Exception) { - $Exception = New-Object $ExceptionType $Message, $Exception - } else { - $Exception = New-Object $ExceptionType $Message - } - } - $errorRecord = New-Object System.Management.Automation.ErrorRecord $Exception, $ErrorId, $Category, $TargetObject - } - $Cmdlet.WriteError($errorRecord) - } -} diff --git a/Source/Metadata/Public/Add-MetadataConverter.ps1 b/Source/Metadata/Public/Add-MetadataConverter.ps1 deleted file mode 100644 index 8d3be87..0000000 --- a/Source/Metadata/Public/Add-MetadataConverter.ps1 +++ /dev/null @@ -1,77 +0,0 @@ -function Add-MetadataConverter { - <# - .Synopsis - Add a converter functions for serialization and deserialization to metadata - .Description - Add-MetadataConverter allows you to map: - * a type to a scriptblock which can serialize that type to metadata (psd1) - * define a name and scriptblock as a function which will be whitelisted in metadata (for ConvertFrom-Metadata and Import-Metadata) - - The idea is to give you a way to extend the serialization capabilities if you really need to. - .Example - Add-MetadataCOnverter @{ [bool] = { if($_) { '$True' } else { '$False' } } } - - Shows a simple example of mapping bool to a scriptblock that serializes it in a way that's inherently parseable by PowerShell. This exact converter is already built-in to the Metadata module, so you don't need to add it. - - .Example - Add-MetadataConverter @{ - [Uri] = { "Uri '$_' " } - "Uri" = { - param([string]$Value) - [Uri]$Value - } - } - - Shows how to map a function for serializing Uri objects as strings with a Uri function that just casts them. Normally you wouldn't need to do that for Uri, since they output strings natively, and it's perfectly logical to store Uris as strings and only cast them when you really need to. - - .Example - Add-MetadataConverter @{ - [DateTimeOffset] = { "DateTimeOffset {0} {1}" -f $_.Ticks, $_.Offset } - "DateTimeOffset" = {param($ticks,$offset) [DateTimeOffset]::new( $ticks, $offset )} - } - - Shows how to change the DateTimeOffset serialization. - - By default, DateTimeOffset values are (de)serialized using the 'o' RoundTrips formatting - e.g.: [DateTimeOffset]::Now.ToString('o') - - #> - [CmdletBinding()] - param( - # A hashtable of types to serializer scriptblocks, or function names to scriptblock definitions - [Parameter(Mandatory = $True)] - [hashtable]$Converters - ) - - if ($Converters.Count) { - switch ($Converters.Keys.GetEnumerator()) { - {$Converters[$_] -isnot [ScriptBlock]} { - WriteError -ExceptionType System.ArgumentExceptionn ` - -Message "Ignoring $_ converter, the value must be ScriptBlock!" ` - -ErrorId "NotAScriptBlock,Metadata\Add-MetadataConverter" ` - -Category "InvalidArgument" - continue - } - - {$_ -is [String]} { - # Write-Debug "Storing deserialization function: $_" - Set-Content "function:script:$_" $Converters[$_] - # We need to store the function in MetadataDeserializers - $script:MetadataDeserializers[$_] = $Converters[$_] - continue - } - - {$_ -is [Type]} { - # Write-Debug "Adding serializer for $($_.FullName)" - $script:MetadataSerializers[$_] = $Converters[$_] - continue - } - default { - WriteError -ExceptionType System.ArgumentExceptionn ` - -Message "Unsupported key type in Converters: $_ is $($_.GetType())" ` - -ErrorId "InvalidKeyType,Metadata\Add-MetadataConverter" ` - -Category "InvalidArgument" - } - } - } -} diff --git a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 b/Source/Metadata/Public/ConvertFrom-Metadata.ps1 deleted file mode 100644 index 2aa177f..0000000 --- a/Source/Metadata/Public/ConvertFrom-Metadata.ps1 +++ /dev/null @@ -1,222 +0,0 @@ -function ConvertFrom-Metadata { - <# - .Synopsis - Deserializes objects from PowerShell Data language (PSD1) - .Description - Converts psd1 notation to actual objects, and supports passing in additional converters - in addition to using the built-in registered converters (see Add-MetadataConverter). - - NOTE: Any Converters that are passed in are temporarily added as though passed Add-MetadataConverter - .Example - ConvertFrom-Metadata 'PSObject @{ Name = PSObject @{ First = "Joel"; Last = "Bennett" }; Id = 1; }' - - Id Name - -- ---- - 1 @{Last=Bennett; First=Joel} - - Convert the example string into a real PSObject using the built-in object serializer. - .Example - $data = ConvertFrom-Metadata .\Configuration.psd1 -Ordered - - Convert a module manifest into a hashtable of properties for introspection, preserving the order in the file - .Example - ConvertFrom-Metadata ("DateTimeOffset 635968680686066846 -05:00:00") -Converters @{ - "DateTimeOffset" = { - param($ticks,$offset) - [DateTimeOffset]::new( $ticks, $offset ) - } - } - - Shows how to temporarily add a "ValidCommand" called "DateTimeOffset" to support extra data types in the metadata. - - See also the third example on ConvertTo-Metadata and Add-MetadataConverter - #> - [CmdletBinding()] - param( - # The metadata text (or a path to a metadata file) - [Parameter(ValueFromPipelineByPropertyName = "True", Position = 0)] - [Alias("PSPath")] - $InputObject, - - # A hashtable of MetadataConverters (same as with Add-MetadataConverter) - [Hashtable]$Converters = @{}, - - # The PSScriptRoot which the metadata should be evaluated from. - # You do not normally need to pass this, and it has no effect unless - # you're referencing $ScriptRoot in your metadata - $ScriptRoot = "$PSScriptRoot", - - # 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 metadata - # BEWARE: This exposes the value of these variables in your context to the caller - # You ware reponsible to only allow variables which you know are safe to share - [String[]]$AllowedVariables, - - # You should not pass this. - # The PSVariable parameter is for preserving variable scope within the Metadata commands - [System.Management.Automation.PSVariableIntrinsics]$PSVariable - ) - begin { - $OriginalMetadataSerializers = $Script:MetadataSerializers.Clone() - $OriginalMetadataDeserializers = $Script:MetadataDeserializers.Clone() - Add-MetadataConverter $Converters - [string[]]$ValidCommands = @( - "ConvertFrom-StringData", "Join-Path", "Split-Path", "ConvertTo-SecureString" - ) + @($MetadataDeserializers.Keys) - [string[]]$ValidVariables = $AllowedVariables + @( - "PSScriptRoot", "ScriptRoot", "PoshCodeModuleRoot", "PSCulture", "PSUICulture", "True", "False", "Null") - - if (!$PSVariable) { - $PSVariable = $PSCmdlet.SessionState.PSVariable - } - } - end { - $Script:MetadataSerializers = $OriginalMetadataSerializers.Clone() - $Script:MetadataDeserializers = $OriginalMetadataDeserializers.Clone() - } - process { - $ErrorActionPreference = "Stop" - $Tokens = $Null; $ParseErrors = $Null - - if (Test-PSVersion -lt "3.0") { - # Write-Debug "ConvertFrom-Metadata: Using Import-LocalizedData to support PowerShell $($PSVersionTable.PSVersion)" - # Write-Debug "ConvertFrom-Metadata: $InputObject" - if (!(Test-Path $InputObject -ErrorAction SilentlyContinue)) { - $Path = [IO.path]::ChangeExtension([IO.Path]::GetTempFileName(), $ModuleManifestExtension) - Set-Content -Encoding UTF8 -Path $Path $InputObject - $InputObject = $Path - } elseif (!"$InputObject".EndsWith($ModuleManifestExtension)) { - $Path = [IO.path]::ChangeExtension([IO.Path]::GetTempFileName(), $ModuleManifestExtension) - Copy-Item "$InputObject" "$Path" - $InputObject = $Path - } - $Result = $null - Import-LocalizedData -BindingVariable Result -BaseDirectory (Split-Path $InputObject) -FileName (Split-Path $InputObject -Leaf) -SupportedCommand $ValidCommands - return $Result - } - - if (Test-Path $InputObject -ErrorAction SilentlyContinue) { - # Write-Debug "ConvertFrom-Metadata: Using ParseInput to support PowerShell $($PSVersionTable.PSVersion)" - # ParseFile on PS5 (and older) doesn't handle utf8 encoding properly (treats it as ASCII) - # Sometimes, that causes an avoidable error. So I'm avoiding it, if I can: - $Path = Convert-Path $InputObject - if (!$PSBoundParameters.ContainsKey('ScriptRoot')) { - $ScriptRoot = Split-Path $Path - } - $Content = (Get-Content -Path $InputObject -Encoding UTF8) - # Remove SIGnature blocks, PowerShell doesn't parse them in .psd1 and chokes on them here. - $Content = $Content -join "`n" -replace "# SIG # Begin signature block(?s:.*)" - try { - # But older versions of PowerShell, this will throw a MethodException because the overload is missing - $AST = [System.Management.Automation.Language.Parser]::ParseInput($Content, $Path, [ref]$Tokens, [ref]$ParseErrors) - } catch [System.Management.Automation.MethodException] { - # Write-Debug "ConvertFrom-Metadata: Using ParseFile as a backup for PowerShell $($PSVersionTable.PSVersion)" - $AST = [System.Management.Automation.Language.Parser]::ParseFile( $Path, [ref]$Tokens, [ref]$ParseErrors) - - # If we got parse errors on older versions of PowerShell, test to see if the error is just encoding - if ($null -ne $ParseErrors -and $ParseErrors.Count -gt 0) { - $StillErrors = $null - $AST = [System.Management.Automation.Language.Parser]::ParseInput($Content, [ref]$Tokens, [ref]$StillErrors) - # If we didn't get errors, clear the errors - # Otherwise, we want to use the original errors with the path in them - if ($null -eq $StillErrors -or $StillErrors.Count -eq 0) { - $ParseErrors = $StillErrors - } - } - } - } else { - # Write-Debug "ConvertFrom-Metadata: Using ParseInput with loose metadata: $InputObject" - if (!$PSBoundParameters.ContainsKey('ScriptRoot')) { - $ScriptRoot = $PoshCodeModuleRoot - } - - $OFS = "`n" - # Remove SIGnature blocks, PowerShell doesn't parse them in .psd1 and chokes on them here. - $InputObject = "$InputObject" -replace "# SIG # Begin signature block(?s:.*)" - $AST = [System.Management.Automation.Language.Parser]::ParseInput($InputObject, [ref]$Tokens, [ref]$ParseErrors) - } - - if ($null -ne $ParseErrors -and $ParseErrors.Count -gt 0) { - ThrowError -Exception (New-Object System.Management.Automation.ParseException (, [System.Management.Automation.Language.ParseError[]]$ParseErrors)) -ErrorId "Metadata Error" -Category "ParserError" -TargetObject $InputObject - } - - # Get the variables or subexpressions from strings which have them ("StringExpandable" vs "String") ... - $Tokens += $Tokens | Where-Object { "StringExpandable" -eq $_.Kind } | Select-Object -ExpandProperty NestedTokens - - # Write-Debug "ConvertFrom-Metadata: Searching $($Tokens.Count) variables for: $($ValidVariables -join ', '))" - # Work around PowerShell rules about magic variables - # Change all the "ValidVariables" to use names like __ModuleBuilder__OriginalName__ - # Later, we'll try to make sure these are all set! - if (($UsedVariables = $Tokens | Where-Object { ("Variable" -eq $_.Kind) -and ($_.Name -in $ValidVariables) })) { - # Write-Debug "ConvertFrom-Metadata: Replacing $($UsedVariables.Name -join ', ')" - if (($scriptroots = @( $UsedVariables | ForEach-Object { $_.Extent | Add-Member NoteProperty Name $_.Name -PassThru } ))) { - $ScriptContent = $Ast.ToString() - # Write-Debug "ConvertFrom-Metadata: Replacing $($UsedVariables.Count) variables in metadata: $ScriptContent" - for ($r = $scriptroots.count - 1; $r -ge 0; $r--) { - $VariableExtent = $scriptroots[$r] - $VariableName = if ($VariableExtent.Name -eq "PSScriptRoot") { - '${__ModuleBuilder__ScriptRoot__}' - } else { - '${__ModuleBuilder__' + $VariableExtent.Name + '__}' - } - $ScriptContent = $ScriptContent.Remove($VariableExtent.StartOffset, ($VariableExtent.EndOffset - $VariableExtent.StartOffset) - ).Insert($VariableExtent.StartOffset, $VariableName) - } - } - } - - # Write-Debug "ConvertFrom-Metadata: Replaced $($UsedVariables.Name -join ' and ') in metadata: $ScriptContent" - $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) - - $Script = $AST.GetScriptBlock() - try { - [string[]]$PrivateVariables = $ValidVariables -replace "^.*$", '__ModuleBuilder__$0__' - # Write-Debug "ConvertFrom-Metadata: Validating metadata: $Script against $PrivateVariables" - $Script.CheckRestrictedLanguage( $ValidCommands, $PrivateVariables, $true ) - } catch { - ThrowError -Exception $_.Exception.InnerException -ErrorId "Metadata Error" -Category "InvalidData" -TargetObject $Script - } - - # Set the __ModuleBuilder__ValidVariables__ in our scope but not for constant variables: - $ReplacementVariables = $ValidVariables | Where-Object { $_ -notin "PSCulture", "PSUICulture", "True", "False", "Null" } - foreach ($name in $ReplacementVariables) { - if (!($Value = $PSVariable.GetValue($Name))) { - $Value = "`${$Name}" - } - # Write-Debug "ConvertFrom-Metadata: Setting __ModuleBuilder__${Name}__ = $Value" - Set-Variable "__ModuleBuilder__${Name}__" $Value - } - - if ($Ordered -and (Test-PSVersion -gt "3.0")) { - # Write-Debug "ConvertFrom-Metadata: Supporting [Ordered] on PowerShell $($PSVersionTable.PSVersion)" - # Make all the hashtables ordered, so that the output objects make more sense to humans... - if ($Tokens | Where-Object { "AtCurly" -eq $_.Kind }) { - $ScriptContent = $AST.ToString() - $Hashtables = $AST.FindAll( {$args[0] -is [System.Management.Automation.Language.HashtableAst] -and ("ordered" -ne $args[0].Parent.Type.TypeName)}, $Recurse) - $Hashtables = $Hashtables | ForEach-Object { - New-Object PSObject -Property @{Type = "([ordered]"; Position = $_.Extent.StartOffset} - New-Object PSObject -Property @{Type = ")"; Position = $_.Extent.EndOffset} - } | Sort-Object Position -Descending - foreach ($point in $Hashtables) { - $ScriptContent = $ScriptContent.Insert($point.Position, $point.Type) - } - - $AST = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$Tokens, [ref]$ParseErrors) - $Script = $AST.GetScriptBlock() - } - } - # Write-Debug "ConvertFrom-Metadata: Metadata: $Script" - # Write-Debug "ConvertFrom-Metadata: Switching to RestrictedLanguage mode" - $Mode, $ExecutionContext.SessionState.LanguageMode = $ExecutionContext.SessionState.LanguageMode, "RestrictedLanguage" - - try { - $Script.InvokeReturnAsIs(@()) - } finally { - $ExecutionContext.SessionState.LanguageMode = $Mode - # Write-Debug "ConvertFrom-Metadata: Switching to $Mode mode" - } - } -} diff --git a/Source/Metadata/Public/ConvertTo-Metadata.ps1 b/Source/Metadata/Public/ConvertTo-Metadata.ps1 deleted file mode 100644 index b7bd90a..0000000 --- a/Source/Metadata/Public/ConvertTo-Metadata.ps1 +++ /dev/null @@ -1,118 +0,0 @@ -function ConvertTo-Metadata { - #.Synopsis - # Serializes objects to PowerShell Data language (PSD1) - #.Description - # Converts objects to a texual representation that is valid in PSD1, - # using the built-in registered converters (see Add-MetadataConverter). - # - # NOTE: Any Converters that are passed in are temporarily added as though passed Add-MetadataConverter - #.Example - # $Name = @{ First = "Joel"; Last = "Bennett" } - # @{ Name = $Name; Id = 1; } | ConvertTo-Metadata - # - # @{ - # Id = 1 - # Name = @{ - # Last = 'Bennett' - # First = 'Joel' - # } - # } - # - # Convert input objects into a formatted string suitable for storing in a psd1 file. - #.Example - # Get-ChildItem -File | Select-Object FullName, *Utc, Length | ConvertTo-Metadata - # - # Convert complex custom types to dynamic PSObjects using Select-Object. - # - # ConvertTo-Metadata understands PSObjects automatically, so this allows us to proceed - # without a custom serializer for File objects, but the serialized data - # will not be a FileInfo or a DirectoryInfo, just a custom PSObject - #.Example - # ConvertTo-Metadata ([DateTimeOffset]::Now) -Converters @{ - # [DateTimeOffset] = { "DateTimeOffset {0} {1}" -f $_.Ticks, $_.Offset } - # } - # - # Shows how to temporarily add a MetadataConverter to convert a specific type while serializing the current DateTimeOffset. - # Note that this serialization would require a "DateTimeOffset" function to exist in order to deserialize properly. - # - # See also the third example on ConvertFrom-Metadata and Add-MetadataConverter. - [OutputType([string])] - [CmdletBinding()] - param( - # The object to convert to metadata - [Parameter(ValueFromPipeline = $True)] - $InputObject, - - # Serialize objects as hashtables - [switch]$AsHashtable, - - # Additional converters - [Hashtable]$Converters = @{} - ) - begin { - $t = " " - $Script:OriginalMetadataSerializers = $Script:MetadataSerializers.Clone() - $Script:OriginalMetadataDeserializers = $Script:MetadataDeserializers.Clone() - Add-MetadataConverter $Converters - } - end { - $Script:MetadataSerializers = $Script:OriginalMetadataSerializers.Clone() - $Script:MetadataDeserializers = $Script:OriginalMetadataDeserializers.Clone() - } - process { - if ($Null -eq $InputObject) { - '""' - } elseif ($InputObject -is [IPsMetadataSerializable] -or ($InputObject.ToPsMetadata -as [Func[String]] -and $InputObject.FromPsMetasta -as [Action[String]])) { - "(FromPsMetadata {0} @'`n{1}`n'@)" -f $InputObject.GetType().FullName, $InputObject.ToMetadata() - } elseif ( $InputObject -is [Int16] -or - $InputObject -is [Int32] -or - $InputObject -is [Int64] -or - $InputObject -is [Double] -or - $InputObject -is [Decimal] -or - $InputObject -is [Byte] ) { - "$InputObject" - } elseif ($InputObject -is [String]) { - "'{0}'" -f $InputObject.ToString().Replace("'", "''") - } elseif ($InputObject -is [Collections.IDictionary]) { - "@{{`n$t{0}`n}}" -f ($( - ForEach ($key in @($InputObject.Keys)) { - if ("$key" -match '^([A-Za-z_]\w*|-?\d+\.?\d*)$') { - "$key = " + (ConvertTo-Metadata $InputObject[$key] -AsHashtable:$AsHashtable) - } else { - "'$key' = " + (ConvertTo-Metadata $InputObject[$key] -AsHashtable:$AsHashtable) - } - }) -split "`n" -join "`n$t") - } elseif ($InputObject -is [System.Collections.IEnumerable]) { - "@($($(ForEach($item in @($InputObject)) { $item | ConvertTo-Metadata -AsHashtable:$AsHashtable}) -join ","))" - } elseif ($InputObject.GetType().FullName -eq 'System.Management.Automation.PSCustomObject') { - # NOTE: we can't put [ordered] here because we need support for PS v2, but it's ok, because we put it in at parse-time - $(if ($AsHashtable) { - "@{{`n$t{0}`n}}" - } else { - "(PSObject @{{`n$t{0}`n}} -TypeName '$($InputObject.PSTypeNames -join "','")')" - }) -f ($( - ForEach ($key in $InputObject | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name) { - if ("$key" -match '^([A-Za-z_]\w*|-?\d+\.?\d*)$') { - "$key = " + (ConvertTo-Metadata $InputObject[$key] -AsHashtable:$AsHashtable) - } else { - "'$key' = " + (ConvertTo-Metadata $InputObject[$key] -AsHashtable:$AsHashtable) - } - } - ) -split "`n" -join "`n$t") - } elseif ($MetadataSerializers.ContainsKey($InputObject.GetType())) { - $Str = ForEach-Object $MetadataSerializers.($InputObject.GetType()) -InputObject $InputObject - - [bool]$IsCommand = & { - $ErrorActionPreference = "Stop" - $Tokens = $Null; $ParseErrors = $Null - $AST = [System.Management.Automation.Language.Parser]::ParseInput( $Str, [ref]$Tokens, [ref]$ParseErrors) - $Null -ne $Ast.Find( {$args[0] -is [System.Management.Automation.Language.CommandAst]}, $false) - } - - if ($IsCommand) { "($Str)" } else { $Str } - } else { - Write-Warning "$($InputObject.GetType().FullName) is not serializable. Serializing as string" - "'{0}'" -f $InputObject.ToString().Replace("'", "`'`'") - } - } -} diff --git a/Source/Metadata/Public/Export-Metadata.ps1 b/Source/Metadata/Public/Export-Metadata.ps1 deleted file mode 100644 index 60a4023..0000000 --- a/Source/Metadata/Public/Export-Metadata.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -function Export-Metadata { - <# - .Synopsis - Creates a metadata file from a simple object - .Description - Serves as a wrapper for ConvertTo-Metadata to explicitly support exporting to files - - Note that exportable data is limited by the rules of data sections (see about_Data_Sections) and the available MetadataSerializers (see Add-MetadataConverter) - - The only things inherently importable in PowerShell metadata files are Strings, Booleans, and Numbers ... and Arrays or Hashtables where the values (and keys) are all strings, booleans, or numbers. - - Note: this function and the matching Import-Metadata are extensible, and have included support for PSCustomObject, Guid, Version, etc. - .Example - $Configuration | Export-Metadata .\Configuration.psd1 - - Export a configuration object (or hashtable) to the default Configuration.psd1 file for a module - The Configuration module uses Configuration.psd1 as it's default config file. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "")] # Because PSSCriptAnalyzer team refuses to listen to reason. See bugs: #194 #283 #521 #608 - [CmdletBinding(SupportsShouldProcess)] - param( - # Specifies the path to the PSD1 output file. - [Parameter(Mandatory = $true, Position = 0)] - $Path, - - # comments to place on the top of the file (to explain settings or whatever for people who might edit it by hand) - [string[]]$CommentHeader, - - # 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)] - $InputObject, - - # Serialize objects as hashtables - [switch]$AsHashtable, - - [Hashtable]$Converters = @{}, - - # If set, output the nuspec file - [Switch]$Passthru - ) - begin { - $data = @() - } - process { - $data += @($InputObject) - } - end { - # Avoid arrays when they're not needed: - if ($data.Count -eq 1) { - $data = $data[0] - } - Set-Content -Encoding UTF8 -Path $Path -Value ((@($CommentHeader) + @(ConvertTo-Metadata -InputObject $data -Converters $Converters -AsHashtable:$AsHashtable)) -Join "`n") - if ($Passthru) { - Get-Item $Path - } - } -} diff --git a/Source/Metadata/Public/Get-Metadata.ps1 b/Source/Metadata/Public/Get-Metadata.ps1 deleted file mode 100644 index 45bfb9a..0000000 --- a/Source/Metadata/Public/Get-Metadata.ps1 +++ /dev/null @@ -1,92 +0,0 @@ -function Get-Metadata { - #.Synopsis - # Reads a specific value from a PowerShell metadata file (e.g. a module manifest) - #.Description - # By default Get-Metadata gets the ModuleVersion, but it can read any key in the metadata file - #.Example - # Get-Metadata .\Configuration.psd1 - # - # Returns the module version number (as a string) - #.Example - # Get-Metadata .\Configuration.psd1 ReleaseNotes - # - # Returns the release notes! - [Alias("Get-ManifestValue")] - [CmdletBinding()] - param( - # The path to the module manifest file - [Parameter(ValueFromPipelineByPropertyName = "True", Position = 0)] - [Alias("PSPath")] - [ValidateScript( { if ([IO.Path]::GetExtension($_) -ne ".psd1") { - throw "Path must point to a .psd1 file" - } $true })] - [string]$Path, - - # The property (or dotted property path) to be read from the manifest. - # Get-Metadata searches the Manifest root properties, and also the nested hashtable properties. - [Parameter(ParameterSetName = "Overwrite", Position = 1)] - [string]$PropertyName = 'ModuleVersion', - - [switch]$Passthru - ) - process { - $ErrorActionPreference = "Stop" - - if (!(Test-Path $Path)) { - WriteError -ExceptionType System.Management.Automation.ItemNotFoundException ` - -Message "Can't find file $Path" ` - -ErrorId "PathNotFound,Metadata\Import-Metadata" ` - -Category "ObjectNotFound" - return - } - $Path = Convert-Path $Path - - $Tokens = $Null; $ParseErrors = $Null - $AST = [System.Management.Automation.Language.Parser]::ParseFile( $Path, [ref]$Tokens, [ref]$ParseErrors ) - - $KeyValue = $Ast.EndBlock.Statements - $KeyValue = @(FindHashKeyValue $PropertyName $KeyValue) - if ($KeyValue.Count -eq 0) { - WriteError -ExceptionType System.Management.Automation.ItemNotFoundException ` - -Message "Can't find '$PropertyName' in $Path" ` - -ErrorId "PropertyNotFound,Metadata\Get-Metadata" ` - -Category "ObjectNotFound" - return - } - if ($KeyValue.Count -gt 1) { - $SingleKey = @($KeyValue | Where-Object { $_.HashKeyPath -eq $PropertyName }) - - if ($SingleKey.Count -gt 1) { - WriteError -ExceptionType System.Reflection.AmbiguousMatchException ` - -Message ("Found more than one '$PropertyName' in $Path. Please specify a dotted path instead. Matching paths include: '{0}'" -f ($KeyValue.HashKeyPath -join "', '")) ` - -ErrorId "AmbiguousMatch,Metadata\Get-Metadata" ` - -Category "InvalidArgument" - return - } else { - $KeyValue = $SingleKey - } - } - $KeyValue = $KeyValue[0] - - if ($Passthru) { - $KeyValue - } else { - # # Write-Debug "Start $($KeyValue.Extent.StartLineNumber) : $($KeyValue.Extent.StartColumnNumber) (char $($KeyValue.Extent.StartOffset))" - # # Write-Debug "End $($KeyValue.Extent.EndLineNumber) : $($KeyValue.Extent.EndColumnNumber) (char $($KeyValue.Extent.EndOffset))" - - # In PowerShell 5+ we can just use: - if ($KeyValue.SafeGetValue) { - $KeyValue.SafeGetValue() - } else { - # Otherwise, this workd for simple values: - $Expression = $KeyValue.GetPureExpression() - if ($Expression.Value) { - $Expression.Value - } else { - # For complex (arrays, hashtables) we parse it ourselves - ConvertFrom-Metadata $KeyValue - } - } - } - } -} diff --git a/Source/Metadata/Public/Import-Metadata.ps1 b/Source/Metadata/Public/Import-Metadata.ps1 deleted file mode 100644 index 179d143..0000000 --- a/Source/Metadata/Public/Import-Metadata.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -function Import-Metadata { - <# - .Synopsis - Creates a data object from the items in a Metadata file (e.g. a .psd1) - .Description - Serves as a wrapper for ConvertFrom-Metadata to explicitly support importing from files - .Example - $data = Import-Metadata .\Configuration.psd1 -Ordered - - Convert a module manifest into a hashtable of properties for introspection, preserving the order in the file - #> - [CmdletBinding()] - param( - # The path to the metadata (.psd1) file to import - [Parameter(ValueFromPipeline = $true, Mandatory = $true, ValueFromPipelineByPropertyName = $true)] - [Alias("PSPath", "Content")] - [string]$Path, - - # A hashtable of MetadataConverters (same as with Add-MetadataConverter) - [Hashtable]$Converters = @{}, - - # 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 metadata - # BEWARE: This exposes the value of these variables in the calling context to the metadata file - # You are reponsible to only allow variables which you know are safe to share - [String[]]$AllowedVariables, - - # You should not pass this. - # The PSVariable parameter is for preserving variable scope within the Metadata commands - [System.Management.Automation.PSVariableIntrinsics]$PSVariable - ) - process { - if (!$PSVariable) { - $PSVariable = $PSCmdlet.SessionState.PSVariable - } - if (Test-Path $Path) { - # Write-Debug "Importing Metadata file from `$Path: $Path" - if (!(Test-Path $Path -PathType Leaf)) { - $Path = Join-Path $Path ((Split-Path $Path -Leaf) + $ModuleManifestExtension) - } - } - if (!(Test-Path $Path)) { - WriteError -ExceptionType System.Management.Automation.ItemNotFoundException ` - -Message "Can't find file $Path" ` - -ErrorId "PathNotFound,Metadata\Import-Metadata" ` - -Category "ObjectNotFound" - return - } - try { - ConvertFrom-Metadata -InputObject $Path -Converters $Converters -Ordered:$Ordered -AllowedVariables $AllowedVariables -PSVariable $PSVariable - } catch { - ThrowError $_ - } - } -} diff --git a/Source/Metadata/Public/Test-PSVersion.ps1 b/Source/Metadata/Public/Test-PSVersion.ps1 deleted file mode 100644 index 18b6025..0000000 --- a/Source/Metadata/Public/Test-PSVersion.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -function Test-PSVersion { - <# - .Synopsis - Test the PowerShell Version - .Description - This function exists so I can do things differently on older versions of PowerShell. - But the reason I test in a function is that I can mock the Version to test the alternative code. - .Example - if(Test-PSVersion -ge 3.0) { - ls | where Length -gt 12mb - } else { - ls | Where { $_.Length -gt 12mb } - } - - This is just a trivial example to show the usage (you wouldn't really bother for a where-object call) - #> - [OutputType([bool])] - [CmdletBinding()] - param( - [Version]$Version = $PSVersionTable.PSVersion, - [Version]$lt, - [Version]$le, - [Version]$gt, - [Version]$ge, - [Version]$eq, - [Version]$ne - ) - - $all = @( - if ($lt) { $Version -lt $lt } - if ($gt) { $Version -gt $gt } - if ($le) { $Version -le $le } - if ($ge) { $Version -ge $ge } - if ($eq) { $Version -eq $eq } - if ($ne) { $Version -ne $ne } - ) - - $all -notcontains $false -} diff --git a/Source/Metadata/Public/Update-Metadata.ps1 b/Source/Metadata/Public/Update-Metadata.ps1 deleted file mode 100644 index 25f68c8..0000000 --- a/Source/Metadata/Public/Update-Metadata.ps1 +++ /dev/null @@ -1,126 +0,0 @@ -function Update-Metadata { - <# - .Synopsis - Update a single value in a PowerShell metadata file - .Description - By default Update-Metadata increments "ModuleVersion" - because my primary use of it is during builds, - but you can pass the PropertyName and Value for any key in a module Manifest, its PrivateData, or the PSData in PrivateData. - - NOTE: This will not currently create new keys, or uncomment keys. - .Example - Update-Metadata .\Configuration.psd1 - - Increments the Build part of the ModuleVersion in the Configuration.psd1 file - .Example - Update-Metadata .\Configuration.psd1 -Increment Major - - Increments the Major version part of the ModuleVersion in the Configuration.psd1 file - .Example - Update-Metadata .\Configuration.psd1 -Value '0.4' - - Sets the ModuleVersion in the Configuration.psd1 file to 0.4 - .Example - Update-Metadata .\Configuration.psd1 -Property ReleaseNotes -Value 'Add the awesome Update-Metadata function!' - - Sets the PrivateData.PSData.ReleaseNotes value in the Configuration.psd1 file! - #> - [Alias("Update-Manifest")] - # Because PSSCriptAnalyzer team refuses to listen to reason. See bugs: #194 #283 #521 #608 - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "")] - [CmdletBinding(SupportsShouldProcess)] - param( - # The path to the module manifest file -- must be a .psd1 file - # As an easter egg, you can pass the CONTENT of a psd1 file instead, and the modified data will pass through - [Parameter(ValueFromPipelineByPropertyName = "True", Position = 0)] - [Alias("PSPath")] - [ValidateScript( { if ([IO.Path]::GetExtension($_) -ne ".psd1") { - throw "Path must point to a .psd1 file" - } $true })] - [string]$Path, - - # The property to be set in the manifest. It must already exist in the file (and not be commented out) - # This searches the Manifest root properties, then the properties PrivateData, then the PSData - [Parameter(ParameterSetName = "Overwrite")] - [string]$PropertyName = 'ModuleVersion', - - # A new value for the property - [Parameter(ParameterSetName = "Overwrite", Mandatory)] - $Value, - - # By default Update-Metadata increments ModuleVersion; this controls which part of the version number is incremented - [Parameter(ParameterSetName = "IncrementVersion")] - [ValidateSet("Major", "Minor", "Build", "Revision")] - [string]$Increment = "Build", - - # When set, and incrementing the ModuleVersion, output the new version number. - [Parameter(ParameterSetName = "IncrementVersion")] - [switch]$Passthru - ) - process { - $KeyValue = Get-Metadata $Path -PropertyName $PropertyName -Passthru - - if ($PSCmdlet.ParameterSetName -eq "IncrementVersion") { - $Version = [Version]$KeyValue.GetPureExpression().Value # SafeGetValue() - - $Version = switch ($Increment) { - "Major" { - [Version]::new($Version.Major + 1, 0) - } - "Minor" { - $Minor = if ($Version.Minor -le 0) { - 1 - } else { - $Version.Minor + 1 - } - [Version]::new($Version.Major, $Minor) - } - "Build" { - $Build = if ($Version.Build -le 0) { - 1 - } else { - $Version.Build + 1 - } - [Version]::new($Version.Major, $Version.Minor, $Build) - } - "Revision" { - $Build = if ($Version.Build -le 0) { - 0 - } else { - $Version.Build - } - $Revision = if ($Version.Revision -le 0) { - 1 - } else { - $Version.Revision + 1 - } - [Version]::new($Version.Major, $Version.Minor, $Build, $Revision) - } - } - - $Value = $Version - - if ($Passthru) { - $Value - } - } - - $Value = ConvertTo-Metadata $Value - - $Extent = $KeyValue.Extent - while ($KeyValue.parent) { - $KeyValue = $KeyValue.parent - } - - $ManifestContent = $KeyValue.Extent.Text.Remove( - $Extent.StartOffset, - ($Extent.EndOffset - $Extent.StartOffset) - ).Insert($Extent.StartOffset, $Value).Trim() - - if (Test-Path $Path) { - Set-Content -Encoding UTF8 -Path $Path -Value $ManifestContent - } else { - $ManifestContent - } - } -} \ No newline at end of file diff --git a/Source/Metadata/Public/Update-Object.ps1 b/Source/Metadata/Public/Update-Object.ps1 deleted file mode 100644 index 94b745e..0000000 --- a/Source/Metadata/Public/Update-Object.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -function Update-Object { - <# - .Synopsis - Recursively updates a hashtable or custom object with new values - .Description - Updates the InputObject with data from the update object, updating or adding values. - .Example - Update-Object -Input @{ - One = "Un" - Two = "Dos" - } -Update @{ - One = "Uno" - Three = "Tres" - } - - Updates the InputObject with the values in the UpdateObject, - will return the following object: - - @{ - One = "Uno" - Two = "Dos" - Three = "Tres" - } - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "")] # Because PSSCriptAnalyzer team refuses to listen to reason. See bugs: #194 #283 #521 #608 - [CmdletBinding(SupportsShouldProcess)] - param( - # The object (or hashtable) with properties (or keys) to overwrite the InputObject - [AllowNull()] - [Parameter(Position = 0, Mandatory = $true)] - $UpdateObject, - - # This base object (or hashtable) will be updated and overwritten by the UpdateObject - [Parameter(ValueFromPipeline = $true, Mandatory = $true)] - $InputObject, - - # A list of values which (if found on InputObject) should not be updated from UpdateObject - [Parameter()] - [string[]]$ImportantInputProperties - ) - process { - # Write-Debug "INPUT OBJECT:" - # Write-Debug (($InputObject | Out-String -Stream | ForEach-Object TrimEnd) -join "`n") - # Write-Debug "Update OBJECT:" - # Write-Debug (($UpdateObject | Out-String -Stream | ForEach-Object TrimEnd) -join "`n") - if ($Null -eq $InputObject) { - return - } - - # $InputObject -is [PSCustomObject] -or - if ($InputObject -is [System.Collections.IDictionary]) { - $OutputObject = $InputObject - } else { - # Create a PSCustomObject with all the properties - $OutputObject = [PSObject]$InputObject # | Select-Object * | % { } - } - - if (!$UpdateObject) { - $OutputObject - return - } - - if ($UpdateObject -is [System.Collections.IDictionary]) { - $Keys = $UpdateObject.Keys - } else { - $Keys = @($UpdateObject | - Get-Member -MemberType Properties | - Where-Object { $p1 -notcontains $_.Name } | - Select-Object -ExpandProperty Name) - } - - function TestKey { - [OutputType([bool])] - [CmdletBinding()] - param($InputObject, $Key) - [bool]$( - if ($InputObject -is [System.Collections.IDictionary]) { - $InputObject.ContainsKey($Key) - } else { - Get-Member -InputObject $InputObject -Name $Key - } - ) - } - - # # Write-Debug "Keys: $Keys" - foreach ($key in $Keys) { - if ($key -notin $ImportantInputProperties -or -not (TestKey -InputObject $InputObject -Key $Key) ) { - # recurse Dictionaries (hashtables) and PSObjects - if (($OutputObject.$Key -is [System.Collections.IDictionary] -or $OutputObject.$Key -is [PSObject]) -and - ($InputObject.$Key -is [System.Collections.IDictionary] -or $InputObject.$Key -is [PSObject])) { - $Value = Update-Object -InputObject $InputObject.$Key -UpdateObject $UpdateObject.$Key - } else { - $Value = $UpdateObject.$Key - } - - if ($OutputObject -is [System.Collections.IDictionary]) { - $OutputObject.$key = $Value - } else { - $OutputObject = Add-Member -InputObject $OutputObject -MemberType NoteProperty -Name $key -Value $Value -PassThru -Force - } - } - } - - $OutputObject - } -} diff --git a/Source/Metadata/build.psd1 b/Source/Metadata/build.psd1 deleted file mode 100644 index eeca891..0000000 --- a/Source/Metadata/build.psd1 +++ /dev/null @@ -1,6 +0,0 @@ -@{ - ModuleManifest = "Metadata.psd1" - SourceDirectories = @("Header", "Private", "Public") - Suffix = "Footer\InitialMetadataConverters.ps1" - VersionedOutputDirectory = $true -} \ No newline at end of file diff --git a/Source/Configuration/Private/InitializeStoragePaths.ps1 b/Source/Private/InitializeStoragePaths.ps1 similarity index 100% rename from Source/Configuration/Private/InitializeStoragePaths.ps1 rename to Source/Private/InitializeStoragePaths.ps1 diff --git a/Source/Configuration/Private/ParameterBinder.ps1 b/Source/Private/ParameterBinder.ps1 similarity index 100% rename from Source/Configuration/Private/ParameterBinder.ps1 rename to Source/Private/ParameterBinder.ps1 diff --git a/Source/Configuration/Public/Export-Configuration.ps1 b/Source/Public/Export-Configuration.ps1 similarity index 100% rename from Source/Configuration/Public/Export-Configuration.ps1 rename to Source/Public/Export-Configuration.ps1 diff --git a/Source/Configuration/Public/Get-ConfigurationPath.ps1 b/Source/Public/Get-ConfigurationPath.ps1 similarity index 100% rename from Source/Configuration/Public/Get-ConfigurationPath.ps1 rename to Source/Public/Get-ConfigurationPath.ps1 diff --git a/Source/Configuration/Public/Import-Configuration.ps1 b/Source/Public/Import-Configuration.ps1 similarity index 100% rename from Source/Configuration/Public/Import-Configuration.ps1 rename to Source/Public/Import-Configuration.ps1 diff --git a/Source/Configuration/Public/Import-ParameterConfiguration.ps1 b/Source/Public/Import-ParameterConfiguration.ps1 similarity index 97% rename from Source/Configuration/Public/Import-ParameterConfiguration.ps1 rename to Source/Public/Import-ParameterConfiguration.ps1 index ee32745..c7654c7 100644 --- a/Source/Configuration/Public/Import-ParameterConfiguration.ps1 +++ b/Source/Public/Import-ParameterConfiguration.ps1 @@ -1,199 +1,199 @@ -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 imagine 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)) +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 imagine 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)) } \ No newline at end of file diff --git a/Source/Configuration/build.psd1 b/Source/build.psd1 similarity index 100% rename from Source/Configuration/build.psd1 rename to Source/build.psd1 From 171e02995de64980cd1a57573002b3f4fcc22424 Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Fri, 2 Jul 2021 23:55:56 -0400 Subject: [PATCH 6/8] Reconfigure build (without Metadata) --- .github/workflows/build.yml | 6 -- Build.ps1 | 46 ++---------- Source/Configuration.psd1 | 16 ++--- Source/Header/param.ps1 | 7 +- Specs/ScriptAnalyzer.feature | 124 ++++++++++++++++---------------- Test.ps1 | 11 ++- Source/build.psd1 => build.psd1 | 4 +- 7 files changed, 81 insertions(+), 133 deletions(-) rename Source/build.psd1 => build.psd1 (58%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b85078..6b5905f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,5 @@ name: Build on push - on: [push] - jobs: build: runs-on: windows-latest @@ -19,10 +17,6 @@ jobs: with: version: ${{ steps.gitversion.outputs.LegacySemVerPadded }} destination: ${{github.workspace}}/output - - name: Combine Configuration - run: | - $OutputModule, $NestedModule = (ConvertFrom-Json -Input '${{ steps.build.outputs.moduleinfo }}').Where({$_.Name -eq "Configuration"}, "Split") - & "${{github.workspace}}/.github/workflows/Merge-Module.ps1" -OutputModulePath $OutputModule.Path -NestedModulePath $NestedModule.Path - name: Upload Build Output uses: actions/upload-artifact@v2 with: diff --git a/Build.ps1 b/Build.ps1 index f019f2c..94289bb 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -10,50 +10,14 @@ param( ) Push-Location $PSScriptRoot -StackName BuildTestStack -if (!$SemVer -and (Get-Command gitversion -ErrorAction Ignore)) { - $PSBoundParameters['SemVer'] = gitversion -showvariable nugetversion -} -if (!$PSBoundParameters.ContainsKey("OutputDirectory")) { - $PSBoundParameters["OutputDirectory"] = $PSScriptRoot +if (-not $Semver -and (Get-Command gitversion -ErrorAction Ignore)) { + if ($semver = gitversion -showvariable SemVer) { + $null = $PSBoundParameters.Add("SemVer", $SemVer) + } } try { - ## Build the actual module - $MetadataInfo = Build-Module -SourcePath .\Source\Metadata ` - -Target CleanBuild -Passthru ` - @PSBoundParameters - - $ConfigurationInfo = Build-Module -SourcePath .\Source\Configuration ` - -Target Build -Passthru ` - @PSBoundParameters - - # Copy and then remove the extra output - Copy-Item -Path (Join-Path $MetadataInfo.ModuleBase Metadata.psm1) -Destination $ConfigurationInfo.ModuleBase - Remove-Item $MetadataInfo.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 $ConfigurationInfo.Path -PropertyName FunctionsToExport ` - -Value @( - @( - $MetadataInfo.ExportedFunctions.Keys - $ConfigurationInfo.ExportedFunctions.Keys - ) | Select-Object -Unique - # @('*') - ) - - # Put the ExportedAliases of both in the manifest - Update-Metadata -Path $ConfigurationInfo.Path -PropertyName AliasesToExport ` - -Value @( - @( - $MetadataInfo.ExportedAliases.Keys - $ConfigurationInfo.ExportedAliases.Keys - ) | Select-Object -Unique - # @('*') - ) - - $ConfigurationInfo - + Build-Module @PSBoundParameters -Target CleanBuild } finally { Pop-Location -StackName BuildTestStack } \ No newline at end of file diff --git a/Source/Configuration.psd1 b/Source/Configuration.psd1 index 0be314d..ccf0835 100644 --- a/Source/Configuration.psd1 +++ b/Source/Configuration.psd1 @@ -4,7 +4,7 @@ ModuleToProcess = 'Configuration.psm1' # Version number of this module. -ModuleVersion = '1.7.2' +ModuleVersion = '1.5.0' # ID used to uniquely identify this module GUID = 'e56e5bec-4d97-4dfd-b138-abbaa14464a6' @@ -16,7 +16,7 @@ Author = @('Joel Bennett') CompanyName = 'HuddledMasses.org' # Copyright statement for this module -Copyright = 'Copyright (c) 2014-2019 by Joel Bennett, all rights reserved.' +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.' @@ -26,9 +26,10 @@ 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','.\Metadata.psm1','.\en-US\about_Configuration.help.txt') +FileList = @('.\Configuration.psd1','.\Configuration.psm1','.\en-US\about_Configuration.help.txt') PrivateData = @{ # Allows overriding the default paths where Configuration stores it's configuration @@ -67,13 +68,8 @@ PrivateData = @{ # Release notes for this particular version of the module ReleaseNotes = ' - - Fix bug in Get-Metadata with complex values (#19) - - Fix postfix/suffix - - Fix serialization of scriptblocks with single quotes - - Convert the modules to ModuleBuilder format - - Switch build to azure pipelines - - Clean up extra output lines in psd1 files - - Clean up exports + - Extract the Metadata module + - Add support for arbitrary AllowedVariables ' } } diff --git a/Source/Header/param.ps1 b/Source/Header/param.ps1 index 004d5fb..f4f9e3c 100644 --- a/Source/Header/param.ps1 +++ b/Source/Header/param.ps1 @@ -6,9 +6,4 @@ param( $MachineData ) -$ConfigurationRoot = Get-Variable PSScriptRoot* -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq "PSScriptRoot" } | ForEach-Object { $_.Value } -if (!$ConfigurationRoot) { - $ConfigurationRoot = Split-Path $MyInvocation.MyCommand.Path -Parent -} - -Import-Module "${ConfigurationRoot}\Metadata.psm1" -Force -Args @($Converters) -Verbose:$false \ No newline at end of file +Import-Module Metadata -Force -Args @($Converters) -Verbose:$false -Global \ No newline at end of file diff --git a/Specs/ScriptAnalyzer.feature b/Specs/ScriptAnalyzer.feature index 890ff25..c13267b 100644 --- a/Specs/ScriptAnalyzer.feature +++ b/Specs/ScriptAnalyzer.feature @@ -5,67 +5,67 @@ Feature: Passes Script Analyzer Scenario: ScriptAnalyzer on the compiled module output Given the configuration module is imported - When we run ScriptAnalyzer on 'Configuration' with 'PSScriptAnalyzerSettings.psd1' + When we run ScriptAnalyzer on 'C:\Users\Jaykul\Projects\Modules\Configuration\1.4.5' 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 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 PSAvoidShouldContinueWithoutForce - Then it passes the ScriptAnalyzer rule PSAvoidTrailingWhitespace - Then it passes the ScriptAnalyzer rule PSAvoidUsingUsernameAndPasswordParams - 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 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 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 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 PSAvoidShouldContinueWithoutForce + Then it passes the ScriptAnalyzer rule PSAvoidTrailingWhitespace + Then it passes the ScriptAnalyzer rule PSAvoidUsingUsernameAndPasswordParams + 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 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 diff --git a/Test.ps1 b/Test.ps1 index 1a5d8da..d2af6c2 100644 --- a/Test.ps1 +++ b/Test.ps1 @@ -32,8 +32,7 @@ try { } } - $Specs = Join-Path $PSScriptRoot Specs - + $Specs = @{ Path = Join-Path $PSScriptRoot Specs } # Just to make sure everything is kosher, run tests in a clean session $PSModulePath = $Env:PSModulePath Invoke-Command { @@ -44,10 +43,10 @@ try { Write-Host "Testing Configuration $SemVer" $SemVer = ($SemVer -split "-")[0] - # We need to make sure we load the right version of the module - Remove-Module Configuration -ErrorAction SilentlyContinue -Force - Import-Module Configuration -RequiredVersion $SemVer - Invoke-Gherkin $Specs + # We need to make sure we have loaded ONLY the right version of the module + Get-Module Configuration -All | Remove-Module -ErrorAction SilentlyContinue -Force + $Specs["CodeCoverage"] = Import-Module Configuration -RequiredVersion $SemVer -PassThru | Select-Object -Expand Path + Invoke-Gherkin @Specs } } finally { diff --git a/Source/build.psd1 b/build.psd1 similarity index 58% rename from Source/build.psd1 rename to build.psd1 index 83412fd..f10583b 100644 --- a/Source/build.psd1 +++ b/build.psd1 @@ -1,7 +1,7 @@ @{ - ModuleManifest = "Configuration.psd1" + ModuleManifest = "Source/Configuration.psd1" + OutputDirectory = "../" SourceDirectories = @("Private", "Public") Prefix = "Header\param.ps1" - ReadMe = "..\..\ReadMe.md" VersionedOutputDirectory = $true } \ No newline at end of file From 366d9074b5709df9a85391de44ade0b713a9a549 Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Sat, 3 Jul 2021 00:24:46 -0400 Subject: [PATCH 7/8] Add Metadata to RequiredModules.psd1 --- RequiredModules.psd1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 45c3d40..a32e023 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -1,6 +1,7 @@ @{ Configuration = "[1.3.1,2.0)" - Pester = "[4.10.1,5.0)" - ModuleBuilder = "[2.0.0,3.0)" + Metadata = "1.5.*" + Pester = "4.10.*" + ModuleBuilder = "2.0.*" PSScriptAnalyzer = "1.19.1" } \ No newline at end of file From e1d893054c2ca3f34d0a4384347869dc61250a74 Mon Sep 17 00:00:00 2001 From: Joel Bennett Date: Sat, 3 Jul 2021 01:00:35 -0400 Subject: [PATCH 8/8] Extend testing to 'nix and OSX --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b5905f..eacc66b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,8 +40,9 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: - os: [windows-latest, windows-2016] #, ubuntu-16.04, ubuntu-18.04, macos-latest] + os: [windows-latest, windows-2016, ubuntu-latest, macos-latest] needs: build steps: - name: Download Build Output