Get-PSOneToken.ps1



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
      }
    }  
  }
}