How to check syntax of scripts automatically?

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

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.

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. :slight_smile:

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 :slight_smile:

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.

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.

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

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!

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 :slight_smile: