functions/configuration/Set-PSFConfig.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
function Set-PSFConfig
{
<#
 .SYNOPSIS
  Sets configuration entries.
  
 .DESCRIPTION
  This function creates or changes configuration values.
  These can be used to provide dynamic configuration information outside the PowerShell variable system.
  
 .PARAMETER FullName
  The full name of a configuration element. Must be namespaced <Module>.<Name>.
  The name can have any number of sub-segments, in order to better group configurations thematically.
  
 .PARAMETER Name
  Name of the configuration entry. If an entry of exactly this non-casesensitive name already exists, its value will be overwritten.
  Duplicate names across different modules are possible and will be treated separately.
  If a name contains namespace notation and no module is set, the first namespace element will be used as module instead of name. Example:
  -Name "Nordwind.Server"
  Is Equivalent to
  -Name "Server" -Module "Nordwind"
  
 .PARAMETER Module
  This allows grouping configuration elements into groups based on the module/component they serve.
  If this parameter is not set, the configuration element must have a module name in the name parameter (the first segment will be taken, irrespective of whether that makes sense).
  
 .PARAMETER Value
  The value to assign to the named configuration element.
  
 .PARAMETER Description
  Using this, the configuration setting is given a description, making it easier for a user to comprehend, what a specific setting is for.
  
 .PARAMETER Validation
  The name of the validation script used for input validation.
  These can be used to validate make sure that input is of the proper data type.
  New validation scripts can be registered using Register-PSFConfigValidation
  
 .PARAMETER Handler
  A scriptblock that is executed when a value is being set.
  Is only executed if the validation was successful (assuming there was a validation, of course)
  
 .PARAMETER Hidden
  Setting this parameter hides the configuration from casual discovery. Configurations with this set will only be returned by Get-Config, if the parameter "-Force" is used.
  This should be set for all system settings a user should have no business changing (e.g. for Infrastructure related settings such as mail server).
  
 .PARAMETER Default
  Setting this parameter causes the system to treat this configuration as a default setting. If the configuration already exists, no changes will be performed.
  Useful in scenarios where for some reason it is not practical to automatically set defaults before loading userprofiles.
  
 .PARAMETER Initialize
  Use this when setting configurations as part of module import.
  When initializing a configuration, it will only do a thing if the configuration hasn't already been initialized (So if you load the module multiple times or in multiple runspaces, it won't make a difference)
  Also, if there already was a non-initialized setting set for a given configuration, it will then try to set the old value again.
  This value will be processed by handlers, if any are set.
  
 .PARAMETER DisableValidation
  This parameters disables the input validation - if any - when processing a setting.
  Normally this shouldn't be circumvented, but just in case, it can be disabled.
  
 .PARAMETER DisableHandler
  This parameter disables the configuration handlers.
  Configuration handlers are designed to automatically process input set to a config value, in addition to writing the value.
  In many cases, this is used to improve performance, by forking the value location also to a static C#-field, which is then used, rather than searching a Hashtable.
  Normally these shouldn't be circumvented, but just in case, it can be disabled.
  
 .PARAMETER EnableException
  Replaces user friendly yellow warnings with bloody red exceptions of doom!
  Use this if you want the function to throw terminating errors you want to catch.
  
 .EXAMPLE
  PS C:\> Set-PSFConfig -FullName 'MyModule.User' -Value "Friedrich" -Description "The user under which the show must go on."
   
  Creates a configuration entry under the module "MyModule" named "User" with the value "Friedrich"
  
 .EXAMPLE
  PS C:\> Set-PSFConfig -Name 'mymodule.User' -Value "Friedrich" -Description "The user under which the show must go on." -Handler $scriptBlock -Initialize -Validation String
   
  Creates a configuration entry ...
  - Named "mymodule.user"
  - With the value "Friedrich"
  - It adds a description as noted
  - It registers the scriptblock stored in $scriptBlock as handler
  - It initializes the script. This block only executes the first time a it is run like this. Subsequent calls will be ignored.
  - It registers the basic string input type validator
  This is the default example for modules using the configuration system.
  Note: While the -Handler parameter is optional, it is important to add it at the initial initialize call, if you are planning to add it.
  Only then will the system validate previous settings (such as what a user might have placed in his user profile)
  
 .EXAMPLE
  PS C:\> Set-PSFConfig 'Company' 'ConfigLink' 'https://www.example.com/config.xml' -Hidden
   
  Creates a configuration entry named "ConfigLink" in the "Company" module with the value 'https://www.example.com/config.xml'.
  This entry is hidden from casual discovery using Get-PSFConfig.
  
 .EXAMPLE
  PS C:\> Set-PSFConfig 'Network.Firewall' '10.0.0.2' -Default
   
  Creates a configuration entry named "Firewall" in the "Network" module with the value '10.0.0.2'
  This is only set, if the setting does not exist yet. If it does, this command will apply no changes.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [CmdletBinding(DefaultParameterSetName = "FullName")]
    Param (
        [Parameter(ParameterSetName = "FullName", Position = 0, Mandatory = $true)]
        [string]
        $FullName,
        
        [Parameter(ParameterSetName = "Module", Position = 1, Mandatory = $true)]
        [string]
        $Name,
        
        [Parameter(ParameterSetName = "Module", Position = 0)]
        [string]
        $Module,
        
        [Parameter(ParameterSetName = "FullName", Position = 1)]
        [Parameter(ParameterSetName = "Module", Position = 2)]
        [AllowNull()]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        $Value,
        
        [string]
        $Description,
        
        [string]
        $Validation,
        
        [System.Management.Automation.ScriptBlock]
        $Handler,
        
        [switch]
        $Hidden,
        
        [switch]
        $Default,
        
        [switch]
        $Initialize,
        
        [switch]
        $DisableValidation,
        
        [switch]
        $DisableHandler,
        
        [switch]
        $EnableException
    )
    
    #region Prepare Names
    if ($PSCmdlet.ParameterSetName -eq "FullName")
    {
        if (-not $FullName.Trim(".").Contains("."))
        {
            Stop-PSFFunction -Message "Invalid Name: $FullName ! At least one '.' is required, to separate module from name" -EnableException $EnableException -Category InvalidArgument
            return
        }
        
        $Module = $FullName.Split(".")[0].ToLower().Trim(".")
        $Name = $FullName.Substring(($Module.Length + 1)).ToLower().Trim(".")
        $internalFullName = $FullName.ToLower().Trim(".")
    }
    else
    {
        $Name = $Name.ToLower().Trim(".")
        if ($Module) { $Module = $Module.ToLower().Trim(".") }
        
        if ((Test-PSFParameterBinding -ParameterName "Module" -Not) -and ($Name -match ".+\..+"))
        {
            $r = $Name | select-string "^(.+?)\..+" -AllMatches
            $Module = $r.Matches[0].Groups[1].Value
            $Name = $Name.Substring($Module.Length + 1)
        }
        elseif ((Test-PSFParameterBinding -ParameterName "Module" -Not) -and ($Name -notmatch ".+\..+"))
        {
            Stop-PSFFunction -Message "Invalid Name: $Name ! At least one '.' is required when not explicitly specifying a module name, to separate module from name" -EnableException $EnableException -Category InvalidArgument
            return
        }
        
        If ($Module) { $internalFullName = $Module, $Name -join "." }
        else { $internalFullName = $Name }
    }
    #endregion Prepare Names
    
    #region Prepare runtime and kill execution as needed
    if ([PSFramework.Configuration.ConfigurationHost]::Configurations.ContainsKey($internalFullName))
    {
        $itExists = $true
        $itIsInitialized = [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Initialized
        $itIsEnforced = [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].PolicyEnforced
    }
    else
    {
        $itExists = $false
        $itIsInitialized = $false
        $itIsEnforced = $false
    }
    
    if ($itExists -and $Default) { return }
    if ($itIsInitialized -and $Initialize) { return }
    if ($itIsEnforced -and (-not $Initialize))
    {
        Stop-PSFFunction -Message "Could not update configuration due to policy settings: $internalFullName" -EnableException $EnableException -Category PermissionDenied
        return
    }
    
    if (Test-PSFParameterBinding -ParameterName "Validation")
    {
        if (-not ([PSFramework.Configuration.ConfigurationHost]::Validation.Keys -contains $Validation.ToLower()))
        {
            Stop-PSFFunction -Message "Invalid validation name: $Validation. Supported validations: $([PSFramework.Configuration.ConfigurationHost]::Validation.Keys -join ", ")" -Category InvalidArgument -Target $Name
            return
        }
    }
    #endregion Prepare runtime and kill execution as needed
    
    #region Initializing a configuration
    if ($Initialize)
    {
        if ($itExists)
        {
            $oldValue = [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Value
            $cfg = [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName]
        }
        else { $cfg = New-Object PSFramework.Configuration.Config }
        $cfg.Name = $Name
        $cfg.Module = $Module
        $cfg.Description = $Description
        $cfg.Value = $Value
        $cfg.Handler = $Handler
        if ($Validation) { $cfg.Validation = [PSFramework.Configuration.ConfigurationHost]::Validation[$Validation.ToLower()] }
        $cfg.Hidden = $Hidden
        $cfg.Initialized = $true
        [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName] = $cfg
        
        if ($itExists) { Set-PSFConfig -Name $internalFullName -Value $oldValue }
    }
    #endregion Initializing a configuration
    
    #region Regular configuration update
    else
    {
        if (-not $itExists)
        {
            $cfg = New-Object PSFramework.Configuration.Config
            $cfg.Name = $Name
            $cfg.Module = $Module
            $cfg.Description = $Description
            $cfg.Handler = $Handler
            if ($Validation) { $cfg.Validation = [PSFramework.Configuration.ConfigurationHost]::Validation[$Validation.ToLower()] }
            $cfg.Hidden = $Hidden
            [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName] = $cfg
            
            Set-PSFConfig -Name $internalFullName -Value $Value
            return
        }
        
        else
        {
            [PSFramework.Configuration.Config]$cfg = [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName]
            if ((-not $DisableValidation) -and ($cfg.Validation) -and (Test-PSFParameterBinding -ParameterName "Value"))
            {
                $testResult = $cfg.Validation.Invoke($Value)
                if (-not $TestResult.Success)
                {
                    Stop-PSFFunction -Message "Could not update configuration $internalFullName | Failed validation: $($testResult.Message)" -EnableException $EnableException -Category InvalidResult -Target $internalFullName
                    return
                }
                $Value = $testResult.Value
            }
            if ((-not $DisableHandler) -and ($cfg.Handler) -and (Test-PSFParameterBinding -ParameterName "Value"))
            {
                try { $cfg.Handler.Invoke($Value) }
                catch
                {
                    Stop-PSFFunction -Message "Could not update configuration $internalFullName | Failed handling $_" -EnableException $EnableException -Category InvalidResult -Target $internalFullName
                    return
                }
            }
            
            if (Test-PSFParameterBinding -ParameterName "Hidden") { [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Hidden = $Hidden }
            if (Test-PSFParameterBinding -ParameterName "Value") { [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Value = $Value }
            if (Test-PSFParameterBinding -ParameterName "Description") { [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Description = $Description }
            if (Test-PSFParameterBinding -ParameterName "Handler") { [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Handler = $Handler }
            if (Test-PSFParameterBinding -ParameterName "Validation") { [PSFramework.Configuration.ConfigurationHost]::Configurations[$internalFullName].Validation = [PSFramework.Configuration.ConfigurationHost]::Validation[$Validation.ToLower()] }
        }
    }
    #endregion Regular configuration update
}