Integrom.Module.Bootstrap.psm1
|
$dynamicEnumBlock = @"
public enum %%ENUM_NAME%% { %%ENUM_DATA%% } "@ $IntegromLibraryExtension = @' psil '@ function logMessage([string]$message, [PSCustomObject[]]$values) { [System.ConsoleColor]$enteringColor = $session.Host.UI.RawUI.ForegroundColor if ($suppressOutput) {return} $chunks = [regex]::Split($message, '(?<!%)%%\w+%%(?!%)') for ($x = 0; $x -lt $chunks.count; $x ++) { if ($chunks.Count -eq 1) { $session.Host.UI.WriteLine($chunks) return } $session.Host.UI.Write($chunks[$x]) if ($null -ne $values[$x]) { $session.Host.UI.RawUI.ForegroundColor = $values[$x].color $session.Host.UI.Write($values[$x].value) $session.Host.UI.RawUI.ForegroundColor = $enteringColor } } $session.Host.UI.WriteLine() } function getPossibleLibPaths{[CmdletBinding()]param([string]$libName, [string]$baseLibPath, [System.Management.Automation.PSCmdlet]$session = $PSCmdlet) # [CmdletBinding()] is necessary to get the correct session to look for the _igmLibPaths environment variable [string[]]$pathSuffixes = '', '_lib\' [string[]]$result = @() $baseLibPath = $baseLibPath -replace '([\\/]+$)|(?<=\w$)', '\' $regexResult = [regex]::Match("$($baseLibPath)$($libName)", '(?<root>^[a-z]:|/|\.\.|\.|\\\\|(?:http|ftp|https|sftp)(://))?(?<path>.*(\\|/))?((?<libName>.+)(?<ext>\.psil$)|(?<libName>.+))', [Text.RegularExpressions.RegexOptions]::IgnoreCase) $root = $regexResult[0].Groups['root'].value $path = $regexResult[0].Groups['path'].value $libSname = $regexResult[0].Groups['libName'].value [string]$envPath = $ExecutionContext.SessionState.InvokeCommand.InvokeScript($session.SessionState, {param() # This section of code runs in the caller's session, not the module's session if ($null -ne $_igmLibPaths) {return $_igmLibPaths} else {return $null} }.Ast.GetScriptBlock() ) if ([string]::IsNullOrEmpty($root)) { $rootsToTest = '.\', '..\' if ($null -ne $envPath) {$rootsToTest += $envPath -replace '([\\/]+$)|(?<=\w$)', '\'} if (Test-Path -Path "$($env:ProgramFiles)\Integrom\Libraries\") {$rootsToTest += "$($env:ProgramFiles)\Integrom\Libraries\"} } else { $rootsToTest = $root } $pathSuffixes += "$($libSname)\" foreach ($rootEntry in $rootsToTest) { foreach ($pathSuffix in $pathSuffixes) { $result += "$($rootEntry)$($path)$($pathSuffix)$($libSname).$($IntegromLibraryExtension)" } } return $result } function getLoadedLibraries{[CmdletBinding()][OutputType([hashtable])]param([System.Management.Automation.PSCmdlet]$session) # [CmdletBinding()] is necessary to get the correct session state to search for loaded libraries if ($null -eq $session) {$session = $PSCmdlet} return $ExecutionContext.SessionState.InvokeCommand.InvokeScript( $session.SessionState, {param() # This section of code runs in the caller's session, not the module's session if ($null -ne $_igmLoadedLibraries) {return $_igmLoadedLibraries} else {return @{}} }.Ast.GetScriptBlock() ) } function loadLibraries{[CmdletBinding()][OutputType([hashtable])]param([string[]]$libraryNames, [string]$libPath, [System.Management.Automation.PSCmdlet]$session, [switch]$allowDowngrade = $false, [switch]$forceReload = $false, [switch]$suppressOutput = $false) # [CmdletBinding()] is necessary to get the correct session state to load the library into [hashtable]$resultData = @{statusCode = 0; failedLibs = @()} $libColor = [System.ConsoleColor]::DarkMagenta $libVerColor = [System.ConsoleColor]::Cyan if ($null -eq $session) {$session = $PSCmdlet} function updateFailedList() { $resultData.statusCode -= 1 $resultData.failedLibs += $libraryName } function selectMsgTemplate([int]$template, [string[]]$paramBlock) { # need to add bounds check on paramBlock switch ($template) { 1 {if ($paramBlock.Count -ge 1) {return @(@{value = $paramBlock[0]; color = $libColor})}} 2 {if ($paramBlock.Count -ge 3) {return @(@{value = $paramBlock[0]; color = $libColor}, @{value = $paramBlock[1]; color = $libVerColor}, @{value = $paramBlock[2]; color = $libVerColor})}} 3 {if ($paramBlock.Count -ge 2) {return @(@{value = $paramBlock[0]; color = $libColor}, @{value = $paramBlock[1]; color = $libVerColor})}} 4 {if ($paramBlock.Count -ge 3) {return @(@{value = $paramBlock[0]; color = $libColor}, @{value = $paramBlock[1]; color = [System.ConsoleColor]::White}, @{value = $paramBlock[2]; color = [System.ConsoleColor]::DarkCyan})}} } } if ($libraryNames.Count -lt 1) { $resultData.statusCode = -5 return $resultData } :libraryCollectionLoop foreach ($libraryName in $libraryNames) { $libSourceVerInfo = getLibrarySourceVersion($libraryName) $libLoadedVerInfo = (getLoadedLibraries $session)[$libraryName] if ($libLoadedVerInfo) { if ($libLoadedVerInfo.train -ne $libSourceVerInfo.train) { logMessage "❌ Replacing the loaded version of %%1%% with a release from a different train is not supported." (selectMsgTemplate 1 $libraryName) updateFailedList continue libraryCollectionLoop } switch ($libLoadedVerInfo.version) { {$_ -gt $libSourceVerInfo.version} { if ($allowDowngrade) { logMessage "ℹ Downgrading the in-memory version of %%1%% from %%2%% to %%3%%....." (selectMsgTemplate 2 @($libraryName, $libLoadedVerInfo.versionStr, $libSourceVerInfo.versionStr)) logMessage " ⚠ Beware: Any current functions, constants, and variables not referenced by the old source will remain as-is." } else { logMessage "❌ Downgrading the in-memory version of %%1%% was requested but is not permitted. Set the %%2%% flag in the call to %%3%%." (selectMsgTemplate 4 @($libraryName, '[allowDowngrade]', 'loadLibraries()')) updateFailedList continue libraryCollectionLoop } } {$_ -lt $libSourceVerInfo.version} { logMessage "ℹ Upgrading in-memory version of %%1%% from %%2%% to %%3%%......." (selectMsgTemplate 2 @($libraryName, $libLoadedVerInfo.versionStr, $libSourceVerInfo.versionStr)) logMessage " ⚠ Beware: Any current functions, constants, and variables not referenced by the new source will remain in memory as-is." } {$_ -eq $libSourceVerInfo.version} { logMessage "ℹ The current version of %%1%% is already loaded." (selectMsgTemplate 1 $libraryName) if ($forceReload) { logMessage "ℹ A forced reload of %%1%% has been requested." (selectMsgTemplate 1 $libraryName) logMessage " ⚠ Beware: Any current functions, constants, and variables not referenced by the new source will remain in memory as-is." } else { continue libraryCollectionLoop } } } } else { logMessage "ℹ Loading library %%1%%...." (selectMsgTemplate 1 $libraryName) } foreach ($fqLibPath in (getPossibleLibPaths -libName $libraryName -baseLibPath $libPath -session $session)) { if (Test-Path $fqLibPath) { $rawLibCode = Get-Content -Path $fqLibPath -Raw -ErrorAction SilentlyContinue -WarningAction SilentlyContinue if ($null -eq $rawLibCode) {break} $regexResult = [regex]::Match($rawLibCode, '^[\s|\t]*#_depends:[\s|\t]*(?<depends>.+)', [Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [Text.RegularExpressions.RegexOptions]::Multiline) $dependsRaw = $regexResult[0].Groups['depends'].value if (-not [string]::IsNullOrEmpty($dependsRaw)) { logMessage "ℹ Processing library dependencies...." $dependsList = $dependsRaw -split ',' foreach ($dependancy in $dependsList) { $dependancyResult = loadLibraries ($dependancy.Trim()) $libPath $session $resultData.statusCode += $dependancyResult.statusCode foreach ($failedLib in $dependancyResult.failedLibs) {$resultData.failedLibs += $dependancyResult.failedLib} } } $parsedLibCode = $rawLibCode -replace '%%PSIL_LOCATION%%', ($fqLibPath -replace '(?:\\|/)[^\\|/]+$', '') try { $ExecutionContext.SessionState.InvokeCommand.InvokeScript( $session.SessionState, {param($libCode, $libName, $libVersion) # This section of code runs in the caller's session, not the module's session if ($null -eq $_igmLoadedLibraries) {$_igmLoadedLibraries = @{}} if ($null -eq (Invoke-Expression -Command ($libCode))) { if ($null -eq $_igmLoadedLibraries.$libName) { [hashtable]$_igmLoadedLibraries.Add($libName, $libVersion) } else { $_igmLoadedLibraries.$libName = $libVersion } } }.Ast.GetScriptBlock(), $parsedLibCode, $libraryName, $libSourceVerInfo ) $libLoadedVerInfo = (getLoadedLibraries $session)[$libraryName] logMessage "✅ %%1%% version %%2%% has been loaded." (selectMsgTemplate 3 @($libraryName, $libLoadedVerInfo.versionStr)) break } catch { break } } } if ($null -eq $libLoadedVerInfo) { logMessage "❌ Failed to load library %%1%%." (selectMsgTemplate 1 $libraryName) updateFailedList } } return $resultData } function getLibrarySourceVersion{param([string]$libraryName, [string]$libPath) foreach ($fqLibPath in (getPossibleLibPaths -libName $libraryName -baseLibPath $libPath)) { if (Test-Path $fqLibPath) { $libHeader = Get-Content -Path $fqLibPath -Head 10 -ErrorAction SilentlyContinue -WarningAction SilentlyContinue | Out-String $regexResult = [regex]::Match($libHeader, '^[\s|\t]*#_version:[\s|\t]*(?<version>[\w|.]+)', [Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [Text.RegularExpressions.RegexOptions]::Multiline) return [PSCustomObject]@{ versionStr = $regexResult[0].Groups['version'].value version = [float][regex]::Replace($regexResult[0].Groups['version'].value, '^(?:[a-z]|[A-Z])*|(?<!^\w+)\.', '') train = [string][regex]::Match($regexResult[0].Groups['version'].value, '((?:[a-z]|[A-Z])+)', [Text.RegularExpressions.RegexOptions]::IgnoreCase) } } } return $null } function injectEnumFromJSON{[CmdletBinding()]param([string]$jsonFile, [string]$jsonKey, [string]$enumDef = $dynamicEnumBlock) # [CmdletBinding()] is necessary to get the correct session state to inject the Enum into [int]$v = 4001 [scriptblock]$unboundCode = {param([string]$enumdef); Add-Type -TypeDefinition $enumdef}.Ast.GetScriptBlock() # This section of code runs in the caller's session, not the module's session [PSCustomObject]$jsonFileData = Get-Content $jsonfile | ConvertFrom-Json if (($jsonFileData.$jsonKey).count -lt 1) {return -5} foreach ($item in $jsonFileData.$jsonKey) {$enumItems += "@$($item) = $($v),`n"; $v ++} $enumDef = $enumDef` -replace '%%ENUM_NAME%%', $jsonKey` -replace '%%ENUM_DATA%%', $enumItems try { $ExecutionContext.SessionState.InvokeCommand.InvokeScript($PSCmdlet.SessionState, $unboundCode, $enumDef) return 0 } catch { return -17 } } |