PSConfig.psm1

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
<#
.SYNOPSIS
Creates a layer of abstraction above reading configuration data.
#>


# Load strings file
$CurrentPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
Import-LocalizedData -BindingVariable r -BaseDirectory (Join-Path -Path $CurrentPath -ChildPath "Localized")
$Script:r = $r

# This array keeps track of all the configuration sources that have been added.
$Script:ConfigurationSources = @()

Function Get-ConfigurationItem {
    <#
    .SYNOPSIS
    Gets a piece of configuration data from the first source that contains the
    specified key.
 
    .DESCRIPTION
    Gets a piece of configuration data from the first source that contains the
    specified key. Configuration sources are searched in the order in which they
    were loaded.
 
    .PARAMETER Key
    Specifies the key to retrieve a value for.
 
    .OUTPUTS
    Variant. The output type depends on the contents of the configuration data.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [string]$Key
    )

    Process {
        $ReturnValue = $null
        foreach ($Item in $Script:ConfigurationSources) {
            Write-Verbose -Message ([string]::Format($Script:r.SearchingForItem_F0_In_F1_Type_F2, $Key, $Item.Name, $Item.Type))
            if ($Item.Type -eq "Environment") {
                if (Test-Path -Path "Env:\$Key") {
                    Write-Verbose -Message ([string]::Format($Script:r.Item_F0_Found, $Key))
                    $ReturnValue = (Get-Item -Path "Env:\$Key").Value
                    break
                }
            } else {
                $Property = $Item.Data.PSObject.Properties | Where-Object -FilterScript { $_.Name -eq $Key }
                if ($Property) {
                    Write-Verbose -Message ([string]::Format($Script:r.Item_F0_Found, $Key))
                    $ReturnValue = $Property.Value
                    break
                }
            }
        }

        return $ReturnValue
    }
}

Function Add-DefaultConfigurationSource {
    <#
    .SYNOPSIS
    Adds a configuration source with explicitly specified values.
 
    .DESCRIPTION
    Adds a configuration source with explicitly specified values. This can be
    useful for specifying fallback values that may not be present in a user's
    specific configuration.
 
    .PARAMETER InputObject
    Specifies configuration data.
 
    .OUTPUTS
    None.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true
        )]
        [PSCustomObject[]]$InputObject
    )

    Process {
        foreach ($Item in $InputObject) {
            # The data coming in should be a PSCustomObject already, but in case
            # it's not convert it.
            if ($Item -is [hashtable]) {
                $Item = New-Object -TypeName PSObject -Property $Item
            }

            $NewSource = New-Object -TypeName PSObject -Property @{
                Name = "Default Values"
                Type = "Default"
                Data = $Item
            }

            Write-Verbose -Message ([string]::Format($Script:r.AddingSource_F0_Type_F1, $NewSource.Name, $NewSource.Type))
            $Script:ConfigurationSources += $NewSource
        }
    }
}

Function Add-FileConfigurationSource {
    <#
    .SYNOPSIS
    Adds a configuration source from the specified file.
 
    .DESCRIPTION
    Adds a configuration source from the specified file. Files can be formatted
    in three different ways:
        StringData - The file will be imported and passed to
                     ConvertFrom-StringData
        Json - The file can be parsed using ConvertFrom-Json.
        Csv - The file can be parsed using Import-Csv. Only the first row will
              be read.
 
    .PARAMETER Path
    Specifies the path to the configuration file.
 
    .PARAMETER Format
    Specifies the format the configuration file is written in.
    #>

    [CmdletBinding()]
    Param (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true
        )]
        [string[]]$Path,

        [Parameter(
            ValueFromPipelineByPropertyName = $true
        )]
        [ValidateSet("StringData", "Json", "Csv")]
        [string]$Format = "StringData"
    )

    Process {
        foreach ($Item in $Path) {
            if (!(Test-Path -Path $Item)) {
                # The file may not exist, that's part of making this flexible,
                # so it's not an error.
                Write-Verbose -Message ([string]::Format($Script:r.File_F0_NotFound, $Item))
                continue
            }
            $NewSource = $null
            switch ($Format) {
                "StringData" {
                    try {
                        $HashData = Get-Content -Path $Item -Raw -ErrorAction Stop | ConvertFrom-StringData -ErrorAction Stop
                        $Data = New-Object -TypeName PSObject -Property $HashData -ErrorAction Stop

                        $NewSource = New-Object -TypeName PSObject -Property @{
                            Type = "File/StringData"
                            Name = $Item
                            Data = $Data
                        } -ErrorAction Stop
                    } catch {
                        Write-Error -Exception $_.Exception -Message ([string]::Format($Script:r.CannotLoad_F0_Data_F1, "StringData", $Item))
                    }
                }

                "Json" {
                    try {
                        $Data = Get-Content -Path $Item -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop

                        $NewSource = New-Object -TypeName PSObject -Property @{
                            Type = "File/Json"
                            Name = $Item
                            Data = $Data
                        } -ErrorAction Stop
                    } catch {
                        Write-Error -Exception $_.Exception -Message ([string]::Format($Script:r.CannotLoad_F0_Data_F1, "Json", $Item))
                    }
                }

                "Csv" {
                    try {
                        $Data = Import-Csv -Path $Item -ErrorAction Stop | Select-Object -First 1 -ErrorAction Stop

                        $NewSource = New-Object -TypeName PSObject -Property @{
                            Type = "File/Csv"
                            Name = $Item
                            Data = $Data
                        } -ErrorAction Stop
                    } catch {
                        Write-Error -Exception $_.Exception -Message ([string]::Format($Script:r.CannotLoad_F0_Data_F1, "Csv", $Item))
                    }
                }
            }

            if ($NewSource) {
                Write-Verbose -Message ([string]::Format($Script:r.AddingSource_F0_Type_F1, $NewSource.Name, $NewSource.Type))
                $Script:ConfigurationSources += $NewSource
            }
        }
    }
}

Function Add-EnvironmentConfigurationSource {
    <#
    .SYNOPSIS
    Adds the current session's environment variables as a configuration source.
 
    .DESCRIPTION
    Adds the current session's environment variables as a configuration source.
 
    .OUTPUTS
    None.
    #>

    [CmdletBinding()]
    Param ()

    Process {
        $NewSource = New-Object -TypeName PSObject -Property @{
            Type = "Environment"
            Name = "Environment Variables"
            Data = $null
        }

        Write-Verbose -Message ([string]::Format($Script:r.AddingSource_F0_Type_F1, $NewSource.Name, $NewSource.Type))
        $Script:ConfigurationSources += $NewSource
    }
}

Function Clear-ConfigurationSource {
    <#
    .SYNOPSIS
    Clears all currently loaded configuration sources.
 
    .DESCRIPTION
    Clears all currently loaded configuration sources.
 
    .OUTPUTS
    None.
    #>

    [CmdletBinding()]
    Param ()

    Process {
        Write-Verbose -Message $Script:r.ClearingSources
        $Script:ConfigurationSources = @()
    }
}