Transpilers/Keywords/Requires.psx.ps1
<# .SYNOPSIS requires one or more modules, variables, or types. .DESCRIPTION Requires will require on or more modules, variables, or types to exist. .EXAMPLE requires latest pipescript # will require the latest version of pipescript .EXAMPLE requires variable $pid $sid # will error, because there is no $sid #> using namespace System.Management.Automation.Language [ValidateScript({ $validateVar = $_ if ($validateVar -is [CommandAst]) { $cmdAst = $validateVar if ($cmdAst.CommandElements[0].Value -in 'require', 'requires') { return $true } } return $false })] [Reflection.AssemblyMetadata("PipeScript.Keyword",$true)] [Alias('Require')] param( # One or more required modules. [Parameter(ValueFromPipelineByPropertyName)] $Module, # If set, will require the latest version of a module. [Parameter(ValueFromPipelineByPropertyName)] [switch] $Latest, # A ModuleLoader script can be used to dynamically load unresolved modules. # This script will be passed the unloaded module as an argument, and should return a module. [Parameter(ValueFromPipelineByPropertyName)] [Alias('ModuleResolver', 'Module Loader', 'Module Resolver')] [ScriptBlock] $ModuleLoader, # One or more required types. [Parameter(ValueFromPipelineByPropertyName)] $Type, # A TypeLoader script can be used to dynamically load unresolved types. # This script will be passed the unloaded type as an argument. [Parameter(ValueFromPipelineByPropertyName)] [Alias('TypeResolver', 'Type Loader', 'Type Resolver')] [ScriptBlock] $TypeLoader, # One or more required variables. [Parameter(ValueFromPipelineByPropertyName)] [Alias('Variable')] $Variables, # A VariableLoader script can be used to dynamically load unresolved variable. # This script will be passed the unloaded variable as an argument. [Parameter(ValueFromPipelineByPropertyName)] [Alias('VariableResolver', 'Variable Loader', 'Variable Resolver')] [ScriptBlock] $VariableLoader, # The Command AST. This will be provided when using the transpiler as a keyword. [Parameter(Mandatory,ParameterSetName='CommandAST',ValueFromPipeline)] [CommandAst] $CommandAst, # The ScriptBlock. This will be provided when using the transpiler as an attribute. [Parameter(Mandatory,ParameterSetName='ScriptBlock',ValueFromPipeline)] [ScriptBlock] $ScriptBlock = {} ) process { # If we were called as a CommandAST if ($PSCmdlet.ParameterSetName -eq 'CommandAst') { # attempt to parse the command as a sentence. $mySentence = $commandAst.AsSentence($MyInvocation.MyCommand) # If the sentence had parameters if ($mySentence.Parameter.Count) { foreach ($clause in $mySentence.Clauses) { if ($clause.ParameterName) { $ExecutionContext.SessionState.PSVariable.Set($clause.ParameterName, $mySentence.Parameter[$clause.Name]) } } } # If the sentence only any remaining arguments, treat them as modules if ($mySentence.Argument) { if ($Module) { $module = @($module) + $mySentence.Argument } else { $module = $mySentence.Argument } } } #region Module Requirements $moduleRequirementScript = {} if ($Module) { $moduleRequirementsList = @(foreach ($mod in $module) { if ($mod -is [string]) { "'$($mod -replace "'","''")'" } else { "$mod" } }) -join ',' $moduleRequirementScript = [ScriptBlock]::Create( ("foreach (`$moduleRequirement in $moduleRequirementsList) { `$requireLatest = $(if ($Latest) { '$true' } else { '$false' }) `$ModuleLoader = $(if ($moduleLoader) { "{ $moduleLoader }" } else { '$null'}) " + { # If the module requirement was a string if ($moduleRequirement -is [string]) { # see if it's already loaded $foundModuleRequirement = Get-Module $moduleRequirement if (-not $foundModuleRequirement) { # If it wasn't, $foundModuleRequirement = try { # try loading it Import-Module -Name $moduleRequirement -PassThru -Global -ErrorAction 'Continue' } catch { $null } } # If we found a version but require the latest version, if ($foundModuleRequirement -and $requireLatest) { # then find if there is a more recent version. Write-Verbose "Searching for a more recent version of $($foundModuleRequirement.Name)@$($foundModuleRequirement.Version)" if (-not $script:FoundModuleVersions) { $script:FoundModuleVersions = @{} } if (-not $script:FoundModuleVersions[$foundModuleRequirement.Name]) { $script:FoundModuleVersions[$foundModuleRequirement.Name] = Find-Module -Name $foundModuleRequirement.Name } $foundModuleInGallery = $script:FoundModuleVersions[$foundModuleRequirement.Name] if ($foundModuleInGallery -and ([Version]$foundModuleInGallery.Version -gt [Version]$foundModuleRequirement.Version)) { Write-Verbose "$($foundModuleInGallery.Name)@$($foundModuleInGallery.Version)" # If there was a more recent version, unload the one we already have $foundModuleRequirement | Remove-Module # Unload the existing module $foundModuleRequirement = $null } else { Write-Verbose "$($foundModuleRequirement.Name)@$($foundModuleRequirement.Version) is the latest" } } # If we have no found the required module at this point if (-not $foundModuleRequirement) { if ($moduleLoader) { # load it using a -ModuleLoader (if provided) $foundModuleRequirement = . $moduleLoader $moduleRequirement } else { # or install it from the gallery. Install-Module -Name $moduleRequirement -Scope CurrentUser -Force -AllowClobber if ($?) { # Provided the installation worked, try importing it $foundModuleRequirement = Import-Module -Name $moduleRequirement -PassThru -Global -ErrorAction 'Continue' -Force } } } else { $foundModuleRequirement } } } + "}") ) } #endregion Module Requirements #region Variable Requirements $variableRequirementScript = {} if ($Variables) { # Translate variables into their string names. $variableRequirementsList = @(foreach ($var in $Variables) { if ($var -is [string]) { "'$($var -replace "'","''")'" } elseif ($var -is [VariableExpressionAst]) { "'$($var.variablepath.ToString() -replace "'", "''")'" } }) -join ',' $variableRequirementScript = "foreach (`$variableRequirement in $variableRequirementsList) { `$variableLoader = $(if ($VariableLoader) { "{ $variableLoader }"} else { '$null'}) " + { if (-not $ExecutionContext.SessionState.PSVariable.Get($variableRequirement)) { if ($VariableLoader) { . $VariableLoader $variableRequirement if (-not $ExecutionContext.SessionState.PSVariable.Get($variableRequirement)) { } } else { Write-Error "Missing required variable $variableRequirement" } } } + "}" $variableRequirementScript = [scriptblock]::Create($variableRequirementScript) } #endregion Variable Requirements #region Type Requirements $typeRequirementScript = {} if ($type) { $typeRequirementsList = @(foreach ($typeName in $Type) { if ($typeName -is [string]) { "'$($typeName -replace "^\[" -replace '\]$')'" } elseif ($typeName.TypeName) { "'$($typeName.TypeName.ToString())'" } }) -join ',' $typeRequirementScript = "foreach (`$typeRequirement in $typeRequirementsList) { `$typeLoader = $(if ($TypeLoader) { "{$typeLoader}" } else { '$null'}) " + { if (-not ($typeRequirement -as [type])) { if ($TypeLoader) { . $TypeLoader $typeName if (-not ($typeRequirement -as [type])) { Write-Error "Type [$typeRequirement] could not be loaded" } } else { Write-Error "Type [$typeRequirement] is not loaded" } } } + "}" $typeRequirementScript = [scriptblock]::Create($typeRequirementScript) } #endregion Type Requirements if ($PSCmdlet.ParameterSetName -eq 'CommandAst') { $moduleRequirementScript, $variableRequirementScript, $typeRequirementScript | Join-PipeScript } else { $declareInBlock = 'begin' if ($scriptBlock.Ast.dynamicParam) { $declareInBlock = 'dynamicParam' } $moduleRequirementScript = [ScriptBlock]::Create("$declareInBlock { $ModuleRequirementScript }") $variableRequirementScript = [ScriptBlock]::Create("$declareInBlock { $variableRequirementScript }") $typeRequirementScript = [ScriptBlock]::Create("$declareInBlock { $typeRequirementScript }") $ScriptBlock, $moduleRequirementScript, $variableRequirementScript, $typeRequirementScript | Join-PipeScript } } |