Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #requires -version 3.0
- ## ResolveAlias Module v2.0
- ########################################################################################################################
- ## Version History
- ## 1.0 - First Version. "It worked on my sample script"
- ## 1.1 - Now it parses the $(...) blocks inside strings
- ## 1.2 - Some tweaks to spacing and indenting (I really gotta get some more test case scripts)
- ## 1.3 - I went back to processing the whole script at once (instead of a line at a time)
- ## Processing a line at a time makes it impossible to handle Here-Strings...
- ## I'm considering maybe processing the tokens backwards, replacing just the tokens that need it
- ## That would mean I could get rid of all the normalizing code, and leave the whitespace as-is
- ## 1.4 - Now resolves parameters too
- ## 1.5 - Fixed several bugs with command resolution (the ? => ForEach-Object problem)
- ## - Refactored the Resolve-Line filter right out of existence
- ## - Created a test script for validation, and
- ## 1.6 - Added resolving parameter ALIASES instead of just short-forms
- ## 1.7 - Minor tweak to make it work in CTP3
- ## 2.0 - Modularized and v3 compatible
- ## 2.1 - Added options to Expand-Alias to support generating scripts from your history buffer'
- ## 2.2 - Update to PowerShell 3 -- add -AllowedModule to Resolve-Command (which)
- ## 2.3 - Update (for PowerShell 3 only)
- ## * *TODO:* Put back the -FullPath option to resolve cmdlets with their snapin path
- ## * *TODO:* Add an option to put #requires statements at the top for each snapin used
- ########################################################################################################################
- Set-StrictMode -Version latest
- function Resolve-Command {
- #.Synopsis
- # Determine which command is being referred to by the Name
- [CmdletBinding()]
- param(
- # The name of the command to be resolved
- [Parameter(Mandatory=$true)]
- [String]$Name,
- # The name(s) of the modules from which commands are allowed (defaults to modules that are already imported). Pass * to allow any commands.
- [String[]]$AllowedModule=$(@(Microsoft.PowerShell.Core\Get-Module | Select -Expand Name) + 'Microsoft.PowerShell.Core'),
- # A list of commands that are allowed even if they're not in the AllowedModule(s)
- [Parameter()]
- [string[]]$AllowedCommand
- )
- end {
- $Search = $Name -replace '(.)$','[$1]'
- # aliases, functions, cmdlets, scripts, executables, normal files
- $Commands = @(Microsoft.PowerShell.Core\Get-Command $Search -Module $AllowedModule -ErrorAction SilentlyContinue)
- if(!$Commands) {
- if($match = $AllowedCommand -match "^[^-\\]*\\*$([Regex]::Escape($Name))") {
- $OFS = ", "
- Write-Verbose "Commands is empty, but AllowedCommand ($AllowedCommand) contains $Name, so:"
- $Commands = @(Microsoft.PowerShell.Core\Get-Command $match)
- }
- }
- $cmd = $null
- if($Commands) {
- Write-Verbose "Commands $($Commands|% { $_.ModuleName + '\' + $_.Name })"
- if($Commands.Count -gt 1) {
- $cmd = @( $Commands | Where-Object { $_.Name -match "^$([Regex]::Escape($Name))" })[0]
- } else {
- $cmd = $Commands[0]
- }
- }
- if(!$cmd -and !$Search.Contains("-")) {
- $Commands = @(Microsoft.PowerShell.Core\Get-Command "Get-$Search" -ErrorAction SilentlyContinue -Module $AllowedModule | Where-Object { $_.Name -match "^Get-$([Regex]::Escape($Name))" })
- if($Commands) {
- if($Commands.Count -gt 1) {
- $cmd = @( $Commands | Where-Object { $_.Name -match "^$([Regex]::Escape($Name))" })[0]
- } else {
- $cmd = $Commands[0]
- }
- }
- }
- if(!$cmd -or $cmd.CommandType -eq "Alias") {
- if(($FullName = Microsoft.PowerShell.Utility\Get-Alias $Name -ErrorAction SilentlyContinue)) {
- if($FullName = $FullName.ResolvedCommand) {
- $cmd = Resolve-Command $FullName -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand -ErrorAction SilentlyContinue
- }
- }
- }
- if(!$cmd) {
- if($PSBoundParameters.ContainsKey("AllowedModule")) {
- Write-Warning "No command '$Name' found in the allowed modules."
- } else {
- Write-Warning "No command '$Name' found in allowed modules. Expand-Alias defaults to only loaded modules, specify -AllowedModule `"*`" to allow ANY module"
- }
- Write-Verbose "The current AllowedModules are: $($AllowedModule -join ', ')"
- }
- return $cmd
- }
- }
- function Protect-Script {
- #.Synopsis
- # Expands aliases and validates scripts, preventing embedded script and
- [CmdletBinding(ConfirmImpact="low",DefaultParameterSetName="Text")]
- param (
- # The script you want to expand aliases in
- [Parameter(Mandatory=$true, ParameterSetName="Text", Position=0)]
- [Alias("Text")]
- [string]$Script,
- # A list of modules that are allowed in the scripts we're protecting
- [Parameter(Mandatory=$true)]
- [string[]]$AllowedModule,
- # A list of commands that are allowed even if they're not in the AllowedModule(s)
- [Parameter()]
- [string[]]$AllowedCommand,
- # A list of variables that are allowed even if they're not in the AllowedModule(s)
- [Parameter()]
- [string[]]$AllowedVariable
- )
- $Script = Expand-Alias -Script:$Script -AllowedModule:$AllowedModule -AllowedCommand $AllowedCommand -AllowedVariable $AllowedVariable -WarningVariable ParseWarnings -ErrorVariable ParseErrors -ErrorAction SilentlyContinue
- foreach($e in $ParseErrors | Select-Object -Expand Exception | Select-Object -Expand Errors) {
- Write-Warning $(if($e.Extent.StartLineNumber -eq $e.Extent.EndLineNumber) {
- "{0} (Line {1}, Char {2}-{2})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndColumnNumber
- } else {
- "{0} (l{1},c{2} - l{3},c{4})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndLineNumber, $e.Extent.EndColumnNumber
- })
- }
- if(![String]::IsNullOrWhiteSpace($Script)) {
- [string[]]$Commands = $AllowedCommand + (Microsoft.PowerShell.Core\Get-Command -Module:$AllowedModule | % { "{0}\{1}" -f $_.ModuleName, $_.Name})
- [string[]]$Variables = $AllowedVariable + (Microsoft.PowerShell.Core\Get-Module $AllowedModule | Select-Object -Expand ExportedVariables | Select-Object -Expand Keys)
- try {
- [ScriptBlock]::Create($Script).CheckRestrictedLanguage($Commands, $Variables, $false)
- return $Script
- } catch [System.Management.Automation.ParseException] {
- $global:ProtectionError = $_.Exception.GetBaseException().Errors
- foreach($e in $ProtectionError) {
- Write-Warning $(if($e.Extent.StartLineNumber -eq $e.Extent.EndLineNumber) {
- "{0} (Line {1}, Char {2}-{2})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndColumnNumber
- } else {
- "{0} (l{1},c{2} - l{3},c{4})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndLineNumber, $e.Extent.EndColumnNumber
- })
- }
- } catch {
- $global:ProtectionError = $_
- Write-Warning $_
- }
- }
- }
- function Expand-Alias {
- #.Synopsis
- # Expands aliases (optionally adding the fully qualified module name) and short parameters
- #.Description
- # Expands all aliases (recursively) to actual functions/cmdlets/executables
- # Expands all short-form parameter names to their full versions
- # Works on files or strings, and can expand "inplace" on a file
- #.Example
- # Expand-Alias -Script "gcm help"
- #
- [CmdletBinding(ConfirmImpact="low",DefaultParameterSetName="Files")]
- param (
- # The script file you want to expand aliases in
- [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Files")]
- [Alias("FullName","PSChildName","PSPath")]
- [IO.FileInfo]$File,
- # Enables replacing aliases in-place in files instead of into a new file
- [Parameter(ParameterSetName="Files")]
- [Switch]$InPlace,
- # The script you want to expand aliases in
- [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Text")]
- [Alias("Text")]
- [string]$Script,
- # The History ID's of commands you want to expand (this supports generating scripts from previous commands, see examples)
- [Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$true, ParameterSetName="History")]
- [Alias("Id")]
- [Int[]]$History,
- # The count of previous commands (from get-history) to expand (see examples)
- [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="HistoryCount")]
- [Int]$Count,
- # Allows you to specify a list of modules that are allowed in the scripts we're resolving.
- # Defaults to the currently loaded modules, but specify "*" to allow ANY module.
- [string[]]$AllowedModule=$(@(Microsoft.PowerShell.Core\Get-Module | Select -Expand Name) + 'Microsoft.PowerShell.Core'),
- # A list of commands that are allowed even if they're not in the AllowedModule(s)
- [Parameter()]
- [string[]]$AllowedCommand,
- # A list of variables that are allowed even if they're not in the AllowedModule(s)
- [Parameter()]
- [string[]]$AllowedVariable,
- # Allows you to leave the namespace (module name) off of commands
- # By default Expand-Alias will expand 'gc' to 'Microsoft.PowerShell.Management\Get-Content'
- # If you specify the Unqualified flag, it will expand to just 'Get-Content' instead.
- [Parameter()]
- [Switch]$Unqualified
- )
- begin {
- Write-Debug $PSCmdlet.ParameterSetName
- }
- process {
- switch( $PSCmdlet.ParameterSetName ) {
- "Files" {
- if($File -is [System.IO.FileInfo]){
- $Script = (Get-Content $File -Delim ([char]0))
- }
- }
- "History" {
- $Script = (Get-History -Id $History | Select-Object -Expand CommandLine) -Join "`n"
- }
- "HistoryCount" {
- $Script = (Get-History -Count $Count | Select-Object -Expand CommandLine) -Join "`n"
- }
- "Text" {}
- default { throw "ParameterSet: $($PSCmdlet.ParameterSetName)" }
- }
- $ParseError = $null
- $Tokens = $null
- $AST = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$Tokens, [ref]$ParseError)
- $Global:Tokens = $Tokens
- if($ParseError) {
- foreach($PEr in $ParseError) {
- $PSCmdlet.WriteError(
- (New-Object System.Management.Automation.ErrorRecord (
- New-Object System.Management.Automation.ParseException $PEr),
- "Unexpected Exception", "InvalidResult", $_) )
- }
- Write-Warning "There was an error parsing script (See above). We cannot expand aliases until the script parses without errors."
- return
- }
- $lastCommand = $Tokens.Count + 1
- :token for($t = $Tokens.Count -1; $t -ge 0; $t--) {
- Write-Verbose "Token $t of $($Tokens.Count)"
- $token = $Tokens[$t]
- switch -Regex ($token.Kind) {
- "Generic|Identifier" {
- if($token.TokenFlags -eq 'CommandName') {
- if($lastCommand -ne $t) {
- $OFS = ", "
- Write-Verbose "Resolve-Command -Name $Token.Text -AllowedModule $AllowedModule -AllowedCommand @($AllowedCommand)"
- $Command = Resolve-Command -Name $Token.Text -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand
- if(!$Command) { return $null }
- }
- Write-Verbose "Unqualified? $Unqualified"
- if(!$Unqualified -and $Command.ModuleName) {
- $CommandName = '{0}\{1}' -f $Command.ModuleName, $Command.Name
- } else {
- $CommandName = $Command.Name
- }
- $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset)).Insert( $Token.Extent.StartOffset, $CommandName )
- }
- }
- "Parameter" {
- # Figure out which command they're talking about
- Write-Verbose "lastCommand: $lastCommand ($t)"
- if($lastCommand -ge $t) {
- for($c = $t; $c -ge 0; $c--) {
- Write-Verbose "c: $($Tokens[$c].Text) ($($Tokens[$c].Kind) and $($Tokens[$c].TokenFlags))"
- if(("Generic","Identifier" -contains $Tokens[$c].Kind) -and $Tokens[$c].TokenFlags -eq "CommandName" ) {
- Write-Verbose "Resolve-Command -Name $($Tokens[$c].Text) -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand"
- $Command = Resolve-Command -Name $Tokens[$c].Text -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand
- if($Command) {
- Write-Verbose "Command: $($Tokens[$c].Text) => $($Command.Name)"
- }
- $global:RCommand = $Command
- if(!$Command) { return $null }
- $lastCommand = $c
- break
- }
- }
- }
- $short = "^" + $token.ParameterName
- $parameters = @($Command.ParameterSets | Select-Object -ExpandProperty Parameters | Where-Object {
- $_.Name -match $short -or $_.Aliases -match $short
- } | Select-Object -Unique)
- Write-Verbose "Parameters: $($parameters | Select -Expand Name)"
- Write-Verbose "Parameters: $($Command.ParameterSets | Select-Object -ExpandProperty Parameters | Select -Expand Name) | ? Name -match $short"
- if($parameters.Count -ge 1) {
- # if("Verbose","Debug","WarningAction","WarningVariable","ErrorAction","ErrorVariable","OutVariable","OutBuffer","WhatIf","Confirm" -contains $parameters[0].Name ) {
- # $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset))
- # continue
- # }
- if($parameters[0].ParameterType -ne [System.Management.Automation.SwitchParameter]) {
- if($Tokens.Count -ge $t -and ("Parameter","Semi","NewLine" -contains $Tokens[($t+1)].Kind)) {
- ## $Tokens[($t+1)].Kind -eq "Generic" -and $Tokens[($t+1)].TokenFlags -eq 'CommandName'
- Write-Warning "No value for parameter: $($parameters[0].Name), the next token is a $($Tokens[($t+1)].Kind) (Flags: $($Tokens[($t+1)].TokenFlags))"
- $Script = ""
- break token
- }
- }
- $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset)).Insert( $Token.Extent.StartOffset, "-$($parameters[0].Name)" )
- } else {
- Write-Warning "Rejecting Non-Parameter: $($token.ParameterName)"
- # $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset))
- $Script = ""
- break token
- }
- continue
- }
- }
- }
- if($InPlace) {
- if([String]::IsNullOrWhiteSpace($Script)) {
- Write-Warning "Script is empty after Expand-Alias, File ($File) not updated"
- } else {
- Set-Content -Path $File -Value $Script
- }
- } else {
- if([String]::IsNullOrWhiteSpace($Script)) {
- return
- } else {
- return $Script
- }
- }
- }
- }
- Set-Alias Resolve-Alias Expand-Alias
- Export-ModuleMember -Function * -Alias *
Add Comment
Please, Sign In to add comment