Guest User

Untitled

a guest
Feb 22nd, 2018
91
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.61 KB | None | 0 0
  1. #requires -version 3.0
  2. ## ResolveAlias Module v2.0
  3. ########################################################################################################################
  4. ## Version History
  5. ## 1.0 - First Version. "It worked on my sample script"
  6. ## 1.1 - Now it parses the $(...) blocks inside strings
  7. ## 1.2 - Some tweaks to spacing and indenting (I really gotta get some more test case scripts)
  8. ## 1.3 - I went back to processing the whole script at once (instead of a line at a time)
  9. ## Processing a line at a time makes it impossible to handle Here-Strings...
  10. ## I'm considering maybe processing the tokens backwards, replacing just the tokens that need it
  11. ## That would mean I could get rid of all the normalizing code, and leave the whitespace as-is
  12. ## 1.4 - Now resolves parameters too
  13. ## 1.5 - Fixed several bugs with command resolution (the ? => ForEach-Object problem)
  14. ## - Refactored the Resolve-Line filter right out of existence
  15. ## - Created a test script for validation, and
  16. ## 1.6 - Added resolving parameter ALIASES instead of just short-forms
  17. ## 1.7 - Minor tweak to make it work in CTP3
  18. ## 2.0 - Modularized and v3 compatible
  19. ## 2.1 - Added options to Expand-Alias to support generating scripts from your history buffer'
  20. ## 2.2 - Update to PowerShell 3 -- add -AllowedModule to Resolve-Command (which)
  21. ## 2.3 - Update (for PowerShell 3 only)
  22. ## * *TODO:* Put back the -FullPath option to resolve cmdlets with their snapin path
  23. ## * *TODO:* Add an option to put #requires statements at the top for each snapin used
  24. ########################################################################################################################
  25. Set-StrictMode -Version latest
  26. function Resolve-Command {
  27. #.Synopsis
  28. # Determine which command is being referred to by the Name
  29. [CmdletBinding()]
  30. param(
  31. # The name of the command to be resolved
  32. [Parameter(Mandatory=$true)]
  33. [String]$Name,
  34.  
  35. # The name(s) of the modules from which commands are allowed (defaults to modules that are already imported). Pass * to allow any commands.
  36. [String[]]$AllowedModule=$(@(Microsoft.PowerShell.Core\Get-Module | Select -Expand Name) + 'Microsoft.PowerShell.Core'),
  37.  
  38. # A list of commands that are allowed even if they're not in the AllowedModule(s)
  39. [Parameter()]
  40. [string[]]$AllowedCommand
  41. )
  42. end {
  43. $Search = $Name -replace '(.)$','[$1]'
  44. # aliases, functions, cmdlets, scripts, executables, normal files
  45. $Commands = @(Microsoft.PowerShell.Core\Get-Command $Search -Module $AllowedModule -ErrorAction SilentlyContinue)
  46. if(!$Commands) {
  47. if($match = $AllowedCommand -match "^[^-\\]*\\*$([Regex]::Escape($Name))") {
  48. $OFS = ", "
  49. Write-Verbose "Commands is empty, but AllowedCommand ($AllowedCommand) contains $Name, so:"
  50. $Commands = @(Microsoft.PowerShell.Core\Get-Command $match)
  51. }
  52. }
  53. $cmd = $null
  54.  
  55. if($Commands) {
  56. Write-Verbose "Commands $($Commands|% { $_.ModuleName + '\' + $_.Name })"
  57.  
  58. if($Commands.Count -gt 1) {
  59. $cmd = @( $Commands | Where-Object { $_.Name -match "^$([Regex]::Escape($Name))" })[0]
  60. } else {
  61. $cmd = $Commands[0]
  62. }
  63. }
  64.  
  65. if(!$cmd -and !$Search.Contains("-")) {
  66. $Commands = @(Microsoft.PowerShell.Core\Get-Command "Get-$Search" -ErrorAction SilentlyContinue -Module $AllowedModule | Where-Object { $_.Name -match "^Get-$([Regex]::Escape($Name))" })
  67. if($Commands) {
  68. if($Commands.Count -gt 1) {
  69. $cmd = @( $Commands | Where-Object { $_.Name -match "^$([Regex]::Escape($Name))" })[0]
  70. } else {
  71. $cmd = $Commands[0]
  72. }
  73. }
  74. }
  75.  
  76. if(!$cmd -or $cmd.CommandType -eq "Alias") {
  77. if(($FullName = Microsoft.PowerShell.Utility\Get-Alias $Name -ErrorAction SilentlyContinue)) {
  78. if($FullName = $FullName.ResolvedCommand) {
  79. $cmd = Resolve-Command $FullName -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand -ErrorAction SilentlyContinue
  80. }
  81. }
  82. }
  83. if(!$cmd) {
  84. if($PSBoundParameters.ContainsKey("AllowedModule")) {
  85. Write-Warning "No command '$Name' found in the allowed modules."
  86. } else {
  87. Write-Warning "No command '$Name' found in allowed modules. Expand-Alias defaults to only loaded modules, specify -AllowedModule `"*`" to allow ANY module"
  88. }
  89. Write-Verbose "The current AllowedModules are: $($AllowedModule -join ', ')"
  90. }
  91. return $cmd
  92. }
  93. }
  94.  
  95. function Protect-Script {
  96. #.Synopsis
  97. # Expands aliases and validates scripts, preventing embedded script and
  98. [CmdletBinding(ConfirmImpact="low",DefaultParameterSetName="Text")]
  99. param (
  100. # The script you want to expand aliases in
  101. [Parameter(Mandatory=$true, ParameterSetName="Text", Position=0)]
  102. [Alias("Text")]
  103. [string]$Script,
  104.  
  105. # A list of modules that are allowed in the scripts we're protecting
  106. [Parameter(Mandatory=$true)]
  107. [string[]]$AllowedModule,
  108.  
  109. # A list of commands that are allowed even if they're not in the AllowedModule(s)
  110. [Parameter()]
  111. [string[]]$AllowedCommand,
  112.  
  113. # A list of variables that are allowed even if they're not in the AllowedModule(s)
  114. [Parameter()]
  115. [string[]]$AllowedVariable
  116. )
  117.  
  118. $Script = Expand-Alias -Script:$Script -AllowedModule:$AllowedModule -AllowedCommand $AllowedCommand -AllowedVariable $AllowedVariable -WarningVariable ParseWarnings -ErrorVariable ParseErrors -ErrorAction SilentlyContinue
  119. foreach($e in $ParseErrors | Select-Object -Expand Exception | Select-Object -Expand Errors) {
  120. Write-Warning $(if($e.Extent.StartLineNumber -eq $e.Extent.EndLineNumber) {
  121. "{0} (Line {1}, Char {2}-{2})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndColumnNumber
  122. } else {
  123. "{0} (l{1},c{2} - l{3},c{4})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndLineNumber, $e.Extent.EndColumnNumber
  124. })
  125. }
  126.  
  127. if(![String]::IsNullOrWhiteSpace($Script)) {
  128.  
  129. [string[]]$Commands = $AllowedCommand + (Microsoft.PowerShell.Core\Get-Command -Module:$AllowedModule | % { "{0}\{1}" -f $_.ModuleName, $_.Name})
  130. [string[]]$Variables = $AllowedVariable + (Microsoft.PowerShell.Core\Get-Module $AllowedModule | Select-Object -Expand ExportedVariables | Select-Object -Expand Keys)
  131.  
  132. try {
  133. [ScriptBlock]::Create($Script).CheckRestrictedLanguage($Commands, $Variables, $false)
  134. return $Script
  135. } catch [System.Management.Automation.ParseException] {
  136. $global:ProtectionError = $_.Exception.GetBaseException().Errors
  137.  
  138. foreach($e in $ProtectionError) {
  139. Write-Warning $(if($e.Extent.StartLineNumber -eq $e.Extent.EndLineNumber) {
  140. "{0} (Line {1}, Char {2}-{2})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndColumnNumber
  141. } else {
  142. "{0} (l{1},c{2} - l{3},c{4})" -f $e.Message, $e.Extent.StartLineNumber, $e.Extent.StartColumnNumber, $e.Extent.EndLineNumber, $e.Extent.EndColumnNumber
  143. })
  144. }
  145. } catch {
  146. $global:ProtectionError = $_
  147. Write-Warning $_
  148. }
  149. }
  150.  
  151. }
  152.  
  153. function Expand-Alias {
  154. #.Synopsis
  155. # Expands aliases (optionally adding the fully qualified module name) and short parameters
  156. #.Description
  157. # Expands all aliases (recursively) to actual functions/cmdlets/executables
  158. # Expands all short-form parameter names to their full versions
  159. # Works on files or strings, and can expand "inplace" on a file
  160. #.Example
  161. # Expand-Alias -Script "gcm help"
  162. #
  163. [CmdletBinding(ConfirmImpact="low",DefaultParameterSetName="Files")]
  164. param (
  165. # The script file you want to expand aliases in
  166. [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Files")]
  167. [Alias("FullName","PSChildName","PSPath")]
  168. [IO.FileInfo]$File,
  169.  
  170. # Enables replacing aliases in-place in files instead of into a new file
  171. [Parameter(ParameterSetName="Files")]
  172. [Switch]$InPlace,
  173.  
  174. # The script you want to expand aliases in
  175. [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="Text")]
  176. [Alias("Text")]
  177. [string]$Script,
  178.  
  179. # The History ID's of commands you want to expand (this supports generating scripts from previous commands, see examples)
  180. [Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$true, ParameterSetName="History")]
  181. [Alias("Id")]
  182. [Int[]]$History,
  183.  
  184. # The count of previous commands (from get-history) to expand (see examples)
  185. [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="HistoryCount")]
  186. [Int]$Count,
  187.  
  188. # Allows you to specify a list of modules that are allowed in the scripts we're resolving.
  189. # Defaults to the currently loaded modules, but specify "*" to allow ANY module.
  190. [string[]]$AllowedModule=$(@(Microsoft.PowerShell.Core\Get-Module | Select -Expand Name) + 'Microsoft.PowerShell.Core'),
  191.  
  192. # A list of commands that are allowed even if they're not in the AllowedModule(s)
  193. [Parameter()]
  194. [string[]]$AllowedCommand,
  195.  
  196. # A list of variables that are allowed even if they're not in the AllowedModule(s)
  197. [Parameter()]
  198. [string[]]$AllowedVariable,
  199.  
  200. # Allows you to leave the namespace (module name) off of commands
  201. # By default Expand-Alias will expand 'gc' to 'Microsoft.PowerShell.Management\Get-Content'
  202. # If you specify the Unqualified flag, it will expand to just 'Get-Content' instead.
  203. [Parameter()]
  204. [Switch]$Unqualified
  205. )
  206. begin {
  207. Write-Debug $PSCmdlet.ParameterSetName
  208. }
  209. process {
  210.  
  211. switch( $PSCmdlet.ParameterSetName ) {
  212. "Files" {
  213. if($File -is [System.IO.FileInfo]){
  214. $Script = (Get-Content $File -Delim ([char]0))
  215. }
  216. }
  217. "History" {
  218. $Script = (Get-History -Id $History | Select-Object -Expand CommandLine) -Join "`n"
  219. }
  220. "HistoryCount" {
  221. $Script = (Get-History -Count $Count | Select-Object -Expand CommandLine) -Join "`n"
  222. }
  223. "Text" {}
  224. default { throw "ParameterSet: $($PSCmdlet.ParameterSetName)" }
  225. }
  226.  
  227. $ParseError = $null
  228. $Tokens = $null
  229. $AST = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$Tokens, [ref]$ParseError)
  230. $Global:Tokens = $Tokens
  231.  
  232. if($ParseError) {
  233. foreach($PEr in $ParseError) {
  234. $PSCmdlet.WriteError(
  235. (New-Object System.Management.Automation.ErrorRecord (
  236. New-Object System.Management.Automation.ParseException $PEr),
  237. "Unexpected Exception", "InvalidResult", $_) )
  238. }
  239. Write-Warning "There was an error parsing script (See above). We cannot expand aliases until the script parses without errors."
  240. return
  241. }
  242. $lastCommand = $Tokens.Count + 1
  243. :token for($t = $Tokens.Count -1; $t -ge 0; $t--) {
  244. Write-Verbose "Token $t of $($Tokens.Count)"
  245. $token = $Tokens[$t]
  246. switch -Regex ($token.Kind) {
  247. "Generic|Identifier" {
  248. if($token.TokenFlags -eq 'CommandName') {
  249. if($lastCommand -ne $t) {
  250. $OFS = ", "
  251. Write-Verbose "Resolve-Command -Name $Token.Text -AllowedModule $AllowedModule -AllowedCommand @($AllowedCommand)"
  252. $Command = Resolve-Command -Name $Token.Text -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand
  253. if(!$Command) { return $null }
  254. }
  255. Write-Verbose "Unqualified? $Unqualified"
  256. if(!$Unqualified -and $Command.ModuleName) {
  257. $CommandName = '{0}\{1}' -f $Command.ModuleName, $Command.Name
  258. } else {
  259. $CommandName = $Command.Name
  260. }
  261. $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset)).Insert( $Token.Extent.StartOffset, $CommandName )
  262. }
  263. }
  264. "Parameter" {
  265. # Figure out which command they're talking about
  266. Write-Verbose "lastCommand: $lastCommand ($t)"
  267. if($lastCommand -ge $t) {
  268. for($c = $t; $c -ge 0; $c--) {
  269. Write-Verbose "c: $($Tokens[$c].Text) ($($Tokens[$c].Kind) and $($Tokens[$c].TokenFlags))"
  270.  
  271. if(("Generic","Identifier" -contains $Tokens[$c].Kind) -and $Tokens[$c].TokenFlags -eq "CommandName" ) {
  272. Write-Verbose "Resolve-Command -Name $($Tokens[$c].Text) -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand"
  273. $Command = Resolve-Command -Name $Tokens[$c].Text -AllowedModule $AllowedModule -AllowedCommand $AllowedCommand
  274. if($Command) {
  275. Write-Verbose "Command: $($Tokens[$c].Text) => $($Command.Name)"
  276. }
  277.  
  278. $global:RCommand = $Command
  279. if(!$Command) { return $null }
  280. $lastCommand = $c
  281. break
  282. }
  283. }
  284. }
  285.  
  286. $short = "^" + $token.ParameterName
  287. $parameters = @($Command.ParameterSets | Select-Object -ExpandProperty Parameters | Where-Object {
  288. $_.Name -match $short -or $_.Aliases -match $short
  289. } | Select-Object -Unique)
  290.  
  291. Write-Verbose "Parameters: $($parameters | Select -Expand Name)"
  292. Write-Verbose "Parameters: $($Command.ParameterSets | Select-Object -ExpandProperty Parameters | Select -Expand Name) | ? Name -match $short"
  293. if($parameters.Count -ge 1) {
  294. # if("Verbose","Debug","WarningAction","WarningVariable","ErrorAction","ErrorVariable","OutVariable","OutBuffer","WhatIf","Confirm" -contains $parameters[0].Name ) {
  295. # $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset))
  296. # continue
  297. # }
  298. if($parameters[0].ParameterType -ne [System.Management.Automation.SwitchParameter]) {
  299. if($Tokens.Count -ge $t -and ("Parameter","Semi","NewLine" -contains $Tokens[($t+1)].Kind)) {
  300. ## $Tokens[($t+1)].Kind -eq "Generic" -and $Tokens[($t+1)].TokenFlags -eq 'CommandName'
  301. Write-Warning "No value for parameter: $($parameters[0].Name), the next token is a $($Tokens[($t+1)].Kind) (Flags: $($Tokens[($t+1)].TokenFlags))"
  302. $Script = ""
  303. break token
  304. }
  305. }
  306. $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset)).Insert( $Token.Extent.StartOffset, "-$($parameters[0].Name)" )
  307. } else {
  308. Write-Warning "Rejecting Non-Parameter: $($token.ParameterName)"
  309. # $Script = $Script.Remove( $Token.Extent.StartOffset, ($Token.Extent.EndOffset - $Token.Extent.StartOffset))
  310. $Script = ""
  311. break token
  312. }
  313. continue
  314. }
  315. }
  316. }
  317.  
  318.  
  319. if($InPlace) {
  320. if([String]::IsNullOrWhiteSpace($Script)) {
  321. Write-Warning "Script is empty after Expand-Alias, File ($File) not updated"
  322. } else {
  323. Set-Content -Path $File -Value $Script
  324. }
  325. } else {
  326. if([String]::IsNullOrWhiteSpace($Script)) {
  327. return
  328. } else {
  329. return $Script
  330. }
  331. }
  332. }
  333. }
  334.  
  335.  
  336. Set-Alias Resolve-Alias Expand-Alias
  337. Export-ModuleMember -Function * -Alias *
Add Comment
Please, Sign In to add comment