DescriptionFile.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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
$nameRegex = "^[0-9A-Za-z_]+$"
$versionRegex = "^v?(?:(?:(?<epoch>[0-9]+)!)?(?<release>[0-9]*(?:[_\.][0-9]+)*)(?<pre>[_\.]?(?<pre_l>(a|b|c|rc|alpha|beta|pre|preview|sp))[_\.]?(?<pre_n>[0-9]+)?)?(?<post>(?:-(?<post_n1>[0-9]+))|(?:[_\.]?(?<post_l>post|rev|r)[_\.]?(?<post_n2>[0-9]+)?))?(?<dev>[_\.]?(?<dev_l>dev)[_\.]?(?<dev_n>[0-9]+)?)?)(?:\+(?<local>[a-z0-9]+(?:[_\.][a-z0-9]+)*))?$"
$architectureRegex = "^x64|x86$"
$additionalOptionsRegex = "^[0-9A-Za-z.]+$"

function Split-EnvironmentModuleName([String] $ModuleFullName, [switch] $Silent)
{
    <#
    .SYNOPSIS
    Splits the given name into an array with 4 parts (name, version, architecture, additionalOptions).
    .DESCRIPTION
    Split a name string that either has the format 'Name-Version-Architecture' or just 'Name'. The output is
    an anonymous object with the 4 properties (name, version, architecture, additionalOptions). If a value was not specified,
    $null is returned at the according array index.
    .PARAMETER ModuleFullName
    The full name of the module that should be splitted.
    .OUTPUTS
    A string array with 4 parts (name, version, architecture, additionalOptions)
    #>

    $parts = $ModuleFullName.Split("-")
    $nameMatchResult = [System.Text.RegularExpressions.Regex]::Match($parts[0], $nameRegex, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)

    $result = @{}
    $result.Name = $nameMatchResult.Value

    $regexOrder = @(@($versionRegex, "Version"), @($architectureRegex, "Architecture"), @($additionalOptionsRegex, "AdditionalOptions"))

    $currentRegexIndex = 0
    $matchFailed = (-not ($nameMatchResult.Success))
    for($i = 1; $i -lt $parts.Count; $i++) {
        if($currentRegexIndex -ge $regexOrder.Count) {
            # More parts than matching regexes found
            $matchFailed = $true
            break
        }

        $currentRegex = $regexOrder[$currentRegexIndex][0]
        $matchResult = [System.Text.RegularExpressions.Regex]::Match($parts[$i], $currentRegex, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
        if($matchResult.Success) {
            $result.($regexOrder[$currentRegexIndex][1]) = $matchResult.Value
        }
        else {
            $i-- # We have to check the same part again with the next regex in the list
        }
        $currentRegexIndex++
    }

    if($matchFailed) {
        if(-not ($Silent)) {
            Write-Warning "The environment module name '$ModuleFullName' is not correctly formated. It must be 'Name-Version-Architecture-AdditionalOptions'"
        }
        return $null
    }

    return $result
}

function Read-EnvironmentModuleDescriptionFile([string] $ModuleBase, [string] $ModuleFullName)
{
    <#
    .SYNOPSIS
    Read the Environment Module file (*.pse) of the of the given module.
    .DESCRIPTION
    This function will read the environment module info of the given module. If the module does not depend on the environment module, $null is returned. If no
    description file was found, an empty map is returned.
    .OUTPUTS
    The map containing the values or $null.
    #>


    Write-Verbose "Reading environment module description file for $($Module.Name)"

    # Search for a pse1 file in the base directory
    return Read-EnvironmentModuleDescriptionFileByPath (Join-Path $ModuleBase "$($ModuleFullName).pse1")
}

function Read-EnvironmentModuleDescriptionFileByPath([string] $Path)
{
    <#
    .SYNOPSIS
    Read the given Environment Module file (*.pse).
    .DESCRIPTION
    This function will read the environment module info. If the description file was not found, an empty map is returned.
    .OUTPUTS
    The map containing the values or $null.
    #>


    if(Test-Path $Path) {
        # Parse the pse1 file
        Write-Verbose "Found desciption file $descriptionFile"
        return Import-PowershellDataFile $Path
    }

    return @{}
}

function New-EnvironmentModuleInfoBase
{
    <#
    .SYNOPSIS
    Create a new EnvironmentModuleInfoBase object from the given parameters.
    .PARAMETER Module
    The module info that contains the base information.
    .OUTPUTS
    The created object of type EnvironmentModuleInfoBase or $null.
    .NOTES
    The given module name must match exactly one module, otherwise $null is returned.
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    param (
        [PSModuleInfo] $Module
    )

    $nameParts = Split-EnvironmentModuleName $Module.Name
    if($null -eq $nameParts) {
        return $null
    }

    $descriptionContent = Read-EnvironmentModuleDescriptionFile $Module.ModuleBase $Module.Name

    if(-not $descriptionContent) {
        return $null
    }

    $result = New-Object EnvironmentModuleCore.EnvironmentModuleInfoBase -ArgumentList @($Module.Name, $Module.ModuleBase, $nameParts.Name, $nameParts.Version, $nameParts.Architecture, $nameParts.AdditionalOptions, [EnvironmentModuleCore.EnvironmentModuleType]::Default)
    Set-EnvironmentModuleInfoBaseParameter $result $descriptionContent

    return $result
}

function Set-EnvironmentModuleInfoBaseParameter
{
    <#
    .SYNOPSIS
    Assign the given parameters to the passed module object.
    .PARAMETER Module
    The module to modify.
    .PARAMETER Parameters
    The parameters to set.
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    param(
        [EnvironmentModuleCore.EnvironmentModuleInfoBase][ref] $Module,
        [hashtable] $Parameters
    )

    if($Parameters.Contains("ModuleType")) {
        $Module.ModuleType = [Enum]::Parse([EnvironmentModuleCore.EnvironmentModuleType], $descriptionContent.Item("ModuleType"))
        Write-Verbose "Read module type $($Module.ModuleType)"
    }
}

function New-EnvironmentModuleInfo
{
    <#
    .SYNOPSIS
    Create a new EnvironmentModuleInfo object from the given parameters.
    .PARAMETER Module
    The module info that contains the base information.
    .PARAMETER ModuleFullName
    The full name of the module. Only used if the module parameter is not set.
    .PARAMETER ModuleFile
    The module file (psd1) to load. If this is set, the ModuleFullName is not evaluated.
    .OUTPUTS
    The created object of type EnvironmentModuleInfo or $null.
    .NOTES
    The given module name must match exactly one module, otherwise $null is returned.
    #>

    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    param (
        [EnvironmentModuleCore.EnvironmentModuleInfoBase] $Module = $null,
        [String] $ModuleFullName = $null,
        [String] $ModuleFile = $null
    )

    if($null -eq $Module) {
        if(-not ([string]::IsNullOrEmpty($ModuleFile))) {
            $matchingModules = (Get-Module "$ModuleFile" -ListAvailable)

            if($matchingModules.Length -lt 1) {
                Write-Verbose "Unable to find the module $ModuleFile"
                return $null
            }

            $Module = New-EnvironmentModuleInfoBase $matchingModules[0]
        }
        else {
            $matchingModules = Get-EnvironmentModule -ListAvailable $ModuleFullName

            if($matchingModules.Length -lt 1) {
                Write-Verbose "Unable to find the module $ModuleFullName in the list of all environment modules"
                return $null
            }

            if($matchingModules.Length -gt 1) {
                Write-Warning "More than one environment module matches the given full name '$ModuleFullName'"
            }

            $Module = $matchingModules[0]
        }
    }

    $descriptionContent = Read-EnvironmentModuleDescriptionFile $Module.ModuleBase $Module.FullName

    if(-not $descriptionContent) {
        return $null
    }

    $arguments = @($Module, $null, (Join-Path $script:tmpEnvironmentRootSessionPath $Module.Name))

    $result = New-Object EnvironmentModuleCore.EnvironmentModuleInfo -ArgumentList $arguments

    Set-EnvironmentModuleInfoBaseParameter $result $descriptionContent

    $result.DirectUnload = $false
    $customSearchPaths = $script:customSearchPaths[$Module.FullName]
    if ($customSearchPaths) {
        $result.SearchPaths = $result.SearchPaths + $customSearchPaths
    }

    $dependencies = @()
    if($descriptionContent.Contains("RequiredEnvironmentModules")) {
        Write-Warning "The field 'RequiredEnvironmentModules' defined for '$($Module.FullName)' is deprecated, please use the dependencies field."
        $dependencies = $descriptionContent.Item("RequiredEnvironmentModules") | Foreach-Object { New-Object "EnvironmentModuleCore.DependencyInfo" -ArgumentList $_}
        Write-Verbose "Read module dependencies $($dependencies)"
    }

    if($descriptionContent.Contains("Dependencies")) {
        $dependencies = $dependencies + ($descriptionContent.Item("Dependencies") | Foreach-Object {
            if($_.GetType() -eq [string]) {
                New-Object "EnvironmentModuleCore.DependencyInfo" -ArgumentList $_
            }
            else {
                New-Object "EnvironmentModuleCore.DependencyInfo" -ArgumentList $_.Name, $_.Optional
            }
        })
        Write-Verbose "Read module dependencies $($dependencies)"
    }

    $result.Dependencies = $dependencies

    if($descriptionContent.Contains("DirectUnload")) {
        $result.DirectUnload = $descriptionContent.Item("DirectUnload")
        Write-Verbose "Read module direct unload $($result.DirectUnload)"
    }

    $requiredItems = @()
    if($descriptionContent.Contains("RequiredFiles")) {
        Write-Warning "The field 'RequiredFiles' defined for '$($Module.FullName)' is deprecated, please use the RequiredItems field."
        $requiredItems = $result.RequiredItems + ($descriptionContent.Item("RequiredFiles") | ForEach-Object {
            New-Object "EnvironmentModuleCore.RequiredItem" -ArgumentList ([EnvironmentModuleCore.RequiredItem]::TYPE_FILE), $_
        })
        Write-Verbose "Read required files $($descriptionContent.Item('RequiredFiles'))"
    }

    if($descriptionContent.Contains("RequiredItems") -and $descriptionContent.Item("RequiredItems").count -gt 0) {
        $requiredItems = $requiredItems + ($descriptionContent.Item("RequiredItems") | Foreach-Object {
            if($_.GetType() -eq [string]) {
                New-Object "EnvironmentModuleCore.RequiredItem" -ArgumentList ([EnvironmentModuleCore.RequiredItem]::TYPE_FILE), $_
            }
            else {
                New-Object "EnvironmentModuleCore.RequiredItem" -ArgumentList $_.Type, $_.Value
            }
        })
        Write-Verbose "Read module dependencies $($dependencies)"
    }

    $result.RequiredItems = $requiredItems

    if($descriptionContent.Contains("DefaultRegistryPaths") -and $descriptionContent.Item("DefaultRegistryPaths").count -gt 0) {
        Write-Warning "The field 'DefaultRegistryPaths' defined for '$($Module.FullName)' is deprecated, please use the DefaultSearchPaths field."
        $pathValues = $descriptionContent.Item("DefaultRegistryPaths")
        $searchPathType = "REGISTRY"
        $searchPathPriority = $script:searchPathTypes[$searchPathType].Item2
        Write-Verbose "Read default registry paths $($result.DefaultRegistryPaths)"

        $result.SearchPaths = $result.SearchPaths + ($pathValues | ForEach-Object {
            $parts = $_.Split([IO.Path]::PathSeparator) + @("")
            New-Object "EnvironmentModuleCore.SearchPath" -ArgumentList @($parts[0], $searchPathType, $searchPathPriority, $parts[1], $true)
        })
    }

    if($descriptionContent.Contains("DefaultFolderPaths") -and $descriptionContent.Item("DefaultFolderPaths").count -gt 0) {
        Write-Warning "The field 'DefaultFolderPaths' defined for '$($Module.FullName)' is deprecated, please use the DefaultSearchPaths field."
        $pathValues = $descriptionContent.Item("DefaultFolderPaths")
        $searchPathType = [EnvironmentModuleCore.SearchPath]::TYPE_DIRECTORY
        $searchPathPriority = $script:searchPathTypes[$searchPathType].Item2
        Write-Verbose "Read default folder paths $($result.DefaultFolderPaths)"

        $result.SearchPaths = $result.SearchPaths + ($pathValues | ForEach-Object {
            $parts = $_.Split([IO.Path]::PathSeparator) + @("")
            New-Object "EnvironmentModuleCore.SearchPath" -ArgumentList @($parts[0], $searchPathType, $searchPathPriority, $parts[1], $true)
        })
    }

    if($descriptionContent.Contains("DefaultEnvironmentPaths") -and $descriptionContent.Item("DefaultEnvironmentPaths").count -gt 0) {
        Write-Warning "The field 'DefaultEnvironmentPaths' defined for '$($Module.FullName)' is deprecated, please use the DefaultSearchPaths field."
        $pathValues = $descriptionContent.Item("DefaultEnvironmentPaths")
        $searchPathType = [EnvironmentModuleCore.SearchPath]::TYPE_ENVIRONMENT_VARIABLE
        $searchPathPriority = $script:searchPathTypes[$searchPathType].Item2
        Write-Verbose "Read default environment paths $($result.DefaultEnvironmentPaths)"

        $result.SearchPaths = $result.SearchPaths + ($pathValues | ForEach-Object {
            $parts = $_.Split([IO.Path]::PathSeparator) + @("")
            New-Object "EnvironmentModuleCore.SearchPath" -ArgumentList @($parts[0], $searchPathType, $searchPathPriority, $parts[1], $true)
        })
    }

    if($descriptionContent.Contains("DefaultSearchPaths") -and $descriptionContent.Item("DefaultSearchPaths").count -gt 0) {
        $result.SearchPaths = $result.SearchPaths + ($descriptionContent.Item("DefaultSearchPaths") | ForEach-Object {
            if($_.GetType() -eq [string]) {
                $searchPathType = [EnvironmentModuleCore.SearchPath]::TYPE_DIRECTORY
                $searchPathPriority = $script:searchPathTypes[$searchPathType].Item2
                New-Object "EnvironmentModuleCore.SearchPath" -ArgumentList $_, $searchPathType, $searchPathPriority, $null, $true
            }
            else {
                $searchPathType = $_.Type
                $searchPathPriority = $_.Priority
                if($null -eq $searchPathPriority) {
                    $searchPathPriority = $script:searchPathTypes[$searchPathType].Item2
                }

                New-Object "EnvironmentModuleCore.SearchPath" -ArgumentList $_.Key, $searchPathType, $searchPathPriority, $_.SubFolder, $true
            }
        })
        Write-Verbose "Read module default search paths $($result.SearchPaths)"
    }

    if($descriptionContent.Contains("StyleVersion")) {
        $result.StyleVersion = $descriptionContent.Item("StyleVersion")
        Write-Verbose "Read module style version $($result.StyleVersion)"
    }

    if($descriptionContent.Contains("Category")) {
        $result.Category = $descriptionContent.Item("Category")
        Write-Verbose "Read module category $($result.Category)"
    }

    if($descriptionContent.Contains("Parameters")) {
        $descriptionContent.Item("Parameters").Keys | Foreach-Object { $result.Parameters[$_] = $descriptionContent.Item("Parameters")[$_] }
        Write-Verbose "Read module parameters $($result.Parameters.GetEnumerator() -join ',')"
    }

    return $result
}