Get-PSOneToken.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166


function Get-PSOneToken
{
  <#
      .SYNOPSIS
      Parses a PowerShell Script (*.ps1, *.psm1, *.psd1) and returns the token
 
      .DESCRIPTION
      Invokes the advanced PowerShell Parser and returns tokens and syntax errors
 
      .EXAMPLE
      Get-PSOneToken -Path c:\test.ps1
      Parses the content of c:\test.ps1 and returns tokens and syntax errors
 
      .EXAMPLE
      Get-ChildItem -Path $home -Recurse -Include *.ps1,*.psm1,*.psd1 -File |
      Get-PSOneToken |
      Out-GridView
 
      parses all PowerShell files found anywhere in your user profile
 
      .EXAMPLE
      Get-ChildItem -Path $home -Recurse -Include *.ps1,*.psm1,*.psd1 -File |
      Get-PSOneToken |
      Where-Object Errors
 
      parses all PowerShell files found anywhere in your user profile
      and returns only those files that contain syntax errors
 
      .LINK
      https://powershell.one/powershell-internals/parsing-and-tokenization/advanced-tokenizer
      https://github.com/TobiasPSP/Modules.PSOneTools/blob/master/PSOneTools/1.4/Get-PSOneToken.ps1
  #>


  [CmdletBinding(DefaultParameterSetName='Path')]
  param
  (
    # Path to PowerShell script file
    # can be a string or any object that has a "Path"
    # or "FullName" property:
    [String]
    [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='Path')]
    [Alias('FullName')]
    $Path,
    
    # PowerShell Code as ScriptBlock
    [ScriptBlock]
    [Parameter(Mandatory,ValueFromPipeline,ParameterSetName='ScriptBlock')]
    $ScriptBlock,
    
    
    # PowerShell Code as String
    [String]
    [Parameter(Mandatory, ValueFromPipeline,ParameterSetName='Code')]
    $Code,
    
    # the kind of token requested. If neither TokenKind nor TokenFlag is requested,
    # a full tokenization occurs
    [System.Management.Automation.Language.TokenKind[]]
    $TokenKind = $null,

    # the kind of token requested. If neither TokenKind nor TokenFlag is requested,
    # a full tokenization occurs
    [System.Management.Automation.Language.TokenFlags[]]
    $TokenFlag = $null,

    # include nested token that are contained inside
    # ExpandableString tokens
    [Switch]
    $IncludeNestedToken

  )
  
  begin
  {
    # create variables to receive tokens and syntax errors:
    $errors = 
    $tokens = $null

    # return tokens only?
    # when the user submits either one of these parameters, the return value should
    # be tokens of these kinds:
    $returnTokens = ($PSBoundParameters.ContainsKey('TokenKind')) -or 
    ($PSBoundParameters.ContainsKey('TokenFlag'))
  }
  process
  {
    # if a scriptblock was submitted, convert it to string
    if ($PSCmdlet.ParameterSetName -eq 'ScriptBlock')
    {
      $Code = $ScriptBlock.ToString()
    }

    # if a path was submitted, read code from file,
    if ($PSCmdlet.ParameterSetName -eq 'Path')
    {
      $code = Get-Content -Path $Path -Raw -Encoding Default
      $name = Split-Path -Path $Path -Leaf
      $filepath = $Path

      # parse the file:
      $ast = [System.Management.Automation.Language.Parser]::ParseFile(
        $Path, 
        [ref] $tokens, 
      [ref]$errors)
    }
    else
    {
      # else the code is already present in $Code
      $name = $Code
      $filepath = ''

      # parse the string code:
      $ast = [System.Management.Automation.Language.Parser]::ParseInput(
        $Code, 
        [ref] $tokens, 
      [ref]$errors)
    }

    if ($IncludeNestedToken)
    {
      # "unwrap" nested token
      $tokens = $tokens | Expand-PSOneToken
    }

    if ($returnTokens)
    {
      # filter token and use fast scriptblock filtering instead of Where-Object:
      $tokens |
      & { process { if ($TokenKind -eq $null -or 
          $TokenKind -contains $_.Kind) 
          { $_ }
      }} |
      & { process {
          $token = $_
          if ($TokenFlag -eq $null) { $token }
          else {
            $TokenFlag | 
            Foreach-Object { 
              if ($token.TokenFlags.HasFlag($_)) 
            { $token } } | 
            Select-Object -First 1
          }
        }
      }
            
    }
    else
    {
      # return the results as a custom object
      [PSCustomObject]@{
        Name = $name
        Path = $filepath
        Tokens = $tokens
        # "move" nested "Extent" up one level
        # so all important properties are shown immediately
        Errors = $errors | 
        Select-Object -Property Message, 
        IncompleteInput, 
        ErrorId -ExpandProperty Extent
        Ast = $ast
      }
    }  
  }
}