How to check syntax of scripts automatically?

Tagged: ,

This topic contains 8 replies, has 4 voices, and was last updated by  Stan Schwartz 2 years, 11 months ago.

  • Author
    Posts
  • #12832

    After adding functionality, I deploy my scripts to a lab. The question is about hot to struggle with typos in code.
    Modules are quite easy to test: just by ipmoing all subfolder names or *.psm1 file names. I run the check script manually, several times a day, and if modules are white or yellow on the black (cmd) they are okay. If I see something red, there is a typo in a module.

    Scripts are other beasts, it is not possible to run them or ipmo them directly. There will be errors, a lot of errors. Maybe, there is a way to filter exceptions to only those that are syntax-related?

    My code for checking modules' syntax is

    Get-ChildItem C:\Projects\product_name\PSModules | `

    ?{ 'bin' -ne $_.Name -and 'obj' -ne $_.Name -and (-not $_.Name.Contains(".")) -and 'SelfTest' -ne $_.Name } | `
    %{ try { Write-Host "loading module $($_.Name)"; ipmo $_.FullName; gmo $_.Name; } catch { Write-Host "failed to load the $($_.Name) module!"; $Error[0].CategoryInfo; if ('ParserError' -eq $Error[0].CategoryInfo) { "aaaaa!"; } } }

    How to write a similar test code to check syntax of scripts?

    UPD: fixed the code section

  • #12834

    Dave Wyatt
    Moderator

    If your syntax errors are in the PowerShell language side of things, you can just open the script in the ISE, which will highlight offending bits of code with lots of little red squiggles to call attention to the problem. You can also make use of the PSParser class to tell you if any such syntax errors exist. This example just returns a simple True or False value (True if the file contains no errors, False otherwise), but you could also enumerate the $errors collection for information about each syntax error:

    function Test-PowerShellSyntax
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true)]
            [string]
            $Path
        )
    
        $contents = Get-Content -Path $Path -ErrorAction Stop
    
        $errors = $null
        $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
    
        return ($errors.Count -eq 0)
    }
    

    Another category of error would be at the cmdlet or method level; passing in parameters that don't exist or in invalid combinations, passing bad values, etc (runtime errors instead of syntax errors, basically.) The ISE and PSParser won't help you find those; you'll have to run the code to test it, at some point.

    • #12838

      Dave Wyatt, your sample is definitely a starting point! Thank you.
      I added pipeline and reintroduced a couple of my today's typos, and this works:

      cls

      function Test-PowerShellSyntax
      {
      [CmdletBinding()]
      param (
      [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
      [string[]]
      $Path
      )

      foreach ($scriptPath in $Path) {

      Write-Host $scriptPath

      $contents = Get-Content -Path $Path -ErrorAction Stop

      $errors = $null
      $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)

      }

      return ($errors.Count -eq 0)
      }

      Get-ChildItem C:\Projects\product_name\tests\feature_tests\*.ps1 -Recurse | %{ Test-PowerShellSyntax -Path $_.FullName; }

      The next step I need to perform is to assert the wrong script (with an ordinary throw or in some other way) and add the number of line to the output.

  • #12835

    Vern Anderson
    Participant

    The new ISE in PowerShell 3 and 4 will highlight syntax errors with a little red squiggly line under the problem areas. So look out for those and use PowerShell 3 and up. 🙂

    • #12836

      I use SharpDevelop to write code and navigate among dozens of files (excellent search, replace, and other things that a standard for a good IDE).
      During a write-up of a new functionality or doing fixes it may be needed to change code in several files (it's not C#, code organization in PowerShell is not so good).
      I'm not comfortable with the idea to load all these files in ISE and check code with just eyes 🙂

  • #12837

    Dave Wyatt
    Moderator

    Fair enough. Give that Test-PowerShellSyntax function a try, and see if it meets your needs. It should work on PowerShell 2.0 or later.

  • #12839

    Dave Wyatt
    Moderator

    If you're going to use pipeline input, make sure you include a Process block, or your function will ignore all but the very last string that was piped in. (If you don't explicitly name begin / process / end blocks, the code in a function is assumed to be the End block.) You'd also need to tweak a couple of other things from the original (using Write-Output instead of return, treating errors from Get-Content as non-terminating, etc.)

    Here's how I would modify the function to work with pipeline input. Note that the True/False logic is reversed here; I made a property called "SyntaxErrorsFound" which is True if a file has a problem:

    function Test-PowerShellSyntax
    {
        [CmdletBinding()]
        param (
            [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
            [string[]]
            $Path
        )
    
        process
        {
            foreach ($scriptPath in $Path) {
                $contents = Get-Content -Path $scriptPath
    
                if ($null -eq $contents)
                {
                    continue
                }
    
                $errors = $null
                $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
    
                New-Object psobject -Property @{
                    Path = $scriptPath
                    SyntaxErrorsFound = ($errors.Count -gt 0)
                }
            }
        }
    }
    
    • #12840

      Excellent! I only added an if expression around output

      if (0 -lt $errors.Count) {
      New-Object psobject -Property @{
      Path = $scriptPath
      SyntaxErrorsFound = ($errors.Count -gt 0)
      }
      }

      and added the function to my auto-typochecker!

  • #18868

    Stan Schwartz
    Participant

    How would this work if I wanted to add it to my ISE interface? I've got the following, but it never tells me I've got an error...

    function Check-Syntax {
        $contents = $psISE.CurrentFile.Editor
        if ($null -eq $contents) {continue}
        
        $errors = $null
        $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
        
        New-Object psobject -Property @{
            Path = $scriptPath
            SyntaxErrorsFound = ($errors.Count -gt 0)
        }
    }
    
    $psISE.CurrentPowerShellTab.AddOnsMenu.Submenus.Add("Check Synatx", {Check-Syntax}, 'F7')
    
    

    Thanks in advance 🙂

You must be logged in to reply to this topic.