Transpilers/Include.psx.ps1
<# .SYNOPSIS Includes Files .DESCRIPTION Includes Files or Functions into a Script. .Example { [Include("Invoke-PipeScript")]$null } | .>PipeScript .Example { [Include("Invoke-PipeScript")] param() } | .>PipeScript .EXAMPLE { [Include('*-*.ps1')]$psScriptRoot } | .>PipeScript .EXAMPLE { [Include('https://pssvg.start-automating.com/Examples/PowerShellChevron.svg')]$PSChevron } | .>PipeScript #> [ValidateScript({ $validating = $_ if ($validating -is [Management.Automation.Language.CommandAst]) { return $validating.CommandElements[0].Value -in 'include','includes' } })] [Alias('Includes')] param( # The File Path to Include [Parameter(Mandatory,ParameterSetName='VariableAST',Position=0)] [Parameter(ParameterSetName='CommandAst',Position=0)] [Alias('FullName','Uri','Url')] [string] $FilePath, # If set, will include the content as a byte array [switch] $AsByte, # If set, will pass thru the included item [switch] $Passthru, # The exclusion pattern to use. [Alias('ExcludePattern')] [string[]] $Exclude = '\.[^\.]+\.ps1$', # The variable that include will be applied to. # If including files with wildcards, this will be the base path. # Otherwise, this variable will be assigned to the included value. [Parameter(Mandatory,ParameterSetName='VariableAST', ValueFromPipeline)] [Management.Automation.Language.VariableExpressionast] $VariableAst, # The CommandAST. # This is provided by the transpiler when include is used as a keyword. [Parameter(Mandatory,ParameterSetName='CommandAst',ValueFromPipeline)] [Management.Automation.Language.CommandAst] $CommandAst ) process { if ($psCmdlet.ParameterSetName -eq 'CommandAst') { # Gather some information about our calling context $myParams = [Ordered]@{} + $PSBoundParameters # and attempt to parse it as a sentance (only allowing it to match this particular command) $mySentence = $commandAst.AsSentence($MyInvocation.MyCommand) $myCmd = $MyInvocation.MyCommand # Walk thru all mapped parameters in the sentence foreach ($paramName in $mySentence.Parameters.Keys) { if (-not $myParams[$paramName]) { # If the parameter was not directly supplied $myParams[$paramName] = $mySentence.Parameters[$paramName] # grab it from the sentence. foreach ($myParam in $myCmd.Parameters.Values) { if ($myParam.Aliases -contains $paramName) { # set any variables that share the name of an alias $ExecutionContext.SessionState.PSVariable.Set($myParam.Name, $mySentence.Parameters[$paramName]) } } # and set this variable for this value. $ExecutionContext.SessionState.PSVariable.Set($paramName, $mySentence.Parameters[$paramName]) } } if ($mySentence.ArgumentList -and -not $FilePath) { $FilePath = $mySentence.ArgumentList[0] } } # Determine the command we would be including (relative to the current path) $includingCommand = $ExecutionContext.SessionState.InvokeCommand.GetCommand($FilePath, 'All') if (-not $includingCommand) { # if we could not determine the command, we may need to error out. if ($FilePath -match '^https?://') { $includingUrl = $FilePath -as [uri] } else { if (-not $FilePath.Contains('*')) { Write-Error "Could not resolve $($FilePath). Must be a command, path, or URI." return } } } function IncludeFileContents { param($FilePath) process { if ($AsByte) { [ScriptBlock]::Create( "@'" + [Environment]::NewLine + [Convert]::ToBase64String( [IO.File]::ReadAllBytes($FilePath), 'InsertLineBreaks' ) + [Environment]::NewLine + "'@") } else { [ScriptBlock]::Create( "@'" + [Environment]::NewLine + ([IO.File]::ReadAllLines($FilePath) -replace "^@'", "@''" -replace "^'@", "''@" -join [Environment]::NewLine) + [Environment]::NewLine + "'@" + @' -split "[\r\n]{1,2}" -replace "^@''", "@'" -replace "^''@", "'@" -join [Environment]::NewLine '@ ) } } } $includedScript = if ($includingCommand -is [Management.Automation.CmdletInfo]) { Write-Error "Cannot Include Cmdlets" return } elseif ($includingCommand -is [Management.Automation.FunctionInfo]) { if ($VariableAst -and $VariableAst.VariablePath -notmatch '^null$') { # If we're including a function as a variable, define it as a ScriptBlock [ScriptBlock]::Create(@" { $($includingCommand.ScriptBlock) } "@) } else { # If we're including a function, define it inline [ScriptBlock]::Create(@" function $($includingCommand.Name) { $($includingCommand.ScriptBlock) }$( if ($Passthru) { [Environment]::NewLine + "`$executionContext.SessionState.InvokeCommand.GetCommand(`"$($includingCommand.Name)`",'Function')" }) "@) } } elseif ($includingCommand.ScriptBlock) { # If we're including a command with a ScriptBlock, assign it to a variable [ScriptBlock]::Create(@" `${$($includingCommand.Name)} = { $($includingCommand.ScriptBlock) }$( if ($Passthru) { [Environment]::NewLine + "`${$($includingCommand.Name)}"} ) "@) } elseif ($includingCommand.Source -match '\.ps1{0,}\.(?<ext>[^.]+$)') { $transpiledFile = Invoke-PipeScript -CommandInfo $includingCommand if (-not $transpiledFile) { Write-Error "Could not transpile $($includingCommand.Source)" return } IncludeFileContents $transpiledFile.Fullname } elseif ($includingCommand.Source -match '\.ps$') { [ScriptBlock]::Create(@" `${$($includingCommand.Name)} = { $([ScriptBlock]::Create([IO.File]::ReadAllText($includingCommand.Source)) | .>PipeScript) }$( if ($Passthru) { [Environment]::NewLine + "`${$($includingCommand.Name)}"} ) "@) } elseif ($includingCommand) { IncludeFileContents $includingCommand.Source } elseif ($includingUrl) { $webResponse = Invoke-WebRequest -Uri $includingUrl if ($webResponse.Content -is [string]) { $restResponse = Invoke-RestMethod -Uri $includingUrl if ( ( ($restResponse -is [PSObject]) -or ($restResponse -is [Object[]]) ) -and ( -not ($webResponse.Content -as [xml]) ) -and ( $restResponse -isnot [string] ) ) { [ScriptBlock]::Create((@( "@'" $($restResponse | ConvertTo-Json -Depth 100) "'@ |" " ConvertFrom-JSON" ) -join [Environment]::NewLine)) } else { if ($restResponse -is [string] -and $( $restScript = try { [scriptblock]::Create($restResponse) } catch { $null } $restScript )) { $restScript } else { [ScriptBlock]::Create((@( "$(if ($webResponse.Content -as [xml]) { '[xml]'})@'" $(if ($restResponse -is [xml]) { $restResponse.OuterXML } else { $restResponse }) "'@" ) -join [Environment]::NewLine)) } } } elseif ($webResponse.Content -is [byte[]]) { [ScriptBlock]::Create( "@'" + [Environment]::NewLine + [Convert]::ToBase64String( $webResponse.Content, 'InsertLineBreaks' ) + [Environment]::NewLine + "'@") } } if ($psCmdlet.ParameterSetName -eq 'ScriptBlock' -or $VariableAst.VariablePath -match '^null$') { if ($ScriptBlock -and $ScriptBlock.ToString().Length) { [ScriptBlock]::Create("$ScriptBlock" + [Environment]::NewLine + $includedScript) } else { $includedScript } } elseif ($VariableAst.VariablePath -and $IncludedScript) { [ScriptBlock]::Create("$($VariableAst) = $IncludedScript") } elseif ($CommandAst) { $IncludedScript } elseif ($VariableAst.VariablePath -notmatch '^null$') { [ScriptBlock]::Create(@" :ToIncludeFiles foreach (`$file in (Get-ChildItem -Path "$($VariableAst)" -Filter "$FilePath" -Recurse)) { if (`$file.Extension -ne '.ps1') { continue } # Skip if the extension is not .ps1 foreach (`$exclusion in '$($Exclude -replace "'","''" -join "','")') { if (-not `$exclusion) { continue } if (`$file.Name -match `$exclusion) { continue ToIncludeFiles # Skip excluded files } } . `$file.FullName$( if ($Passthru) { [Environment]::NewLine + (' ' * 4) + '$file'} ) } "@) } } |