<# .SYNOPSIS Checks PowerShell scripts for syntax errors without running a single line of them. .DESCRIPTION Uses PowerShell's own parser to read each .ps1, .psm1, or .psd1 file and report any parse errors with the file, line, and column. Nothing is executed. Handy before you commit, before you hand a script to someone else, or as a quick CI check. .PARAMETER Path One or more files or folders. Folders are searched for PowerShell files. .PARAMETER Recurse Search subfolders too. .EXAMPLE .\Test-ScriptSyntax.ps1 -Path .\scripts -Recurse .EXAMPLE Get-ChildItem *.ps1 | .\Test-ScriptSyntax.ps1 #> [CmdletBinding()] param( [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('FullName')] [string[]]$Path = '.', [switch]$Recurse ) begin { $extensions = '.ps1', '.psm1', '.psd1' $checked = 0 $failed = 0 } process { foreach ($item in $Path) { $files = if (Test-Path -LiteralPath $item -PathType Container) { Get-ChildItem -LiteralPath $item -File -Recurse:$Recurse | Where-Object { $extensions -contains $_.Extension } } else { Get-Item -LiteralPath $item } foreach ($file in $files) { $tokens = $null $errors = $null [void][System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$errors) $checked++ if ($errors.Count -eq 0) { Write-Verbose "OK $($file.FullName)" continue } $failed++ foreach ($err in $errors) { [pscustomobject]@{ File = $file.Name Line = $err.Extent.StartLineNumber Column = $err.Extent.StartColumnNumber Message = $err.Message Path = $file.FullName } } } } } end { $color = if ($failed) { 'Red' } else { 'Green' } Write-Host ("Checked {0} file(s): {1} with errors." -f $checked, $failed) -ForegroundColor $color }