diff --git a/reference/docs-conceptual/PSScriptAnalyzer/create-custom-rule.md b/reference/docs-conceptual/PSScriptAnalyzer/create-custom-rule.md new file mode 100644 index 0000000..bd64b0f --- /dev/null +++ b/reference/docs-conceptual/PSScriptAnalyzer/create-custom-rule.md @@ -0,0 +1,241 @@ +--- +description: This article provides a basic guide for creating your own customized rules. +ms.date: 03/22/2022 +title: Creating custom rules +--- +# Creating custom rules + +PSScriptAnalyzer uses the [Managed Extensibility Framework (MEF)](/dotnet/framework/mef/) to import +all rules defined in the assembly. It can also consume rules written in PowerShell scripts. + +When calling `Invoke-ScriptAnalyzer`, users can specify custom rules using the +**CustomizedRulePath** parameter. + +This article provides a basic guide for creating your own customized rules. + +## Basic requirements + +### Functions should have comment-based help + +Include the `.DESCRIPTION` field. This becomes the description for the customized rule. + +```powershell +<# +.SYNOPSIS + Name of your rule. +.DESCRIPTION + This would be the description of your rule. Please refer to Rule Documentation + for consistent rule messages. +.EXAMPLE +.INPUTS +.OUTPUTS +.NOTES +#> +``` + +### Output type should be **DiagnosticRecord** + +```powershell +[OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] +``` + +### Each function must have a Token array or an Ast parameter + +The name of the **Ast** parameter name must end with **Ast**. + +```powershell +Param +( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $testAst +) +``` + +The name of the **Token** parameter name must end with **Token**. + +```powershell +Param +( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.Token[]] + $testToken +) +``` + +### DiagnosticRecord should have the required properties + +The **DiagnosticRecord** should have at least four properties: + +- **Message** +- **Extent** +- **RuleName** +- **Severity** + +```powershell +$result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]]@{ + "Message" = "This is a sample rule" + "Extent" = $ast.Extent + "RuleName" = $PSCmdlet.MyInvocation.InvocationName + "Severity" = "Warning" +} +``` + +Since version 1.17.0, you can include a **SuggestedCorrections** property of type +**IEnumerable\**. Make sure to specify the correct type. For example: + +```powershell +[int]$startLineNumber = $ast.Extent.StartLineNumber +[int]$endLineNumber = $ast.Extent.EndLineNumber +[int]$startColumnNumber = $ast.Extent.StartColumnNumber +[int]$endColumnNumber = $ast.Extent.EndColumnNumber +[string]$correction = 'Correct text that replaces Extent text' +[string]$file = $MyInvocation.MyCommand.Definition +[string]$optionalDescription = 'Useful but optional description text' +$objParams = @{ + TypeName = 'Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.CorrectionExtent' + ArgumentList = $startLineNumber, $endLineNumber, $startColumnNumber, + $endColumnNumber, $correction, $optionalDescription +} +$correctionExtent = New-Object @objParams +$suggestedCorrections = New-Object System.Collections.ObjectModel.Collection[$($objParams.TypeName)] +$suggestedCorrections.add($correctionExtent) | Out-Null + +[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord]@{ + "Message" = "This is a rule with a suggested correction" + "Extent" = $ast.Extent + "RuleName" = $PSCmdlet.MyInvocation.InvocationName + "Severity" = "Warning" + "Severity" = "Warning" + "RuleSuppressionID" = "MyRuleSuppressionID" + "SuggestedCorrections" = $suggestedCorrections +} +``` + +### Make sure you export the function(s) + +```powershell +Export-ModuleMember -Function (FunctionName) +``` + +## Example rule function + +```powershell +<# + .SYNOPSIS + Uses #Requires -RunAsAdministrator instead of your own methods. + .DESCRIPTION + The #Requires statement prevents a script from running unless the Windows PowerShell + version, modules, snap-ins, and module and snap-in version prerequisites are met. + From Windows PowerShell 4.0, the #Requires statement let script developers require that + sessions be run with elevated user rights (run as Administrator). Script developers does + not need to write their own methods any more. To fix a violation of this rule, please + consider using #Requires -RunAsAdministrator instead of your own methods. + .EXAMPLE + Measure-RequiresRunAsAdministrator -ScriptBlockAst $ScriptBlockAst + .INPUTS + [System.Management.Automation.Language.ScriptBlockAst] + .OUTPUTS + [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]] + .NOTES + None +#> +function Measure-RequiresRunAsAdministrator +{ + [CmdletBinding()] + [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + Param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $ScriptBlockAst + ) + + Process + { + $results = @() + try + { + #region Define predicates to find ASTs. + # Finds specific method, IsInRole. + [ScriptBlock]$predicate1 = { + param ([System.Management.Automation.Language.Ast]$Ast) + [bool]$returnValue = $false + if ($Ast -is [System.Management.Automation.Language.MemberExpressionAst]) + { + [System.Management.Automation.Language.MemberExpressionAst]$meAst = $Ast + if ($meAst.Member -is [System.Management.Automation.Language.StringConstantExpressionAst]) + { + [System.Management.Automation.Language.StringConstantExpressionAst]$sceAst = $meAst.Member + if ($sceAst.Value -eq 'isinrole') + { + $returnValue = $true + } + } + } + return $returnValue + } + + # Finds specific value, [system.security.principal.windowsbuiltinrole]::administrator. + [ScriptBlock]$predicate2 = { + param ([System.Management.Automation.Language.Ast]$Ast) + [bool]$returnValue = $false + if ($Ast -is [System.Management.Automation.Language.AssignmentStatementAst]) + { + [System.Management.Automation.Language.AssignmentStatementAst]$asAst = $Ast + if ($asAst.Right.ToString() -eq '[system.security.principal.windowsbuiltinrole]::administrator') + { + $returnValue = $true + } + } + return $returnValue + } + #endregion + #region Finds ASTs that match the predicates. + + [System.Management.Automation.Language.Ast[]]$methodAst = $ScriptBlockAst.FindAll($predicate1, $true) + [System.Management.Automation.Language.Ast[]]$assignmentAst = $ScriptBlockAst.FindAll($predicate2, $true) + if ($null -ne $ScriptBlockAst.ScriptRequirements) + { + if ((!$ScriptBlockAst.ScriptRequirements.IsElevationRequired) -and + ($methodAst.Count -ne 0) -and ($assignmentAst.Count -ne 0)) + { + $result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{ + 'Message' = $Messages.MeasureRequiresRunAsAdministrator + 'Extent' = $assignmentAst.Extent + 'RuleName' = $PSCmdlet.MyInvocation.InvocationName + 'Severity' = 'Information' + } + $results += $result + } + } + else + { + if (($methodAst.Count -ne 0) -and ($assignmentAst.Count -ne 0)) + { + $result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]@{ + 'Message' = $Messages.MeasureRequiresRunAsAdministrator + 'Extent' = $assignmentAst.Extent + 'RuleName' = $PSCmdlet.MyInvocation.InvocationName + 'Severity' = 'Information' + } + $results += $result + } + } + return $results + #endregion + } + catch + { + $PSCmdlet.ThrowTerminatingError($PSItem) + } + } +} +``` + +More examples can be found in the +[CommunityAnalyzerRules](https://github.com/PowerShell/PSScriptAnalyzer/tree/master/Tests/Engine/CommunityAnalyzerRules) +folder on GitHub. diff --git a/reference/docs-conceptual/PSScriptAnalyzer/overview.md b/reference/docs-conceptual/PSScriptAnalyzer/overview.md index 032ba2e..a6664fa 100644 --- a/reference/docs-conceptual/PSScriptAnalyzer/overview.md +++ b/reference/docs-conceptual/PSScriptAnalyzer/overview.md @@ -11,336 +11,37 @@ best practices identified by PowerShell Team and the community. It generates **D (errors and warnings) to inform users about potential code defects and suggests possible solutions for improvements. -PSScriptAnalyzer is shipped with a collection of built-in rules that checks various aspects of -PowerShell code such as presence of uninitialized variables, usage of **PSCredential** type, usage -of `Invoke-Expression`, and many more. Additional functionalities such as the option to exclude or -include specific rules are also supported. +PSScriptAnalyzer ships with a collection of built-in rules that check various aspects of +PowerShell code such as: -## Supported PowerShell Versions and Platforms +- The presence of uninitialized variables +- Use of **PSCredential** type +- Use of `Invoke-Expression` +- And many more -- Windows PowerShell 3.0 or greater -- PowerShell Core 7.0.3 or greater on Windows/Linux/macOS - -## Parser Errors - -In prior versions of ScriptAnalyzer, errors found during parsing were reported as errors and -diagnostic records were not created. ScriptAnalyzer now emits parser errors as diagnostic records in -the output stream with other diagnostic records. - -```powershell -PS> Invoke-ScriptAnalyzer -ScriptDefinition '"b" = "b"; function eliminate-file () { }' - -RuleName Severity ScriptName Line Message --------- -------- ---------- ---- ------- -InvalidLeftHandSide ParseError 1 The assignment expression is not - valid. The input to an - assignment operator must be an - object that is able to accept - assignments, such as a variable - or a property. -PSUseApprovedVerbs Warning 1 The cmdlet 'eliminate-file' uses an - unapproved verb. -``` - -The RuleName is set to the `ErrorId` of the parser error. - -To suppress **ParseErrors**, do not include it as a value in the `-Severity` parameter. - -```powershell -PS> Invoke-ScriptAnalyzer -ScriptDefinition '"b" = "b"; function eliminate-file () { }' -Severity Warning - -RuleName Severity ScriptName Line Message --------- -------- ---------- ---- ------- -PSUseApprovedVerbs Warning 1 The cmdlet 'eliminate-file' uses an - unapproved verb. -``` - -## Suppressing Rules - -You can suppress a rule by decorating a script/function or script/function parameter with .NET's -[SuppressMessageAttribute](/dotnet/api/system.diagnostics.codeanalysis.suppressmessageattribute). -The constructor for `SuppressMessageAttribute` takes two parameters: a category and a check ID. Set -the `categoryID` parameter to the name of the rule you want to suppress and set the `checkID` -parameter to a null or empty string. You can optionally add a third named parameter with a -justification for suppressing the message: - -```powershell -function SuppressMe() -{ - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideCommentHelp', '', Justification='Just an example')] - param() - - Write-Verbose -Message "I'm making a difference!" - -} -``` - -All rule violations within the scope of the script/function/parameter you decorate will be -suppressed. - -To suppress a message on a specific parameter, set the `SuppressMessageAttribute`'s `CheckId` -parameter to the name of the parameter: - -```powershell -function SuppressTwoVariables() -{ - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideDefaultParameterValue', 'b')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideDefaultParameterValue', 'a')] - param([string]$a, [int]$b) - { - } -} -``` - -Use the `SuppressMessageAttribute`'s `Scope` property to limit rule suppression to functions or -classes within the attribute's scope. - -Use the value `Function` to suppress violations on all functions within the attribute's scope. Use -the value `Class` to suppress violations on all classes within the attribute's scope: - -```powershell -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideCommentHelp', '', Scope='Function')] -param( -) - -function InternalFunction -{ - param() - - Write-Verbose -Message "I am invincible!" -} -``` - -You can further restrict suppression based on a function/parameter/class/variable/object's name by -setting the `SuppressMessageAttribute's` `Target` property to a regular expression or a glob -pattern. Few examples are given below. - -Suppress `PSAvoidUsingWriteHost` rule violation in `start-bar` and `start-baz` but not in -`start-foo` and `start-bam`: - -```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Scope='Function', Target='start-ba[rz]')] -param() -function start-foo { - write-host "start-foo" -} - -function start-bar { - write-host "start-bar" -} - -function start-baz { - write-host "start-baz" -} - -function start-bam { - write-host "start-bam" -} -``` - -Suppress violations in all the functions: - -```powershell -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Scope='Function', Target='*')] -Param() -``` - -Suppress violation in `start-bar`, `start-baz` and `start-bam` but not in `start-foo`: - -```powershell -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Scope='Function', Target='start-b*')] -Param() -``` - -**Note**: Parser Errors cannot be suppressed via the `SuppressMessageAttribute` - -## Settings Support in ScriptAnalyzer - -Settings that describe ScriptAnalyzer rules to include/exclude based on `Severity` can be created -and supplied to `Invoke-ScriptAnalyzer` using the `Setting` parameter. This enables a user to create -a custom configuration for a specific environment. We support the following modes for specifying the -settings file. - -## Using parameter Settings +You can choose which rules to include or exclude for your modules and scripts. PSScriptAnalyzer also +has the ability to fix the formatting of your code. This helps you produce code that conforms to a +standard style, is easier to read, and is more maintainable. -### Built-in Presets +## Installing PSScriptAnalyzer -ScriptAnalyzer ships a set of built-in presets that can be used to analyze scripts. For example, if -the user wants to run _PowerShell Gallery_ rules on their module, then they use the following -command. +Supported PowerShell Versions and Platforms -```powershell -Invoke-ScriptAnalyzer -Path /path/to/module/ -Settings PSGallery -Recurse -``` - -Along with `PSGallery` there are a few other built-in presets, including, `DSC` and -`CodeFormatting`, that can be used. These presets can be tab completed for the `Settings` parameter. - -### Explicit - -The following example excludes two rules from the default set of rules and any rule that does not -output an Error or Warning diagnostic record. - -```powershell -# PSScriptAnalyzerSettings.psd1 -@{ - Severity=@('Error','Warning') - ExcludeRules=@('PSAvoidUsingCmdletAliases', - 'PSAvoidUsingWriteHost') -} -``` - -Then invoke that settings file when using `Invoke-ScriptAnalyzer`: - -```powershell -Invoke-ScriptAnalyzer -Path MyScript.ps1 -Settings PSScriptAnalyzerSettings.psd1 -``` - -The next example selects a few rules to execute instead of all the default rules. - -```powershell -# PSScriptAnalyzerSettings.psd1 -@{ - IncludeRules=@('PSAvoidUsingPlainTextForPassword', - 'PSAvoidUsingConvertToSecureStringWithPlainText') -} -``` - -Then invoke that settings file: - -```powershell -Invoke-ScriptAnalyzer -Path MyScript.ps1 -Settings PSScriptAnalyzerSettings.psd1 -``` - -### Implicit - -If you place a settings file named `PSScriptAnalyzerSettings.psd1` in your project root, -PSScriptAnalyzer discovers it when you pass the project root as the `Path` parameter. - -```powershell -Invoke-ScriptAnalyzer -Path "C:\path\to\project" -Recurse -``` - -Note that providing settings explicitly takes higher precedence over this implicit mode. Sample -settings files are provided in the -[Settings folder](https://github.com/PowerShell/PSScriptAnalyzer/tree/master/Engine/Settings) of the -GitHub repository. - -## Custom rules - -It is possible to provide one or more paths to custom rules in the settings file. It is important -that these paths either point to a module's folder (implicitly uses the module manifest) or to the -module's script file (.psm1). The module should export the custom rules (as functions) for them to -be available to PS Script Analyzer. +- Windows PowerShell 3.0 or greater +- PowerShell 7.0.3 or greater on Windows/Linux/macOS -In this example the property `CustomRulePath` points to two different modules. Both modules export -the rules (the functions) with the verb `Measure` so that is used for the property `IncludeRules`. +Install using PowerShellGet 2.x: ```powershell -@{ - CustomRulePath = @( - '.\output\RequiredModules\DscResource.AnalyzerRules' - '.\tests\QA\AnalyzerRules\SqlServerDsc.AnalyzerRules.psm1' - ) - - IncludeRules = @( - 'Measure-*' - ) -} +Install-Module -Name PSScriptAnalyzer -Force ``` -You can also add default rules by listing the rules in the `IncludeRules` property. When including -default rules is important that the property `IncludeDefaultRules` is set to `$true`, otherwise the -default rules will not be used. +Install using PowerShellGet 3.x: ```powershell -@{ - CustomRulePath = @( - '.\output\RequiredModules\DscResource.AnalyzerRules' - '.\tests\QA\AnalyzerRules\SqlServerDsc.AnalyzerRules.psm1' - ) - - IncludeDefaultRules = $true - - IncludeRules = @( - # Default rules - 'PSAvoidDefaultValueForMandatoryParameter' - 'PSAvoidDefaultValueSwitchParameter' - - # Custom rules - 'Measure-*' - ) -} +Install-PSResource -Name PSScriptAnalyzer -Reinstall ``` -### Using custom rules in Visual Studio Code - -It is also possible to use the custom rules that are provided in the settings file in Visual Studio -Code. This is done by adding a Visual Studio Code workspace settings file (`.vscode/settings.json`). - -```json -{ - "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1", - "powershell.scriptAnalysis.enable": true, -} -``` - -## ScriptAnalyzer as a .NET library - -ScriptAnalyzer engine and functionality can now be directly consumed as a library. - -Here are the public interfaces: - -```csharp -using Microsoft.Windows.PowerShell.ScriptAnalyzer; - -public void Initialize(System.Management.Automation.Runspaces.Runspace runspace, -Microsoft.Windows.PowerShell.ScriptAnalyzer.IOutputWriter outputWriter, -[string[] customizedRulePath = null], -[string[] includeRuleNames = null], -[string[] excludeRuleNames = null], -[string[] severity = null], -[bool suppressedOnly = false], -[string profile = null]) - -public System.Collections.Generic.IEnumerable AnalyzePath(string path, - [bool searchRecursively = false]) - -public System.Collections.Generic.IEnumerable GetRule(string[] moduleNames, string[] ruleNames) -``` - -## Violation Correction - -Some violations can be fixed by replacing the violation causing content with a suggested -alternative. You can use the `-Fix` switch to automatically apply the suggestions. Since -`Invoke-ScriptAnalyzer` implements `SupportsShouldProcess`, you can additionally use `-WhatIf` or -`-Confirm` to find out which corrections would be applied. Be sure to use source control when -applying those corrections since some changes, such as the one for `AvoidUsingPlainTextForPassword`, -might require additional script modifications that cannot be made automatically. If your scripts are -sensitive to encoding, you should also check for that because the initial encoding can not be -preserved in all cases. - -The initial motivation behind having the `SuggestedCorrections` property on the `ErrorRecord` (which -is how the `-Fix` parameter works under the hood) was to enable quick-fix like scenarios in editors -like VSCode, Sublime, etc. At present, we provide valid `SuggestedCorrection` only for the following -rules, while gradually adding this feature to more rules. - -- AvoidAlias.cs -- AvoidUsingPlainTextForPassword.cs -- MisleadingBacktick.cs -- MissingModuleManifestField.cs -- UseToExportFieldsInManifest.cs - -## Contributions are welcome - -There are many ways to contribute: - -1. Open a new bug report, feature request or just ask a question by opening a new issue in - [GitHub](https://github.com/PowerShell/PSScriptAnalyzer/issues/new/choose). -1. Participate in the discussions of - [issues](https://github.com/PowerShell/PSScriptAnalyzer/issues), - [pull requests](https://github.com/PowerShell/PSScriptAnalyzer/pulls), and test fixes and - new features. -1. Submit your own fixes or features as a pull request but please discuss it beforehand in an issue - if the change is substantial. -1. Submit test cases. +The **Force** or **Reinstall** parameters are only necessary when you have an older version of +PSScriptAnalyzer installed. These parameters also work even when you do not have a previous version +installed. diff --git a/reference/docs-conceptual/PSScriptAnalyzer/rules-recommendations.md b/reference/docs-conceptual/PSScriptAnalyzer/rules-recommendations.md new file mode 100644 index 0000000..b7e2ed4 --- /dev/null +++ b/reference/docs-conceptual/PSScriptAnalyzer/rules-recommendations.md @@ -0,0 +1,207 @@ +--- +description: This article lists best-practice recommendations and the rules associated with them. +ms.date: 03/22/2022 +title: PSScriptAnalyzer rules and recommendations +--- +# PSScriptAnalyzer rules and recommendations + +The following guidelines come from a combined effort from both the PowerShell team and the +community. The guidelines are organized by type. Within each type there is a list of rules. The +rules are grouped by the **Severity** defined in the implementation of the **PSScriptAnalyzer** +rule. The severity level labeled as 'TBD' means "To be determined". These are recommendations that +do not currently have rules defined. + +## Cmdlet Design Rules + +### Severity: Error + +No rules defined. + +### Severity: Warning + +- Use only Approved Verbs [UseApprovedVerbs](Rules/UseApprovedVerbs.md) +- Cmdlets names with unusable characters + [AvoidReservedCharInCmdlet](Rules/ReservedCmdletChar.md) +- Parameter names that cannot be used + [AvoidReservedParams](Rules/ReservedParams.md) +- Support confirmation requests + [UseShouldProcessForStateChangingFunctions](Rules/UseShouldProcessForStateChangingFunctions.md) + and + [UseShouldProcessForStateChangingFunctions](Rules/UseShouldProcessForStateChangingFunctions.md) +- Must call **ShouldProcess** when the **ShouldProcess** attribute is present and vice + versa [UseShouldProcess](Rules/ShouldProcess.md) +- Nouns should be singular + [UseSingularNouns](Rules/UseSingularNouns.md) +- Missing module manifest fields + [MissingModuleManifestField](Rules/MissingModuleManifestField.md) + - **Version** + - **Author** + - **Description** + - **LicenseUri** (for PowerShell Gallery) +- Switch parameters should not default to true + [AvoidDefaultValueSwitchParameter](Rules/AvoidDefaultValueSwitchParameter.md) + +### Severity: Information + +No rules defined. + +### Severity: TBD + +- Support **Force** parameter for interactive sessions. If your cmdlet is used interactively, always + provide a **Force** parameter to override the interactive actions, such as prompts or reading + lines of input. This is important because it allows your cmdlet to be used in non-interactive + scripts and hosts. The following methods can be implemented by an interactive host. +- Document output objects +- Module must be loadable +- No syntax errors +- Unresolved dependencies are an error +- Derive from the Cmdlet or PSCmdlet Classes +- Specify the Cmdlet Attribute +- Override an Input Processing Method +- Specify the OutputType Attribute +- Write single records to the pipeline +- Make cmdlets case-insensitive and case-preserving + +## Script Functions + +### Severity: Error + +No rules defined. + +### Severity: Warning + +- Avoid using aliases + [AvoidUsingCmdletAliases](Rules/AvoidUsingCmdletAliases.md) +- Avoid using deprecated WMI cmdlets + [AvoidUsingWMICmdlet](Rules/AvoidUsingWMICmdlet.md) +- Avoid using empty **catch** blocks [AvoidUsingEmptyCatchBlock](Rules/AvoidUsingEmptyCatchBlock.md) +- Invoke existing cmdlets with correct parameters + [UseCmdletCorrectly](Rules/UseCmdletCorrectly.md) +- Cmdlets should have **ShouldProcess**/**ShouldContinue** and **Force** parameter if using certain + system-modifying verbs (Update, Set, Remove, New): + [UseShouldProcessForStateChangingFunctions](Rules/UseShouldProcessForStateChangingFunctions.md) +- Avoid using positional parameters + [AvoidUsingPositionalParameters](Rules/AvoidUsingPositionalParameters.md) +- Avoid using global variables + [AvoidGlobalVars](Rules/AvoidGlobalVars.md) +- Declared variables should be used after their assignment + [UseDeclaredVarsMoreThanAssignments](Rules/UseDeclaredVarsMoreThanAssignments.md) +- Avoid using `Invoke-Expression` + [AvoidUsingInvokeExpression](Rules/AvoidUsingInvokeExpression.md) + +### Severity: Information + +No rules defined. + +### Severity: TBD + +- Avoid using `Clear-Host` +- Avoid using UNC file paths +- Error Handling + - Use `-ErrorAction Stop` when calling cmdlets + - Use `$ErrorActionPreference = 'Stop'/'Continue'` when calling non-cmdlets + - Avoid using flags to handle errors + - Avoid using `$?` + - Avoid testing for a null variable as an error condition + - Copy `$Error[0]` to your own variable +- Avoid using pipelines in scripts +- If a return type is declared, the cmdlet must return that type. If a type is returned, a return + type must be declared. + +## Scripting Style + +### Severity: Error + +No rules defined. + +### Severity: Warning + +- Avoid using `Write-Host` unless writing to the host is all you want to do + [AvoidUsingWriteHost](Rules/AvoidUsingWriteHost.md) + +### Severity: Information + +- Write comment-based help + [ProvideCommentHelp](Rules/ProvideCommentHelp.md) + +### Severity: TBD + +- Provide usage Examples +- Use the Notes section for details on how the tool works +- Every exported command should have help (including parameter documentation) +- Document the version of PowerShell that the script was written for +- Indent your code +- Avoid backticks + +## Script Security + +### Severity: Error + +- Avoid using plain text passwords + [AvoidUsingPlainTextForPassword](Rules/AvoidUsingPlainTextForPassword.md) +- Avoid `-Username` and `-Password` parameters (use **PSCredential** instead): + [UsePSCredentialType](Rules/UsePSCredentialType.md) +- Avoid hardcoding a `-ComputerName` parameter argument (information disclosure): + [AvoidUsingComputerNameHardcoded](Rules/AvoidUsingComputerNameHardcoded.md) +- Avoid using `ConvertTo-SecureString` with plaintext (information disclosure): + [AvoidUsingConvertToSecureStringWithPlainText](Rules/AvoidUsingConvertToSecureStringWithPlainText.md) + +### Severity: Warning + +- Avoid using `$Password = 'string'` (information disclosure). + [AvoidUsingUsernameAndPasswordParams](Rules/AvoidUsingUsernameAndPasswordParams.md) + +### Severity: Information + +No rules defined. + +### Severity: TBD + +- Avoid initializing APIKey and Credentials variables (information disclosure) + +## DSC Related Rules + +### Severity: Error + +- Use standard DSC methods + [StandardDSCFunctionsInResource](Rules/DSCStandardDSCFunctionsInResource.md) +- Use identical mandatory parameters for all DSC methods + [UseIdenticalMandatoryParametersForDSC](Rules/DSCUseIdenticalMandatoryParametersForDSC.md) +- Use identical parameters for Set and Test DSC methods + [UseIdenticalParametersForDSC](Rules/DSCUseIdenticalParametersForDSC.md) + +### Severity: Warning + +No rules defined. + +### Severity: Information + +- The following three recommendations are covered by the + [ReturnCorrectTypesForDSCFunctions](Rules/DSCReturnCorrectTypesForDSCFunctions.md) rule + - Avoid returning any object from a `Set-TargetResource` or Set (Class Based) function + - Return a Boolean value from a `Test-TargetResource` or Test (Class Based) function + - Return an object from a `Get-TargetResource` or Get (Class Based) function +- DSC resources should have DSC tests [DSCTestsPresent](Rules/DSCDscTestsPresent.md) +- DSC resources should have DSC examples [DSCExamplesPresent](Rules/DSCDscExamplesPresent.md) + +### Severity: TBD + +- For Windows PowerShell v4, resource modules should have a `.psd1` file and `schema.mof` for every + resource +- MOFs should have a description for each element - see + [Issue #131](https://github.com/PowerShell/PSScriptAnalyzer/issues/131) +- Resource modules should have a `.psd1` file (always) and `schema.mof` (for non-class resource) see + [Issue #116](https://github.com/PowerShell/PSScriptAnalyzer/issues/116) +- Use **ShouldProcess** for a **Set** DSC method +- Resource module contains DscResources folder which contains the resources - see + [Issue #130](https://github.com/PowerShell/PSScriptAnalyzer/issues/130) + +### References + +- [Cmdlet Development Guidelines](/powershell/scripting/developer/cmdlet/cmdlet-development-guidelines) +- [PowerShell DSC Resource Design and Testing Checklist](https://devblogs.microsoft.com/powershell/powershell-dsc-resource-design-and-testing-checklist/) +- DSC Guidelines can also be found in the DSC Resources Repository + - [DSC Resource Style Guidelines & Best Practices](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md) + - [DSC Resource Naming](https://github.com/PowerShell/DscResources/blob/master/Naming.md) + - [Creating a High Quality DSC Resource Module](https://github.com/PowerShell/DscResources/blob/master/HighQualityModuleGuidelines.md) +- [The Unofficial PowerShell Best Practices and Style Guide](https://github.com/PoshCode/PowerShellPracticeAndStyle) diff --git a/reference/docs-conceptual/PSScriptAnalyzer/using-scriptanalyzer.md b/reference/docs-conceptual/PSScriptAnalyzer/using-scriptanalyzer.md new file mode 100644 index 0000000..40995f5 --- /dev/null +++ b/reference/docs-conceptual/PSScriptAnalyzer/using-scriptanalyzer.md @@ -0,0 +1,316 @@ +--- +description: This article describes various features of PSScriptAnalyzer and how to use them. +ms.date: 03/22/2022 +title: Using PSScriptAnalyzer +--- +# Using PSScriptAnalyzer + +This article describes various features of **PSScriptAnalyzer** and how to use them. + +## Parser Errors + +Beginning in version 1.18.0, **PSScriptAnalyzer** emits parser errors as diagnostic records in the +output stream. + +```powershell +Invoke-ScriptAnalyzer -ScriptDefinition '"b" = "b"; function eliminate-file () { }' +``` + +```Output +RuleName Severity ScriptName Line Message +-------- -------- ---------- ---- ------- +InvalidLeftHandSide ParseError 1 The assignment expression is not + valid. The input to an + assignment operator must be an + object that is able to accept + assignments, such as a variable + or a property. +PSUseApprovedVerbs Warning 1 The cmdlet 'eliminate-file' uses an + unapproved verb. +``` + +The **RuleName** is set to the **ErrorId** of the parser error. + +To suppress **ParseErrors**, do not include it as a value in the **Severity** parameter. + +```powershell +Invoke-ScriptAnalyzer -ScriptDefinition '"b" = "b"; function eliminate-file () { }' -Severity Warning +``` + +```Output +RuleName Severity ScriptName Line Message +-------- -------- ---------- ---- ------- +PSUseApprovedVerbs Warning 1 The cmdlet 'eliminate-file' uses an + unapproved verb. +``` + +## Suppressing Rules + +You can suppress a rule by decorating a script, function, or parameter with .NET's +[SuppressMessageAttribute](/dotnet/api/system.diagnostics.codeanalysis.suppressmessageattribute). +The constructor for **SuppressMessageAttribute** takes two parameters: a category and a check ID. Set +the **categoryID** parameter to the name of the rule you want to suppress and set the **checkID** +parameter to a null or empty string. You can optionally add a third named parameter with a +justification for suppressing the message: + +```powershell +function SuppressMe() +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideCommentHelp', '', Justification='Just an example')] + param() + + Write-Verbose -Message "I'm making a difference!" + +} +``` + +Within the scope of the script, function, or parameter that you decorated, all rule violations are +suppressed. + +To suppress a message on a specific parameter, set the **CheckId** parameter of +**SuppressMessageAttribute** to the name of the parameter: + +```powershell +function SuppressTwoVariables() +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideDefaultParameterValue', 'b')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideDefaultParameterValue', 'a')] + param([string]$a, [int]$b) + { + } +} +``` + +Use the **Scope** property of **SuppressMessageAttribute** to limit rule suppression to functions +or classes within the attribute's scope. + +Use the value **Function** to suppress violations on all functions within the attribute's scope. Use +the value **Class** to suppress violations on all classes within the attribute's scope: + +```powershell +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSProvideCommentHelp', '', Scope='Function')] +param( +) + +function InternalFunction +{ + param() + + Write-Verbose -Message "I am invincible!" +} +``` + +You can further restrict suppression based on a function, parameter, class, variable or object's +name by setting the **Target** property of **SuppressMessageAttribute** to a regular expression or +a wildcard pattern. + +For example, to suppress the **PSAvoidUsingWriteHost** rule violation in `start-bar` and +`start-baz` but not in `start-foo` and `start-bam`: + +```powershell +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Scope='Function', Target='start-ba[rz]')] +param() +function start-foo { + write-host "start-foo" +} + +function start-bar { + write-host "start-bar" +} + +function start-baz { + write-host "start-baz" +} + +function start-bam { + write-host "start-bam" +} +``` + +To suppress violations in all of the functions: + +```powershell +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Scope='Function', Target='*')] +Param() +``` + +To suppress violations in `start-bar`, `start-baz` and `start-bam` but not in `start-foo`: + +```powershell +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Scope='Function', Target='start-b*')] +Param() +``` + +> [!NOTE] +> Parser errors cannot be suppressed with **SuppressMessageAttribute**. + +## Settings Support in ScriptAnalyzer + +You can create settings that describe the ScriptAnalyzer rules to include or exclude based on +**Severity**. Use the **Settings** parameter of `Invoke-ScriptAnalyzer` to specify configuration. +This enables you to create a custom configuration for a specific environment. We support the +following modes for specifying the settings file: + +### Built-in Presets + +ScriptAnalyzer ships a set of built-in presets that can be used to analyze scripts. For example, if +you want to run _PowerShell Gallery_ rules on your module, use the following command: + +```powershell +Invoke-ScriptAnalyzer -Path /path/to/module/ -Settings PSGallery -Recurse +``` + +Additionally, you can use other built-in presets, including **DSC** and **CodeFormatting**. +These presets can be tab completed for the **Settings** parameter. + +### Explicit + +The following example excludes two rules from the default set of rules and any rule with a +severity other than **Error* and **Warning**. + +```powershell +# PSScriptAnalyzerSettings.psd1 +@{ + Severity=@('Error','Warning') + ExcludeRules=@('PSAvoidUsingCmdletAliases', + 'PSAvoidUsingWriteHost') +} +``` + +You can then invoke that settings file with `Invoke-ScriptAnalyzer`: + +```powershell +Invoke-ScriptAnalyzer -Path MyScript.ps1 -Settings PSScriptAnalyzerSettings.psd1 +``` + +The next example selects a few rules to execute instead of all the default rules. + +```powershell +# PSScriptAnalyzerSettings.psd1 +@{ + IncludeRules=@('PSAvoidUsingPlainTextForPassword', + 'PSAvoidUsingConvertToSecureStringWithPlainText') +} +``` + +You can then invoke that settings file: + +```powershell +Invoke-ScriptAnalyzer -Path MyScript.ps1 -Settings PSScriptAnalyzerSettings.psd1 +``` + +### Implicit + +If you place a settings file named `PSScriptAnalyzerSettings.psd1` in your project root, +**PSScriptAnalyzer** discovers it when you pass the project root as the **Path** parameter. + +```powershell +Invoke-ScriptAnalyzer -Path "C:\path\to\project" -Recurse +``` + +Note that providing settings explicitly takes higher precedence over this implicit mode. Sample +settings files are provided in the `Settings` folder of the **PSScriptAnalyzer** module. + +## Custom rules + +It is possible to provide one or more paths to custom rules in the settings file. It is important +that these paths point either to a module's folder, which implicitly uses the module manifest, or to +the module's script file (`.psm1`). The module must export the custom rule functions using +`Export-ModuleMember` for them to be available to **PSScriptAnalyzer**. + +In this example the property **CustomRulePath** points to two different modules. Both modules +export the rule functions with the verb **Measure** so `Measure-*` is used for the property +**IncludeRules**. + +```powershell +@{ + CustomRulePath = @( + '.\output\RequiredModules\DscResource.AnalyzerRules' + '.\tests\QA\AnalyzerRules\SqlServerDsc.AnalyzerRules.psm1' + ) + + IncludeRules = @( + 'Measure-*' + ) +} +``` + +You can also add default rules by listing the rules in the **IncludeRules** property. When including +default rules, it is important that you set the property **IncludeDefaultRules** to `$true`; +otherwise the default rules are used. + +```powershell +@{ + CustomRulePath = @( + '.\output\RequiredModules\DscResource.AnalyzerRules' + '.\tests\QA\AnalyzerRules\SqlServerDsc.AnalyzerRules.psm1' + ) + + IncludeDefaultRules = $true + + IncludeRules = @( + # Default rules + 'PSAvoidDefaultValueForMandatoryParameter' + 'PSAvoidDefaultValueSwitchParameter' + + # Custom rules + 'Measure-*' + ) +} +``` + +### Using custom rules in Visual Studio Code + +It is also possible to use the custom rules that are provided in the settings file in Visual Studio +Code. This is done by adding a Visual Studio Code workspace settings file (`.vscode/settings.json`). + +```json +{ + "powershell.scriptAnalysis.settingsPath": ".vscode/analyzersettings.psd1", + "powershell.scriptAnalysis.enable": true, +} +``` + +## ScriptAnalyzer as a .NET library + +You can directly consume ScriptAnalyzer's engine and functionality as a library. + +Here are the public interfaces: + +```csharp +using Microsoft.Windows.PowerShell.ScriptAnalyzer; + +public void Initialize(System.Management.Automation.Runspaces.Runspace runspace, +Microsoft.Windows.PowerShell.ScriptAnalyzer.IOutputWriter outputWriter, +[string[] customizedRulePath = null], +[string[] includeRuleNames = null], +[string[] excludeRuleNames = null], +[string[] severity = null], +[bool suppressedOnly = false], +[string profile = null]) + +public System.Collections.Generic.IEnumerable AnalyzePath(string path, + [bool searchRecursively = false]) + +public System.Collections.Generic.IEnumerable GetRule(string[] moduleNames, string[] ruleNames) +``` + +## Violation Correction + +You can use the **Fix** switch to to automatically replace violation-causing content with a +suggested alternative. Additionally, because `Invoke-ScriptAnalyzer` implements +**SupportsShouldProcess**, you can use **WhatIf** or **Confirm** to find out which corrections +would be applied. You should use source control when applying corrections as some changes, such as +the one for **AvoidUsingPlainTextForPassword**, might require additional script modifications that +can't be made automatically. Because initial encoding can't always be preserved when you +automatically apply suggestions, you should check your file's encoding if your scripts depend on a +particular encoding. + +The **SuggestedCorrections** property of the error record enables quick-fix scenarios in editors +like VSCode. We provide valid **SuggestedCorrection** for the following rules: + +- **AvoidAlias** +- **AvoidUsingPlainTextForPassword** +- **MisleadingBacktick** +- **MissingModuleManifestField** +- **UseToExportFieldsInManifest** diff --git a/reference/docs-conceptual/toc.yml b/reference/docs-conceptual/toc.yml index c329d98..7488b7f 100644 --- a/reference/docs-conceptual/toc.yml +++ b/reference/docs-conceptual/toc.yml @@ -27,7 +27,13 @@ items: items: - name: Overview href: PSScriptAnalyzer/overview.md - - name: Rules + - name: Using PSScriptAnalyzer + href: PSScriptAnalyzer/using-scriptanalyzer.md + - name: Rules and recommendations + href: PSScriptAnalyzer/rules-recommendations.md + - name: Creating custom rules + href: PSScriptAnalyzer/create-custom-rule.md + - name: Rules reference items: - name: Rules overview href: PSScriptAnalyzer/Rules/README.md