diff --git a/.azure-pipelines-ci/ci.yaml b/.azure-pipelines-ci/ci.yaml deleted file mode 100644 index 01e893f0c..000000000 --- a/.azure-pipelines-ci/ci.yaml +++ /dev/null @@ -1,51 +0,0 @@ -variables: - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - -stages: - - stage: Build - jobs: - - job: 'Full_Build' - pool: - vmImage: windows-latest - steps: - - pwsh: | - Import-Module .\tools\appveyor.psm1 - Invoke-AppveyorInstall -SkipPesterInstallation - ./build.ps1 -Configuration 'Release' -All - ./PSCompatibilityCollector/build.ps1 -Configuration 'Release' - displayName: 'Full Build' - - pwsh: | - Write-Host "##vso[artifact.upload containerfolder=out;artifactname=out;]${env:Build_SourcesDirectory}/out" - - stage: Test - jobs: - - job: - strategy: - matrix: - Ubuntu_18_04: - vmImage: ubuntu-18.04 - Ubuntu_20_04: - vmImage: ubuntu-20.04 - mac_Latest: - vmImage: macOS-latest - Windows_Server2019_PowerShell_Core: - vmImage: windows-2019 - Windows_Server2022_PowerShell_Core: - vmImage: windows-2022 - pool: - vmImage: $[ variables['vmImage'] ] - steps: - - template: templates/test-pwsh.yaml - - job: - strategy: - matrix: - Windows_Server2019_PowerShell_5_1: - vmImage: windows-2019 - pwsh: false - Windows_Server2022_PowerShell_5_1: - vmImage: windows-2022 - pwsh: false - pool: - vmImage: $[ variables['vmImage'] ] - steps: - - template: templates/test-powershell.yaml diff --git a/.azure-pipelines-ci/templates/test-powershell.yaml b/.azure-pipelines-ci/templates/test-powershell.yaml deleted file mode 100644 index de3f30b68..000000000 --- a/.azure-pipelines-ci/templates/test-powershell.yaml +++ /dev/null @@ -1,20 +0,0 @@ -steps: -- task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact: out Folder' - inputs: - artifactName: out - targetPath: '$(Build.SourcesDirectory)/out' -- task: PowerShell@2 - displayName: 'Test' - retryCountOnTaskFailure: 2 - inputs: - targetType: inline - pwsh: false - script: | - Import-Module .\tools\appveyor.psm1 - Invoke-AppveyorTest -CheckoutPath $env:BUILD_SOURCESDIRECTORY -- task: PublishTestResults@2 - inputs: - testRunner: NUnit - testResultsFiles: 'testResults.xml' - condition: succeededOrFailed() diff --git a/.azure-pipelines-ci/templates/test-pwsh.yaml b/.azure-pipelines-ci/templates/test-pwsh.yaml deleted file mode 100644 index 152458d6c..000000000 --- a/.azure-pipelines-ci/templates/test-pwsh.yaml +++ /dev/null @@ -1,20 +0,0 @@ -steps: -- task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact: out Folder' - inputs: - artifactName: out - targetPath: '$(Build.SourcesDirectory)/out' -- task: PowerShell@2 - displayName: 'Test' - retryCountOnTaskFailure: 2 - inputs: - targetType: inline - pwsh: true - script: | - Import-Module .\tools\appveyor.psm1 - Invoke-AppveyorTest -CheckoutPath $env:BUILD_SOURCESDIRECTORY -- task: PublishTestResults@2 - inputs: - testRunner: NUnit - testResultsFiles: 'TestResults.xml' - condition: succeededOrFailed() diff --git a/.ci/releaseBuild.yml b/.ci/releaseBuild.yml deleted file mode 100644 index 9878498d9..000000000 --- a/.ci/releaseBuild.yml +++ /dev/null @@ -1,211 +0,0 @@ -# The name of the build that will be seen in mscodehub -name: PSSA-Release-$(Build.BuildId) -# how is the build triggered -# since this is a release build, no trigger as it's a manual release -trigger: none - -pr: - branches: - include: - - master - - release* - -# variables to set in the build environment -variables: - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - -# since this build relies on templates, we need access to those -# This needs a service connection in the build to work -# the *name* of the service connection must be the same as the endpoint -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - # this can be any branch of your choosing - ref: master - -# the stages in this build. There are 2 -# the assumption for script analyzer is that test is done as part of -# CI so we needn't do it here -stages: -- stage: Build - displayName: Build - pool: - name: PowerShell1ES # was Package ES CodeHub Lab E - demands: - - ImageOverride -equals PSMMS2019-Secure - jobs: - - job: Build_Job - displayName: Build Microsoft.PowerShell.ScriptAnalyzer - # note the variable reference to ESRP. - # this must be created in Project -> Pipelines -> Library -> VariableGroups - # where it describes the link to the SigningServer - variables: - - group: ESRP - steps: - - checkout: self - - # the steps for building the module go here - - pwsh: | - Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA" - try { ./build.ps1 -Configuration Release -All } catch { throw $_ } - displayName: Execute build - - # these are setting vso variables which will be persisted between stages - - pwsh: | - $signSrcPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/out" - # Set signing src path variable - $vstsCommandString = "vso[task.setvariable variable=signSrcPath]${signSrcPath}" - Write-Host "sending $vstsCommandString" - Write-Host "##$vstsCommandString" - - $signOutStep1 = "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/Step1" - $null = New-Item -ItemType Directory -Path $signOutStep1 - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutStep1]${signOutStep1}" - Write-Host "sending $vstsCommandString" - Write-Host "##$vstsCommandString" - - $signOutPath = "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/signed" - $null = New-Item -ItemType Directory -Path $signOutPath - # Set signing out path variable - $vstsCommandString = "vso[task.setvariable variable=signOutPath]${signOutPath}" - Write-Host "sending $vstsCommandString" - Write-Host "##$vstsCommandString" - - # Set path variable for guardian codesign validation - $vstsCommandString = "vso[task.setvariable variable=GDN_CODESIGN_TARGETDIRECTORY]${signOutPath}" - Write-Host "sending $vstsCommandString" - Write-Host "##$vstsCommandString" - - # Get version and create a variable - $moduleData = Import-PowerShellDataFile "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/Engine/PSScriptAnalyzer.psd1" - $moduleVersion = $moduleData.ModuleVersion - $vstsCommandString = "vso[task.setvariable variable=moduleVersion]${moduleVersion}" - Write-Host "sending $vstsCommandString" - Write-Host "##$vstsCommandString" - - - displayName: Setup variables for signing - - # checkout the Compliance repository so it can be used to do the actual signing - - checkout: ComplianceRepo - - # in script analyzer, we must sign with 2 different certs - # the normal cert for MS created items and the 3rd party cert - # this the MS authored step - # Because this needs 2 certs, we do it in 2 steps. - # the first step signs the binaries and puts them in a staging directory which - # will then be used for the second step. - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signSrcPath) - # the location to put the signed output - signOutputPath: $(signOutStep1) - # the certificate ID to use - certificateId: "CP-230012" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - newtonSoft is excluded - pattern: | - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\Microsoft*.dll - - # this is the second step of the signing. - # note that the buildOutputPath (where we get the files to sign) - # is the same as the signOutputPath in the previous step - # at the end of this step we will have all the files signed that should be - # signOutPath is the location which contains the files we will use to make the module - - template: EsrpSign.yml@ComplianceRepo - parameters: - # the folder which contains the binaries to sign - buildOutputPath: $(signOutStep1) - # the location to put the signed output - signOutputPath: $(signOutPath) - # the certificate ID to use - # we'll need to change this to the 3rd party cert id - certificateId: "CP-231522" - # use minimatch because we need to exclude the NewtonSoft assembly - useMinimatch: true - # the file pattern to use - only sign newtonsoft and pluralize - pattern: | - **/Pluralize*.dll - **/Newtonsoft*.dll - - # Create the manifest for the module - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: $(signOutPath) - Build_Repository_Uri: 'https://github.com/powershell/PSScriptAnalyzer' - - # now create the nupkg which we will use to publish the module - # to the powershell gallery (not part of this yaml) - - pwsh: | - Set-Location "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA" - ./build -BuildNupkg -CopyManifest -signed - displayName: Create nupkg for publishing - - # finally publish the parts of the build which will be used in the next stages - # if it's not published, the subsequent stages will not be able to access it. - # This is the build directory (it contains all of the dll/pdb files) - - publish: "$(Build.SourcesDirectory)/OSS_Microsoft_PSSA" - artifact: build - displayName: publish build directory - - # export the nupkg only which will be used in the release pipeline - - publish: "$(signOutPath)/PSScriptAnalyzer.$(moduleVersion).nupkg" - artifact: nupkg - displayName: Publish module nupkg - -# Now on to the compliance stage -- stage: compliance - displayName: Compliance - dependsOn: Build - jobs: - - job: Compliance_Job - pool: - name: PowerShell1ES # was Package ES CodeHub Lab E - steps: - - checkout: self - - checkout: ComplianceRepo - - download: current - artifact: build - - # use the templates in the compliance repo - # since script analyzer has modules, we're using the assembly-module-compliance template - # if you don't have assemblies, you should use script-module-compliance template - - template: assembly-module-compliance.yml@ComplianceRepo - parameters: - # component-governance - the path to sources - sourceScanPath: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA' - # binskim - this isn't recursive, so you need the path to the assemblies - AnalyzeTarget: '$(Pipeline.Workspace)\build\bin\PSV7Release\netcoreapp3.1\Microsoft.Windows.PowerShell.ScriptAnalyzer*.dll' - # credscan - scan the repo for credentials - # you can suppress some files with this. - suppressionsFile: '$(Build.SourcesDirectory)/OSS_Microsoft_PSSA/tools/ReleaseBuild/CredScan.Suppressions.json' - # TermCheck - optionsRulesDBPath: '' - optionsFTPath: '' - # tsa-upload - # the compliance scanning must be uploaded, which you need to request - codeBaseName: 'PSSA_202004' - # selections - APIScan: false # set to false when not using Windows APIs. - -#- template: template/publish.yml -# parameters: -# stageName: AzArtifactsFeed -# environmentName: -# feedCredential: - -#- template: template/publish.yml -# parameters: -# stageName: NuGet -# environmentName: PSMarkdownRenderNuGetApproval -# feedCredential: NugetOrgPush diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json new file mode 100644 index 000000000..a0887d850 --- /dev/null +++ b/.config/tsaoptions.json @@ -0,0 +1,10 @@ +{ + "instanceUrl": "https://msazure.visualstudio.com", + "projectName": "One", + "areaPath": "One\\MGMT\\Compute\\Powershell\\Powershell\\PowerShell Core", + "notificationAliases": [ + "andschwa@microsoft.com", + "slee@microsoft.com" + ], + "codebaseName": "PSSA_202403" +} diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 06cc1380a..cc17c531c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -FROM mcr.microsoft.com/dotnet/core/sdk:3.1.419 +FROM mcr.microsoft.com/dotnet/sdk:8.0 -RUN pwsh --command Install-Module platyPS,Pester -Force +RUN pwsh --command Install-Module platyPS,Pester -Force \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0ee4c2049..dbbc9c7ee 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,14 +1,16 @@ -// For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at -// https://github.com/microsoft/vscode-dev-containers/tree/master/containers/dotnetcore-3.0 +// For format details, see https://aka.ms/vscode-remote/devcontainer.json { - "name": "C# (.NET Core 3.1)", - "dockerFile": "Dockerfile", - "settings": { - "terminal.integrated.shell.linux": "/usr/bin/pwsh" - }, - "postCreateCommand": "dotnet restore", - "extensions": [ - "ms-dotnettools.csharp", - "ms-vscode.powershell-preview" - ] -} + "name": "C# (.NET 8.0)", + "dockerFile": "Dockerfile", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "pwsh" + }, + "extensions": [ + "ms-dotnettools.csharp", + "ms-vscode.powershell" + ] + } + } +} \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..8de3ce904 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Default owners +* @PowerShell/extension @bergmeister + +# Version bumps and documentation updates +Directory.Build.props @PowerShell/extension @sdwheeler @michaeltlombardi +/docs/ @PowerShell/extension @sdwheeler @michaeltlombardi diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cd548b070..f0ba62a3e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,6 @@ updates: timezone: America/Los_Angeles open-pull-requests-limit: 10 reviewers: - - "rjmholt" - "JamesWTruher" - "bergmeister" ignore: diff --git a/.github/fabricbot.json b/.github/fabricbot.json deleted file mode 100644 index fbe8db42c..000000000 --- a/.github/fabricbot.json +++ /dev/null @@ -1,711 +0,0 @@ -{ - "version": "1.0", - "tasks": [ - { - "taskType": "trigger", - "capabilityId": "IssueResponder", - "subCapability": "IssuesOnlyResponder", - "version": "1.0", - "config": { - "taskName": "Add needs triage label to new issues", - "conditions": { - "operator": "and", - "operands": [ - { - "name": "isAction", - "parameters": { - "action": "opened" - } - }, - { - "operator": "not", - "operands": [ - { - "name": "isPartOfProject", - "parameters": {} - } - ] - }, - { - "operator": "not", - "operands": [ - { - "name": "isAssignedToSomeone", - "parameters": {} - } - ] - } - ] - }, - "actions": [ - { - "name": "addLabel", - "parameters": { - "label": "Needs: Triage :mag:" - } - } - ], - "eventType": "issue", - "eventNames": [ - "issues", - "project_card" - ] - }, - "id": "gEuS61IET", - "disabled": false - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "config": { - "taskName": "Close duplicate issues", - "frequency": [ - { - "weekDay": 0, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 1, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 2, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 3, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 4, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 5, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - }, - { - "weekDay": 6, - "hours": [ - 2, - 5, - 8, - 11, - 14, - 17, - 20, - 23 - ], - "timezoneOffset": -8 - } - ], - "searchTerms": [ - { - "name": "isIssue", - "parameters": {} - }, - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution - Duplicate" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 1 - } - } - ], - "actions": [ - { - "name": "addReply", - "parameters": { - "comment": "This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes." - } - }, - { - "name": "closeIssue", - "parameters": {} - } - ] - }, - "id": "exOradLcY", - "disabled": false - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "id": "Mx1BeKlyM", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 1, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 2, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 3, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 4, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 5, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 6, - "hours": [ - 0, - 6, - 12, - 18 - ] - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution - Answered" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 7 - } - } - ], - "actions": [ - { - "name": "closeIssue", - "parameters": {} - } - ], - "taskName": "Close Answered Issues" - } - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "id": "-3BK34t2l", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 1, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 2, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 3, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 4, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 5, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 6, - "hours": [ - 0, - 6, - 12, - 18 - ] - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution - External" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 7 - } - } - ], - "taskName": "Close External Issues", - "actions": [ - { - "name": "closeIssue", - "parameters": {} - } - ] - } - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "id": "HJ_mnzjns", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 1, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 2, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 3, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 4, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 5, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 6, - "hours": [ - 0, - 6, - 12, - 18 - ] - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution - Fixed" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 7 - } - } - ], - "taskName": "Close Fixed Issues", - "actions": [ - { - "name": "closeIssue", - "parameters": {} - } - ] - } - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "id": "UXVP56PHc", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 1, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 2, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 3, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 4, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 5, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 6, - "hours": [ - 0, - 6, - 12, - 18 - ] - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Resolution - Won't Fix" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 7 - } - } - ], - "taskName": "Close Wont Fix", - "actions": [ - { - "name": "closeIssue", - "parameters": {} - } - ] - } - }, - { - "taskType": "scheduled", - "capabilityId": "ScheduledSearch", - "subCapability": "ScheduledSearch", - "version": "1.1", - "id": "eklMZQWVo", - "config": { - "frequency": [ - { - "weekDay": 0, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 1, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 2, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 3, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 4, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 5, - "hours": [ - 0, - 6, - 12, - 18 - ] - }, - { - "weekDay": 6, - "hours": [ - 0, - 6, - 12, - 18 - ] - } - ], - "searchTerms": [ - { - "name": "isOpen", - "parameters": {} - }, - { - "name": "hasLabel", - "parameters": { - "label": "Need Repro Info" - } - }, - { - "name": "noActivitySince", - "parameters": { - "days": 14 - } - }, - { - "name": "noLabel", - "parameters": { - "label": "Issue - Bug" - } - } - ], - "taskName": "Close Stale Issues ", - "actions": [ - { - "name": "addReply", - "parameters": { - "comment": "Closing due to inactivity" - } - }, - { - "name": "closeIssue", - "parameters": {} - } - ] - } - } - ], - "userGroups": [] -} diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml new file mode 100644 index 000000000..c5262aa28 --- /dev/null +++ b/.github/policies/resourceManagement.yml @@ -0,0 +1,92 @@ +id: +name: GitOps.PullRequestIssueManagement +description: GitOps.PullRequestIssueManagement primitive +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + scheduledSearches: + - description: + frequencies: + - hourly: + hour: 3 + filters: + - isIssue + - isOpen + - hasLabel: + label: Resolution - Duplicate + - noActivitySince: + days: 1 + actions: + - addReply: + reply: This issue has been marked as duplicate and has not had any activity for **1 day**. It will be closed for housekeeping purposes. + - closeIssue + - description: + frequencies: + - hourly: + hour: 6 + filters: + - isOpen + - hasLabel: + label: Resolution - Answered + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: + frequencies: + - hourly: + hour: 6 + filters: + - isOpen + - hasLabel: + label: Resolution - External + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: + frequencies: + - hourly: + hour: 6 + filters: + - isOpen + - hasLabel: + label: Resolution - Fixed + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: + frequencies: + - hourly: + hour: 6 + filters: + - isOpen + - hasLabel: + label: Resolution - Won't Fix + - noActivitySince: + days: 7 + actions: + - closeIssue + - description: + frequencies: + - hourly: + hour: 6 + filters: + - isOpen + - hasLabel: + label: Need Repro Info + - noActivitySince: + days: 14 + - isNotLabeledWith: + label: Issue - Bug + actions: + - addReply: + reply: Closing due to inactivity + - closeIssue + eventResponderTasks: [] +onFailure: +onSuccess: diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml new file mode 100644 index 000000000..5469c54a5 --- /dev/null +++ b/.github/workflows/ci-test.yml @@ -0,0 +1,83 @@ +name: CI Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + ci: + name: pester + strategy: + matrix: + os: [ windows-latest, macos-latest, ubuntu-latest ] + runs-on: ${{ matrix.os }} + env: + DOTNET_NOLOGO: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dotnet + uses: actions/setup-dotnet@v4 + with: + cache: true + cache-dependency-path: '**/*.csproj' + + - name: Install PSResources + run: ./tools/installPSResources.ps1 + shell: pwsh + + - name: Build + run: ./build.ps1 -Configuration Release -All -Verbose + shell: pwsh + + - name: Package + run: ./build.ps1 -BuildNupkg -Verbose + shell: pwsh + + - name: Test + run: ./build.ps1 -Test -Verbose + shell: pwsh + + - name: Test Windows PowerShell + if: matrix.os == 'windows-latest' + run: | + Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck + ./build.ps1 -Test -Verbose + shell: powershell + + - name: Download PowerShell install script + uses: actions/checkout@v4 + with: + repository: PowerShell/PowerShell + path: pwsh + sparse-checkout: tools/install-powershell.ps1 + sparse-checkout-cone-mode: false + + - name: Install preview + continue-on-error: true + run: ./pwsh/tools/install-powershell.ps1 -Preview -Destination ./preview + shell: pwsh + + - name: Test preview + run: | + $PwshPreview = if ($isWindows) { "./preview/pwsh.exe" } else { "./preview/pwsh" } + ./build.ps1 -Test -WithPowerShell:$PwshPreview -Verbose + shell: pwsh + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: PSScriptAnalyzer-package-${{ matrix.os }} + path: out/**/*.nupkg + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: PSScriptAnalyzer-tests-${{ matrix.os }} + path: testResults.xml diff --git a/.gitignore b/.gitignore index 92c1621bd..fdf91a4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,226 +1,5 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -build/ -bld/ -[Bb]in/ -[Oo]bj/ - -# Visual Studo 2015 cache/options directory -.vs/ - -# VSCode configuration directory -.vscode/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# .Net Core CLI -project.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opensdf -*.sdf -*.cachefile - -# Visual Studio profiler -*.psess -*.vsp -*.vspx - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding addin-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config - -# Windows Azure Build Output -csx/ -*.build.csdef - -# Windows Store app package directory -AppPackages/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -[Ss]tyle[Cc]op.* -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.pfx -*.publishsettings -node_modules/ -bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -##Our project binplace location -PSScriptAnalyzer/ - -# Vim swap files -*.swp - -# Test result file -TestResults.xml - -# PSCompatibilityCollector module -PSCompatibilityCollector/out/ - -# Folder of build module -out - -# Explicitely Include test dir -!/Tests/** +bin/ +obj/ +/module/ +/out/ +testResults.xml diff --git a/.pipelines/PSScriptAnalyzer-Official.yml b/.pipelines/PSScriptAnalyzer-Official.yml new file mode 100644 index 000000000..abea9ab3c --- /dev/null +++ b/.pipelines/PSScriptAnalyzer-Official.yml @@ -0,0 +1,174 @@ +################################################################################# +# OneBranch Pipelines # +# This pipeline was created by EasyStart from a sample located at: # +# https://aka.ms/obpipelines/easystart/samples # +# Documentation: https://aka.ms/obpipelines # +# Yaml Schema: https://aka.ms/obpipelines/yaml/schema # +# Retail Tasks: https://aka.ms/obpipelines/tasks # +# Support: https://aka.ms/onebranchsup # +################################################################################# + +trigger: +- main + +schedules: +- cron: '20 16 * * 4' + displayName: Weekly CodeQL + branches: + include: + - main + always: true + +parameters: +- name: debug + displayName: Enable debug output + type: boolean + default: false + +variables: + system.debug: ${{ parameters.debug }} + BuildConfiguration: Release + WindowsContainerImage: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest + DOTNET_NOLOGO: true + DOTNET_GENERATE_ASPNET_CERTIFICATE: false + +resources: + repositories: + - repository: templates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + # https://aka.ms/obpipelines/templates + template: v2/OneBranch.Official.CrossPlat.yml@templates + parameters: + globalSdl: # https://aka.ms/obpipelines/sdl + asyncSdl: + enabled: true + forStages: [build] + featureFlags: + EnableCDPxPAT: false + WindowsHostVersion: + Version: 2022 + Network: KS3 + release: + category: NonAzure + stages: + - stage: build + jobs: + - job: main + displayName: Build package + pool: + type: windows + variables: + ob_outputDirectory: $(Build.SourcesDirectory)/out + steps: + - pwsh: | + [xml]$xml = Get-Content ./Directory.Build.props + $version = $xml.Project.PropertyGroup.ModuleVersion + Write-Output "##vso[task.setvariable variable=version;isOutput=true]$version" + name: package + displayName: Get version from project properties + - task: onebranch.pipeline.version@1 + displayName: Set OneBranch version + inputs: + system: Custom + customVersion: $(package.version) + - task: UseDotNet@2 + displayName: Use .NET SDK + inputs: + packageType: sdk + useGlobalJson: true + - pwsh: ./tools/installPSResources.ps1 -PSRepository CFS + displayName: Install PSResources + - pwsh: ./build.ps1 -Configuration Release -All + displayName: Build + - task: onebranch.pipeline.signing@1 + displayName: Sign 1st-party files + inputs: + command: sign + signing_profile: external_distribution + search_root: $(Build.SourcesDirectory)/out + files_to_sign: | + **/Microsoft.*.dll; + **/*.psd1; + **/*.psm1; + **/*.ps1xml; + - task: onebranch.pipeline.signing@1 + displayName: Sign 3rd-party files + inputs: + command: sign + signing_profile: 135020002 + search_root: $(Build.SourcesDirectory)/out + files_to_sign: | + **/Newtonsoft.Json.dll; + **/Pluralize.NET.dll; + - pwsh: ./build.ps1 -BuildNupkg + displayName: Package module + - task: onebranch.pipeline.signing@1 + displayName: Sign NuGet package + inputs: + command: sign + signing_profile: external_distribution + search_root: $(Build.SourcesDirectory)/out + files_to_sign: | + *.nupkg + - stage: release + dependsOn: build + condition: eq(variables['Build.Reason'], 'Manual') + variables: + ob_release_environment: Production + version: $[ stageDependencies.build.main.outputs['package.version'] ] + jobs: + - job: github + displayName: Publish draft to GitHub + pool: + type: release + templateContext: + inputs: + - input: pipelineArtifact + artifactName: drop_build_main + steps: + - task: GitHubRelease@1 + displayName: Create GitHub release + inputs: + gitHubConnection: GitHub + repositoryName: PowerShell/PSScriptAnalyzer + target: main + assets: $(Pipeline.Workspace)/PSScriptAnalyzer.$(version).nupkg + tagSource: userSpecifiedTag + tag: $(version) + isDraft: true + addChangeLog: false + releaseNotesSource: inline + releaseNotesInline: "" + - job: validation + displayName: Manual validation + pool: + type: server + timeoutInMinutes: 1440 + steps: + - task: ManualValidation@0 + displayName: Wait 24 hours for validation + inputs: + notifyUsers: $(Build.RequestedForEmail) + instructions: Please validate the release and then publish it! + timeoutInMinutes: 1440 + - job: publish + dependsOn: validation + displayName: Publish to PowerShell Gallery + pool: + type: release + templateContext: + inputs: + - input: pipelineArtifact + artifactName: drop_build_main + steps: + - task: NuGetCommand@2 + displayName: Publish module to PowerShell Gallery + inputs: + command: push + packagesToPush: $(Pipeline.Workspace)/PSScriptAnalyzer.$(version).nupkg + nuGetFeedType: external + publishFeedCredentials: PowerShellGallery diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 098ed86e6..bf1b08715 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,7 +3,6 @@ // for the documentation about the extensions.json format "recommendations": [ "ms-vscode.PowerShell", - "ms-dotnettools.csharp", - "ms-vscode-remote.remote-containers" + "ms-dotnettools.csharp" ] -} \ No newline at end of file +} diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 19ff33a1f..76352e7c7 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,11 +1,184 @@ # CHANGELOG +## [1.24.0](https://github.com/PowerShell/PSScriptAnalyzer/releases/tag/1.24.0) + +### What's Changed +#### Breaking Changes + +Minimum required PowerShell version raised from 3 to 5.1 +* Drop v3 and v4 support from build by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2081 + +#### New Features + +* Add new options (enabled by default) to formatting rule `UseCorrectCasing` to also correct operators, keywords and commands - Add UseConsistentCasing by @Jaykul in https://github.com/PowerShell/PSScriptAnalyzer/pull/1704 + +#### Enhancements + +* PSAlignAssignmentStatement: Ignore hashtables with a single key-value pair by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/1986 +* Use `RequiredResource` hashtable to specify PowerShell module versions by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2053 +* Set exit code of `Invoke-ScriptAnalyzer -EnableExit` to total number of diagnostics (#2054) by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2055 +* PSAvoidAssignmentToAutomaticVariable: Ignore when a Parameter has an Attribute that contains a Variable expression by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/1988 +* Trim unnecessary trailing spaces from string resources in Strings.resx by @XPlantefeve in https://github.com/PowerShell/PSScriptAnalyzer/pull/1972 +* Do not print summary repeatedly for each logger by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2058 +* Make Settings type detection more robust by @Tadas in https://github.com/PowerShell/PSScriptAnalyzer/pull/1967 +* Add foreach Assignment to AvoidAssignmentToAutomaticVariable by @PoshAJ in https://github.com/PowerShell/PSScriptAnalyzer/pull/2021 +* Invoke-ScriptAnalyzer: Stream diagnostics instead of batching by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2062 +* Invoke-ScriptAnalyzer: Print summary only once per invocation by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2063 +* Invoke-ScriptAnalyzer: Include parse errors in reported error count by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2069 +* Add exception message for missing rules by @Tadas in https://github.com/PowerShell/PSScriptAnalyzer/pull/1968 + +#### Bug Fixes + +* Update links in module manifest by @martincostello in https://github.com/PowerShell/PSScriptAnalyzer/pull/2034 +* Fix incorrect `-ReportSummary` Pester test grouping by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2057 +* Fixed erroneous PSUseDeclaredVarsMoreThanAssignments for some globals variables by @John-Leitch in https://github.com/PowerShell/PSScriptAnalyzer/pull/2013 +* PSReservedParams: Make severity Error instead of Warning by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/1989 +* PSUseConsistentIndentation: Check indentation of lines where first token is a LParen not followed by comment or new line by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/1995 +* PSUseConsistentWhitespace: Correctly fix whitespace between command parameters when parameter value spans multiple lines by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/2064 +* PSAvoidTrailingWhitespace: Rule not applied when using formatter + single character lines with trailing whitespace are truncated by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/1993 +* PSUseConsistentWhitespace: Ignore whitespace between separator and comment by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/2065 +* PSReviewUnusedParameter false positive for ValueFromPipeline by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/2072 +* Change severity of UseCorrectCasing to be Information by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2082 + +#### Process Changes + +* Copy more files to module root by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2037 +* Upgrade to .NET 8 since .NET 6 is past EOL by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2073 +* Use -NoProfile when invoking pwsh in Pester tests by @MatejKafka in https://github.com/PowerShell/PSScriptAnalyzer/pull/2061 +* Add GitHub Actions Ubuntu's dotnet path by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2080 +* Update README.md with recent upgrade to .NET 8 by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2076 +* Update CHANGELOG.MD with 1.23.0 release notes by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2078 +* Bring back Codespaces by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2077 +* Update SMA version to 7.4.7 by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2075 +* Test PowerShell Preview in CI by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2070 +* Backport MSDocs changes by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2085 +* Document new optional parameters added to UseCorrectCasing by @bergmeister in https://github.com/PowerShell/PSScriptAnalyzer/pull/2086 + +### New Contributors +* @martincostello made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/2034 +* @MatejKafka made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/2055 +* @XPlantefeve made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/1972 +* @John-Leitch made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/2013 +* @Tadas made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/1967 +* @PoshAJ made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/2021 +* @Jaykul made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/1704 + +**Full Changelog**: https://github.com/PowerShell/PSScriptAnalyzer/compare/1.23.0...1.24.0 + +## [1.23.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.23.0) - 2024-10-09 + +### What's Changed +* Adding OneBranch pipeline YAML config file for OSS_Microsoft_PSSA-Official by @adityapatwardhan in https://github.com/PowerShell/PSScriptAnalyzer/pull/1981 +* Update format and grammar of AvoidUsingAllowUnencryptedAuthentication by @sdwheeler in https://github.com/PowerShell/PSScriptAnalyzer/pull/1974 +* Move to OneBranch Signing and SBOM generation by @TravisEz13 in https://github.com/PowerShell/PSScriptAnalyzer/pull/1982 +* Sync rule docs changes by @sdwheeler in https://github.com/PowerShell/PSScriptAnalyzer/pull/1985 +* Sync docs changes from MicrosoftDocs/PowerShell-Docs-Modules#213 by @sdwheeler in https://github.com/PowerShell/PSScriptAnalyzer/pull/1987 +* Update CHANGELOG for 1.22.0 release by @sdwheeler in https://github.com/PowerShell/PSScriptAnalyzer/pull/1990 +* Update Code of Conduct by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2002 +* Update default type definition of `RuleInfo` by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/2011 +* PSUseConsistentWhitespace: Handle redirect operators which are not in stream order by @liamjpeters in https://github.com/PowerShell/PSScriptAnalyzer/pull/2001 +* Setup GitHub Actions CI by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2018 +* Setup new OneBranch pipeline by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2027 +* Bump SMA version by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2028 +* Package updates by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2030 +* v1.23.0: Update version for new release by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2032 +* Migrate release pipeline to DeployBox by @andyleejordan in https://github.com/PowerShell/PSScriptAnalyzer/pull/2033 + +### New Contributors +* @adityapatwardhan made their first contribution in https://github.com/PowerShell/PSScriptAnalyzer/pull/1981 + +**Full Changelog**: https://github.com/PowerShell/PSScriptAnalyzer/compare/1.22.0...1.23.0 + +## [1.22.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.22.0) - 2024-03-05 + +Minimum required version when using PowerShell 7 is now `7.2.11`. + +### New Rule + +- Add AvoidUsingAllowUnencryptedAuthentication by @MJVL in (#1857) +- Add the AvoidExclaimOperator rule to warn about the use of the ! negation operator. Fixes (#1826) by + @liamjpeters in (#1922) + +### Enhancements + +- Enable suppression of PSAvoidAssignmentToAutomaticVariable for specific variable or parameter by + @fflaten in (#1896) +- Upgrade to use .NET 6 since PowerShell 7.0 is now out out of support by @bergmeister in (#1873) +- Convert UseSingularNouns to configurable rule and add Windows to allowlist by @MJVL in (#1858) +- Add ErrorView to SpecialVars.cs by @ewisniew0 in (#1865) +- Allow suppression of PSUseSingularNouns for specific function by @fflaten in (#1903) +- Adding ToString() methods to [CorrectionExtent] and [DiagnosticRecord] by @StartAutomating in (#1946) +- Add PSNativeCommandUseErrorActionPreference preference variable by @aelij in (#1954) +- AvoidUsingPositionalParameter: Check if command has parameters to avoid having az in default + CommandAllowList by @bergmeister in (#1850) +- PSReviewUnusedParameter: Add CommandsToTraverse option by @FriedrichWeinmann in (#1921) + +### Fixes + +- Prevent NullReferenceException for null analysis type. by @hubuk in (#1949) + +### Build & Test, Documentation and Maintenance + +- UseApprovedVerbs.md: Backport minor change of PR 104 in PowerShell-Docs-Modules by @bergmeister in + (#1849) +- Improve Pester bootstrap logic for CI by @bergmeister in (#1853) +- Bump .NET SDK from 3.1.419 to 3.1.424 by @bergmeister in (#1852) +- AvoidLongLines: Make internal function DiagnosticSeverity private by @bergmeister in (#1851) +- SupportsShouldProcess.md: Fix Typo - MicrosoftDocs backport of PR 121 there by @sdwheeler in (#1869) +- Minor test fix for UseCorrectCasing rule by @kilasuit in (#1885) +- Make Invoke-Formatter test case assertion fail in case of incorrect casing by @alexandear in (#1888) +- Fix `AvoidUsingDoubleQuotesForConstantString` information in overview README by @michaeltlombardi + in (#1883) +- Update dependabot reviewers to remove Rob by @fflaten in (#1897) +- Fix typo in AvoidUsingPlainTextForPassword error message: 'to' being repeated two times by + @ALiwoto in (#1902) +- CI: Use new Ubuntu 22.04 image and remove deprecated Ubuntu 18.04 by @bergmeister in (#1847) +- Change double quotes to single where possible by @sdwheeler in (#1911) +- Backport MicrosoftDocs PR 143 by @sdwheeler in (#1910) +- Fix typos in rules documentation by @sdwheeler in (#1913) +- add demand for compliance job by @TravisEz13 in (#1920) +- FabricBot: Onboarding to GitOps.ResourceManagement because of FabricBot decommissioning by + @microsoft-github-policy-service in (#1925) +- Sync changes from Docs repository by @sdwheeler in (#1929) +- Developer documentation fix and message fix of + PossibleIncorrectUsageOfRedirectionOperatorDescription by @JoelTipke in (#1928) +- Documentation corrections for AvoidUsingPositionalParameters by @ImportTaste in (#1917) +- Update minimum PowerShell Core version to 7.2.11 as 7.0 is now EOL by @bergmeister in (#1872) +- Remove dead code and simplify by @bergmeister in (#1856) +- PSReservedParams - link about_CommonParameters by @petervandivier in (#1908) +- Generate strongly typed resources as part of build by @bergmeister in (#1855) +- Bump Newtonsoft.Json to 13.0.3 by @dependabot in (#1866) +- Use latest .NET 6.0 SDK patch version and update devcontainer to use .NET 6 as well by + @bergmeister in (#1955) +- Bump Microsoft.Management.Infrastructure from 1.0.0 to 3.0.0 for PowerShell 7 only by @dependabot + in (#1947) +- Bump version from 1.21.0 to 1.22.0 by @bergmeister in (#1965) +- Remove Appveyor badge from main README by @bergmeister in (#1962) +- Do not hard code common parameters in module help test any more by @bergmeister in (#1963) + +### New Contributors + +- @fflaten made their first contribution in (#1897) +- @ALiwoto made their first contribution in (#1902) +- @microsoft-github-policy-service made their first contribution in (#1925) +- @JoelTipke made their first contribution in (#1928) +- @ImportTaste made their first contribution in (#1917) +- @liamjpeters made their first contribution in (#1922) +- @petervandivier made their first contribution in (#1908) +- @ewisniew0 made their first contribution in (#1865) +- @StartAutomating made their first contribution in (#1946) +- @aelij made their first contribution in (#1954) +- @FriedrichWeinmann made their first contribution in (#1921) + +**Full Changelog**: + ## [1.21.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.21.0) - 2022-09-27 ### New Rule - Add AvoidMultipleTypeAttributes rule (#1705) (thanks @hankyi95) -- Add the AvoidSemicolonsAsLineTerminators rule to warn about lines ending with a semicolon. Fix (#824) (#1806) (thanks @tempora-mutantur) +- Add the AvoidSemicolonsAsLineTerminators rule to warn about lines ending with a semicolon. Fix + (#824) (#1806) (thanks @tempora-mutantur) - Add AvoidUsingBrokenHashAlgorithms (#1787) (thanks @MJVL) ### Enhancements @@ -13,7 +186,8 @@ - Also return suggestion to use PSCredential for AvoidUsingPlainTextForPassword rule (#1782) (by @bergmeister) - Invoke-Formatter: Accept input from pipeline (#1763) (by @bergmeister) - Make messages of UseCorrectCasing more detailed (#1843) -- Exclude automatic variable FormatEnumerationLimit from analysis by PSAvoidGlobalVars and PSUseDeclaredVarsMoreThanAssignments (#1836) (by @bergmeister) +- Exclude automatic variable FormatEnumerationLimit from analysis by PSAvoidGlobalVars and + PSUseDeclaredVarsMoreThanAssignments (#1836) (by @bergmeister) - PSAvoidUsingPositionalParameters: Do not warn on AZ CLI (#1846) (by @bergmeister) ### Fixes @@ -59,7 +233,7 @@ - Remove Ubuntu 16.04 from test matrix (#1733) (by @rjmholt) - Use PowerShell1ES pool for official build (#1719) (by @JamesWTruher) - Update cmdlet docs for 1.20.0 (#1726) (by @sdwheeler) -- Fixes #1720 - move rule docs and update tests (#1724) (by @sdwheeler) +- Fixes (#1720) - move rule docs and update tests (#1724) (by @sdwheeler) - Update rule docs (#1711) (by @sdwheeler) ## [1.20.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.20.0) - 2021-08-20 @@ -170,7 +344,7 @@ ### Compatibility Rules -- Make CompatibilityCollector able to parse a single version String #1446 (by @bergmeister) +- Make CompatibilityCollector able to parse a single version String (#1446) (by @bergmeister) - Update compatibility profiles for PowerShell 7 (#1429) (by @rjmholt) - Ps7 syntax (#1426) (by @rjmholt) - Fix ps3 syntax check (#1395) (by @rjmholt) @@ -728,10 +902,10 @@ Here are some improvements since the last release. - Add build script to automate building and testing the solution A big **Thank You!** to the following folks for making PSScriptAnalyzer even better: -- [Kieran Jacobsen (@kjacobsen)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=kjacobsen): Fix rule documentation of `PSDSCExamplesPresent` [PR #591](https://github.com/PowerShell/PSScriptAnalyzer/pull/591) -- [Charlie Schmidt (@charlieschmidt)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=charlieschmidt): Suppress External Rules [PR #585](https://github.com/PowerShell/PSScriptAnalyzer/pull/585) +- [Kieran Jacobsen (@kjacobsen)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=kjacobsen): Fix rule documentation of `PSDSCExamplesPresent` [PR #591](#591) +- [Charlie Schmidt (@charlieschmidt)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=charlieschmidt): Suppress External Rules [PR #585](#585) - [June Blender (@juneb)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=juneb): Add tests for module help [058f65e1](https://github.com/PowerShell/PSScriptAnalyzer/commit/058f65e1f6278222378fedf444eecb2e32865b1e) -- [Shayde Nofziger (@Blackbaud-ShaydeNofziger)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=Blackbaud-ShaydeNofziger): Fix rule name typo and comment [PR #560](https://github.com/PowerShell/PSScriptAnalyzer/pull/560) +- [Shayde Nofziger (@Blackbaud-ShaydeNofziger)](https://github.com/PowerShell/PSScriptAnalyzer/commits/development?author=Blackbaud-ShaydeNofziger): Fix rule name typo and comment [PR #560](#560) ## [1.6.0](https://github.com/PowerShell/PSScriptAnalyzer/tree/1.6.0) - 2016-06-07 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..686e5e7a0 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,10 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns +- Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..f7d809c1d --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,7 @@ + + + + 1.24.0 + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..f5afcab86 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/Engine/CommandInfoCache.cs b/Engine/CommandInfoCache.cs index dbcb41eda..aa9d725f3 100644 --- a/Engine/CommandInfoCache.cs +++ b/Engine/CommandInfoCache.cs @@ -15,17 +15,15 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer internal class CommandInfoCache : IDisposable { private readonly ConcurrentDictionary> _commandInfoCache; - private readonly Helper _helperInstance; private readonly RunspacePool _runspacePool; private bool disposed = false; /// /// Create a fresh command info cache instance. /// - public CommandInfoCache(Helper pssaHelperInstance) + public CommandInfoCache() { _commandInfoCache = new ConcurrentDictionary>(); - _helperInstance = pssaHelperInstance; _runspacePool = RunspaceFactory.CreateRunspacePool(1, 10); _runspacePool.Open(); } @@ -82,16 +80,31 @@ public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes /// Returns null if command does not exists private CommandInfo GetCommandInfoInternal(string cmdName, CommandTypes? commandType) { + string moduleName = null; + string actualCmdName = cmdName; + + // Check if cmdName is in the format "moduleName\CmdletName" (exactly one backslash) + int backslashIndex = cmdName.IndexOf('\\'); + if ( + backslashIndex > 0 && + backslashIndex == cmdName.LastIndexOf('\\') && + backslashIndex != cmdName.Length - 1 && + backslashIndex != 0 + ) + { + moduleName = cmdName.Substring(0, backslashIndex); + actualCmdName = cmdName.Substring(backslashIndex + 1); + } // 'Get-Command ?' would return % for example due to PowerShell interpreting is a single-character-wildcard search and not just the ? alias. // For more details see https://github.com/PowerShell/PowerShell/issues/9308 - cmdName = WildcardPattern.Escape(cmdName); + actualCmdName = WildcardPattern.Escape(actualCmdName); using (var ps = System.Management.Automation.PowerShell.Create()) { ps.RunspacePool = _runspacePool; ps.AddCommand("Get-Command") - .AddParameter("Name", cmdName) + .AddParameter("Name", actualCmdName) .AddParameter("ErrorAction", "SilentlyContinue"); if (commandType != null) @@ -99,6 +112,11 @@ private CommandInfo GetCommandInfoInternal(string cmdName, CommandTypes? command ps.AddParameter("CommandType", commandType); } + if (!string.IsNullOrEmpty(moduleName)) + { + ps.AddParameter("Module", moduleName); + } + return ps.Invoke() .FirstOrDefault(); } diff --git a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs index 7a9d50561..3219affa7 100644 --- a/Engine/Commands/GetScriptAnalyzerRuleCommand.cs +++ b/Engine/Commands/GetScriptAnalyzerRuleCommand.cs @@ -84,13 +84,12 @@ protected override void BeginProcessing() // Initialize helper Helper.Instance = new Helper( - SessionState.InvokeCommand, - this); + SessionState.InvokeCommand); Helper.Instance.Initialize(); string[] rulePaths = Helper.ProcessCustomRulePaths(customRulePath, this.SessionState, recurseCustomRulePath); - ScriptAnalyzer.Instance.Initialize(this, rulePaths, null, null, null, null == rulePaths ? true : false); + ScriptAnalyzer.Instance.Initialize(this, rulePaths, null, null, null, null == rulePaths); } /// diff --git a/Engine/Commands/InvokeFormatterCommand.cs b/Engine/Commands/InvokeFormatterCommand.cs index d08d8c817..25a2d364e 100644 --- a/Engine/Commands/InvokeFormatterCommand.cs +++ b/Engine/Commands/InvokeFormatterCommand.cs @@ -124,11 +124,5 @@ protected override void StopProcessing() ScriptAnalyzer.Instance.CleanUp(); base.StopProcessing(); } - - private void ValidateInputSettings() - { - // todo implement this - return; - } } } diff --git a/Engine/Commands/InvokeScriptAnalyzerCommand.cs b/Engine/Commands/InvokeScriptAnalyzerCommand.cs index 77364ac9c..a444327e0 100644 --- a/Engine/Commands/InvokeScriptAnalyzerCommand.cs +++ b/Engine/Commands/InvokeScriptAnalyzerCommand.cs @@ -34,6 +34,9 @@ public class InvokeScriptAnalyzerCommand : PSCmdlet, IOutputWriter #region Private variables List processedPaths; + // initialize to zero for all severity enum values + private Dictionary diagnosticCounts = + Enum.GetValues(typeof(DiagnosticSeverity)).Cast().ToDictionary(s => s, _ => 0); #endregion // Private variables #region Parameters @@ -285,8 +288,7 @@ protected override void BeginProcessing() } #endif Helper.Instance = new Helper( - SessionState.InvokeCommand, - this); + SessionState.InvokeCommand); Helper.Instance.Initialize(); var psVersionTable = this.SessionState.PSVariable.GetValue("PSVersionTable") as Hashtable; @@ -413,6 +415,37 @@ protected override void EndProcessing() { ScriptAnalyzer.Instance.CleanUp(); base.EndProcessing(); + + var diagnosticCount = diagnosticCounts.Values.Sum(); + + if (ReportSummary.IsPresent) + { + if (diagnosticCount == 0) + { + Host.UI.WriteLine("0 rule violations found."); + } + else + { + var infoCount = diagnosticCounts[DiagnosticSeverity.Information]; + var warningCount = diagnosticCounts[DiagnosticSeverity.Warning]; + var errorCount = diagnosticCounts[DiagnosticSeverity.Error] + diagnosticCounts[DiagnosticSeverity.ParseError]; + var severeDiagnosticCount = diagnosticCount - infoCount; + + var colorPropertyPrefix = severeDiagnosticCount == 0 ? "Warning" : "Error"; + var pluralS = diagnosticCount > 1 ? "s" : string.Empty; + ConsoleHostHelper.DisplayMessageUsingSystemProperties( + Host, colorPropertyPrefix + "ForegroundColor", colorPropertyPrefix + "BackgroundColor", + $"{diagnosticCount} rule violation{pluralS} found. Severity distribution: " + + $"{DiagnosticSeverity.Error} = {errorCount}, " + + $"{DiagnosticSeverity.Warning} = {warningCount}, " + + $"{DiagnosticSeverity.Information} = {infoCount}"); + } + } + + if (EnableExit) + { + this.Host.SetShouldExit(diagnosticCount); + } } protected override void StopProcessing() @@ -427,92 +460,50 @@ protected override void StopProcessing() private void ProcessInput() { - WriteToOutput(RunAnalysis()); + foreach (var diagnostic in RunAnalysis()) + { + diagnosticCounts[diagnostic.Severity]++; + + foreach (var logger in ScriptAnalyzer.Instance.Loggers) + { + logger.LogObject(diagnostic, this); + } + } } private IEnumerable RunAnalysis() { if (!IsFileParameterSet()) { - return ScriptAnalyzer.Instance.AnalyzeScriptDefinition(scriptDefinition, out _, out _); - } - - var diagnostics = new List(); - foreach (string path in this.processedPaths) - { - if (fix) - { - ShouldProcess(path, $"Analyzing and fixing path with Recurse={this.recurse}"); - diagnostics.AddRange(ScriptAnalyzer.Instance.AnalyzeAndFixPath(path, this.ShouldProcess, this.recurse)); - } - else + foreach (var record in ScriptAnalyzer.Instance.AnalyzeScriptDefinition(scriptDefinition, out _, out _)) { - ShouldProcess(path, $"Analyzing path with Recurse={this.recurse}"); - diagnostics.AddRange(ScriptAnalyzer.Instance.AnalyzePath(path, this.ShouldProcess, this.recurse)); + yield return record; } + yield break; } - return diagnostics; - } - - private void WriteToOutput(IEnumerable diagnosticRecords) - { - foreach (ILogger logger in ScriptAnalyzer.Instance.Loggers) + foreach (var path in this.processedPaths) { - var errorCount = 0; - var warningCount = 0; - var infoCount = 0; - var parseErrorCount = 0; - - foreach (DiagnosticRecord diagnostic in diagnosticRecords) + if (!ShouldProcess(path, $"Analyzing path with Fix={this.fix} and Recurse={this.recurse}")) { - logger.LogObject(diagnostic, this); - switch (diagnostic.Severity) - { - case DiagnosticSeverity.Information: - infoCount++; - break; - case DiagnosticSeverity.Warning: - warningCount++; - break; - case DiagnosticSeverity.Error: - errorCount++; - break; - case DiagnosticSeverity.ParseError: - parseErrorCount++; - break; - default: - throw new ArgumentOutOfRangeException(nameof(diagnostic.Severity), $"Severity '{diagnostic.Severity}' is unknown"); - } + continue; } - if (ReportSummary.IsPresent) + if (fix) { - var numberOfRuleViolations = infoCount + warningCount + errorCount; - if (numberOfRuleViolations == 0) + foreach (var record in ScriptAnalyzer.Instance.AnalyzeAndFixPath(path, this.ShouldProcess, this.recurse)) { - Host.UI.WriteLine("0 rule violations found."); + yield return record; } - else + } + else + { + foreach (var record in ScriptAnalyzer.Instance.AnalyzePath(path, this.ShouldProcess, this.recurse)) { - var pluralS = numberOfRuleViolations > 1 ? "s" : string.Empty; - var message = $"{numberOfRuleViolations} rule violation{pluralS} found. Severity distribution: {DiagnosticSeverity.Error} = {errorCount}, {DiagnosticSeverity.Warning} = {warningCount}, {DiagnosticSeverity.Information} = {infoCount}"; - if (warningCount + errorCount == 0) - { - ConsoleHostHelper.DisplayMessageUsingSystemProperties(Host, "WarningForegroundColor", "WarningBackgroundColor", message); - } - else - { - ConsoleHostHelper.DisplayMessageUsingSystemProperties(Host, "ErrorForegroundColor", "ErrorBackgroundColor", message); - } + yield return record; } } } - - if (EnableExit.IsPresent) - { - this.Host.SetShouldExit(diagnosticRecords.Count()); - } } private void ProcessPath() @@ -536,4 +527,4 @@ private bool OverrideSwitchParam(bool paramValue, string paramName) #endregion // Private Methods } -} +} \ No newline at end of file diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj index 10ebf1222..63b9a1b9c 100644 --- a/Engine/Engine.csproj +++ b/Engine/Engine.csproj @@ -1,10 +1,10 @@  - 1.21.0 - netcoreapp3.1;net462 + $(ModuleVersion) + net8;net462 Microsoft.Windows.PowerShell.ScriptAnalyzer - 1.21.0 + $(ModuleVersion) Engine Microsoft.Windows.PowerShell.ScriptAnalyzer 9.0 @@ -18,11 +18,11 @@ portable - + $(DefineConstants);CORECLR - + @@ -45,57 +45,39 @@ - - - - - - True - True - Strings.resx - + + - ResXFileCodeGenerator + + MSBuild:Compile Strings.Designer.cs + $(IntermediateOutputPath)\Strings.Designer.cs + CSharp + Microsoft.Windows.PowerShell.ScriptAnalyzer + Strings - - - - - - $(DefineConstants);PSV7;CORECLR + + + PrepareResources;$(CompileDependsOn) - - - - - + + + + $(DefineConstants);PSV7;CORECLR + - - + + - - - $(DefineConstants);PSV3 - - - - $(DefineConstants);PSV3;PSV4 - - - - $(DefineConstants);PSV3 - - - - $(DefineConstants);PSV3;PSV4 - diff --git a/Engine/FindAstPositionVisitor.cs b/Engine/FindAstPositionVisitor.cs index 05532c456..459581cbc 100644 --- a/Engine/FindAstPositionVisitor.cs +++ b/Engine/FindAstPositionVisitor.cs @@ -333,7 +333,7 @@ public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinit public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { - return Visit(usingStatementAst); + return AstVisitAction.Continue; } #endif diff --git a/Engine/Formatter.cs b/Engine/Formatter.cs index 3c1325a5d..a6a25f0fb 100644 --- a/Engine/Formatter.cs +++ b/Engine/Formatter.cs @@ -32,7 +32,7 @@ public static string Format( ValidateNotNull(settings, "settings"); ValidateNotNull(cmdlet, "cmdlet"); - Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand, cmdlet); + Helper.Instance = new Helper(cmdlet.SessionState.InvokeCommand); Helper.Instance.Initialize(); var ruleOrder = new string[] @@ -46,6 +46,8 @@ public static string Format( "PSAvoidUsingCmdletAliases", "PSAvoidUsingDoubleQuotesForConstantString", "PSAvoidSemicolonsAsLineTerminators", + "PSAvoidExclaimOperator", + "PSAvoidTrailingWhitespace", }; var text = new EditableText(scriptDefinition); diff --git a/Engine/Generic/CorrectionExtent.cs b/Engine/Generic/CorrectionExtent.cs index caad49cdb..f92070844 100644 --- a/Engine/Generic/CorrectionExtent.cs +++ b/Engine/Generic/CorrectionExtent.cs @@ -104,5 +104,13 @@ public CorrectionExtent( { } + + /// + /// Outputs a CorrectionExtent as a string. + /// + /// Returns the text in a CorrectionExtent. + public override string ToString() { + return this.Text; + } } } diff --git a/Engine/Generic/DiagnosticRecord.cs b/Engine/Generic/DiagnosticRecord.cs index cd325ecf2..41eb86a05 100644 --- a/Engine/Generic/DiagnosticRecord.cs +++ b/Engine/Generic/DiagnosticRecord.cs @@ -74,7 +74,7 @@ public string ScriptPath } /// - /// Returns the rule id for this record + /// Returns the rule suppression id for this record /// public string RuleSuppressionID { @@ -88,7 +88,7 @@ public string RuleSuppressionID /// public IEnumerable SuggestedCorrections { - get { return suggestedCorrections; } + get { return suggestedCorrections; } set { suggestedCorrections = value; } } @@ -100,7 +100,7 @@ public IEnumerable SuggestedCorrections public DiagnosticRecord() { } - + /// /// DiagnosticRecord: The constructor for DiagnosticRecord class that takes in suggestedCorrection /// @@ -108,6 +108,7 @@ public DiagnosticRecord() /// The place in the script this diagnostic refers to /// The name of the rule that created this diagnostic /// The severity of this diagnostic + /// The rule suppression ID of this diagnostic /// The full path of the script file being analyzed /// The correction suggested by the rule to replace the extent text public DiagnosticRecord( @@ -127,6 +128,14 @@ public DiagnosticRecord( RuleSuppressionID = ruleId; this.suggestedCorrections = suggestedCorrections; } + + /// + /// Outputs a DiagnosticRecord as a string. + /// + /// Returns the message in a DiagnosticRecord. + public override string ToString() { + return this.Message; + } } diff --git a/Engine/Generic/ModuleDependencyHandler.cs b/Engine/Generic/ModuleDependencyHandler.cs index 347b9a9a1..31a43d6ca 100644 --- a/Engine/Generic/ModuleDependencyHandler.cs +++ b/Engine/Generic/ModuleDependencyHandler.cs @@ -22,7 +22,6 @@ public class ModuleDependencyHandler : IDisposable private string moduleRepository; private string tempPath; // path to the user temporary directory private string tempModulePath; // path to temp directory containing modules - Dictionary modulesFound; private string localAppdataPath; private string pssaAppDataPath; private const string symLinkName = "TempModuleDir"; @@ -271,8 +270,6 @@ public ModuleDependencyHandler( ? "PSScriptAnalyzer" : pssaAppDataPath); - modulesFound = new Dictionary(StringComparer.OrdinalIgnoreCase); - // TODO Add PSSA Version in the path symLinkPath = Path.Combine(pssaAppDataPath, symLinkName); SetupPSSAAppData(); diff --git a/Engine/Generic/RuleSuppression.cs b/Engine/Generic/RuleSuppression.cs index d912eee0c..7daab3e86 100644 --- a/Engine/Generic/RuleSuppression.cs +++ b/Engine/Generic/RuleSuppression.cs @@ -193,12 +193,12 @@ public RuleSuppression(AttributeAst attrAst, int start, int end) } else if (argumentName.Equals("rulesuppressionid", StringComparison.OrdinalIgnoreCase)) { - if (!String.IsNullOrWhiteSpace(RuleName)) + if (!String.IsNullOrWhiteSpace(RuleSuppressionID)) { Error = String.Format(Strings.NamedAndPositionalArgumentsConflictError, name); } - RuleName = (name.Argument as StringConstantExpressionAst).Value; + RuleSuppressionID = (name.Argument as StringConstantExpressionAst).Value; } else if (argumentName.Equals("scope", StringComparison.OrdinalIgnoreCase)) { @@ -333,12 +333,12 @@ public static List GetSuppressions(IEnumerable at { targetAsts = scopeAst.FindAll(ast => ast is FunctionDefinitionAst && reg.IsMatch((ast as FunctionDefinitionAst).Name), true); } - #if !(PSV3 || PSV4) +#if !(PSV3 || PSV4) else if (scope.Equals("class", StringComparison.OrdinalIgnoreCase)) { targetAsts = scopeAst.FindAll(ast => ast is TypeDefinitionAst && reg.IsMatch((ast as TypeDefinitionAst).Name), true); } - #endif +#endif if (targetAsts != null) { diff --git a/Engine/Helper.cs b/Engine/Helper.cs index feea8a5ec..098d8a276 100644 --- a/Engine/Helper.cs +++ b/Engine/Helper.cs @@ -24,7 +24,6 @@ public class Helper #region Private members private CommandInvocationIntrinsics invokeCommand; - private IOutputWriter outputWriter; private readonly static Version minSupportedPSVersion = new Version(3, 0); private Dictionary> ruleArguments; private PSVersionTable psVersionTable; @@ -115,7 +114,7 @@ internal set /// private Helper() { - _commandInfoCacheLazy = new Lazy(() => new CommandInfoCache(pssaHelperInstance: this)); + _commandInfoCacheLazy = new Lazy(() => new CommandInfoCache()); } /// @@ -125,16 +124,11 @@ private Helper() /// A CommandInvocationIntrinsics instance for use in gathering /// information about available commands and aliases. /// - /// - /// An IOutputWriter instance for use in writing output - /// to the PowerShell environment. - /// public Helper( - CommandInvocationIntrinsics invokeCommand, - IOutputWriter outputWriter) : this() + CommandInvocationIntrinsics invokeCommand + ): this() { this.invokeCommand = invokeCommand; - this.outputWriter = outputWriter; } #region Methods @@ -615,14 +609,15 @@ public bool HasSplattedVariable(CommandAst cmdAst) /// /// /// - public bool IsKnownCmdletFunctionOrExternalScript(CommandAst cmdAst) + public bool IsKnownCmdletFunctionOrExternalScript(CommandAst cmdAst, out CommandInfo commandInfo) { + commandInfo = null; if (cmdAst == null) { return false; } - var commandInfo = GetCommandInfo(cmdAst.GetCommandName()); + commandInfo = GetCommandInfo(cmdAst.GetCommandName()); if (commandInfo == null) { return false; @@ -766,8 +761,15 @@ public IScriptExtent GetScriptExtentForFunctionName(FunctionDefinitionAst functi token => ContainsExtent(functionDefinitionAst.Extent, token.Extent) && token.Text.Equals(functionDefinitionAst.Name)); - var funcNameToken = funcNameTokens.FirstOrDefault(); - return funcNameToken == null ? null : funcNameToken.Extent; + + // If the functions name is 'function' then the first token in the + // list is the function keyword itself, so we need to skip it + if (functionDefinitionAst.Name.Equals("function", StringComparison.OrdinalIgnoreCase)) + { + var funcNameToken = funcNameTokens.Skip(1).FirstOrDefault() ?? funcNameTokens.FirstOrDefault(); + return funcNameToken?.Extent; + } + return funcNameTokens.FirstOrDefault()?.Extent; } /// @@ -875,19 +877,13 @@ public bool IsUninitialized(VariableExpressionAst varAst, Ast ast) } /// - /// Returns true if varaible is either a global variable or an environment variable + /// Returns true if variable is either a global variable or an environment variable /// /// - /// /// - public bool IsVariableGlobalOrEnvironment(VariableExpressionAst varAst, Ast ast) + public bool IsVariableGlobalOrEnvironment(VariableExpressionAst varAst) { - if (!VariableAnalysisDictionary.ContainsKey(ast) || VariableAnalysisDictionary[ast] == null) - { - return false; - } - - return VariableAnalysisDictionary[ast].IsGlobalOrEnvironment(varAst); + return VariableAnalysis.IsGlobalOrEnvironment(varAst); } diff --git a/Engine/PSScriptAnalyzer.psd1 b/Engine/PSScriptAnalyzer.psd1 index f3692b483..49fb93227 100644 --- a/Engine/PSScriptAnalyzer.psd1 +++ b/Engine/PSScriptAnalyzer.psd1 @@ -11,7 +11,7 @@ Author = 'Microsoft Corporation' RootModule = 'PSScriptAnalyzer.psm1' # Version number of this module. -ModuleVersion = '1.21.0' +ModuleVersion = '{{ModuleVersion}}' # ID used to uniquely identify this module GUID = 'd6245802-193d-4068-a631-8863a4342a18' @@ -20,13 +20,13 @@ GUID = 'd6245802-193d-4068-a631-8863a4342a18' CompanyName = 'Microsoft Corporation' # Copyright statement for this module -Copyright = '(c) Microsoft Corporation 2016. All rights reserved.' +Copyright = '(c) Microsoft Corporation 2025. All rights reserved.' # Description of the functionality provided by this module Description = 'PSScriptAnalyzer provides script analysis and checks for potential code defects in the scripts by applying a group of built-in or customized rules on the scripts being analyzed.' # Minimum version of the Windows PowerShell engine required by this module -PowerShellVersion = '3.0' +PowerShellVersion = '5.1' # Name of the Windows PowerShell host required by this module # PowerShellHostName = '' @@ -83,9 +83,9 @@ AliasesToExport = @() PrivateData = @{ PSData = @{ Tags = 'lint', 'bestpractice' - LicenseUri = 'https://github.com/PowerShell/PSScriptAnalyzer/blob/master/LICENSE' + LicenseUri = 'https://github.com/PowerShell/PSScriptAnalyzer/blob/main/LICENSE' ProjectUri = 'https://github.com/PowerShell/PSScriptAnalyzer' - IconUri = 'https://raw.githubusercontent.com/powershell/psscriptanalyzer/master/logo.png' + IconUri = 'https://raw.githubusercontent.com/powershell/psscriptanalyzer/main/logo.png' ReleaseNotes = '' } } diff --git a/Engine/PSScriptAnalyzer.psm1 b/Engine/PSScriptAnalyzer.psm1 index 91f096c25..6d2563a49 100644 --- a/Engine/PSScriptAnalyzer.psm1 +++ b/Engine/PSScriptAnalyzer.psm1 @@ -9,7 +9,7 @@ $PSModuleRoot = $PSModule.ModuleBase # Import the appropriate nested binary module based on the current PowerShell version $binaryModuleRoot = $PSModuleRoot -[Version] $minimumPowerShellCoreVersion = '7.0.11' +[Version] $minimumPowerShellCoreVersion = '7.4.13' if ($PSVersionTable.PSVersion.Major -ge 6) { $binaryModuleRoot = Join-Path -Path $PSModuleRoot -ChildPath "PSv$($PSVersionTable.PSVersion.Major)" # Minimum PowerShell Core version given by PowerShell Core support itself and diff --git a/Engine/ScriptAnalyzer.cs b/Engine/ScriptAnalyzer.cs index d35ab7e0a..f250336b5 100644 --- a/Engine/ScriptAnalyzer.cs +++ b/Engine/ScriptAnalyzer.cs @@ -168,8 +168,7 @@ public void Initialize( //initialize helper Helper.Instance = new Helper( - runspace.SessionStateProxy.InvokeCommand, - outputWriter); + runspace.SessionStateProxy.InvokeCommand); Helper.Instance.Initialize(); SuppressionPreference suppressionPreference = suppressedOnly @@ -823,13 +822,13 @@ private void Initialize( // Ensure that rules were actually loaded if (rules == null || rules.Any() == false) { + string errorMessage = string.Format(CultureInfo.CurrentCulture, Strings.RulesNotFound); + this.outputWriter.ThrowTerminatingError( new ErrorRecord( - new Exception(), - string.Format( - CultureInfo.CurrentCulture, - Strings.RulesNotFound), - ErrorCategory.ResourceExists, + new Exception(errorMessage), + errorMessage, + ErrorCategory.ObjectNotFound, this)); } @@ -1489,7 +1488,7 @@ public IEnumerable AnalyzeAndFixPath(string path, FuncParsed tokens of /// Whether variable analysis can be skipped (applicable if rules do not use variable analysis APIs). /// - public IEnumerable AnalyzeScriptDefinition(string scriptDefinition, out ScriptBlockAst scriptAst, out Token[] scriptTokens, bool skipVariableAnalysis = false) + public List AnalyzeScriptDefinition(string scriptDefinition, out ScriptBlockAst scriptAst, out Token[] scriptTokens, bool skipVariableAnalysis = false) { scriptAst = null; scriptTokens = null; @@ -1504,7 +1503,7 @@ public IEnumerable AnalyzeScriptDefinition(string scriptDefini catch (Exception e) { this.outputWriter.WriteWarning(e.ToString()); - return null; + return new(); } var relevantParseErrors = RemoveTypeNotFoundParseErrors(errors, out List diagnosticRecords); @@ -1529,7 +1528,8 @@ public IEnumerable AnalyzeScriptDefinition(string scriptDefini } // now, analyze the script definition - return diagnosticRecords.Concat(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, String.Empty, skipVariableAnalysis)); + diagnosticRecords.AddRange(this.AnalyzeSyntaxTree(scriptAst, scriptTokens, null, skipVariableAnalysis)); + return diagnosticRecords; } /// diff --git a/Engine/ScriptAnalyzer.types.ps1xml b/Engine/ScriptAnalyzer.types.ps1xml index da989657e..03bf3937e 100644 --- a/Engine/ScriptAnalyzer.types.ps1xml +++ b/Engine/ScriptAnalyzer.types.ps1xml @@ -79,7 +79,7 @@ DefaultDisplayPropertySet - Name + RuleName Severity Description SourceName diff --git a/Engine/Settings.cs b/Engine/Settings.cs index b13f9405b..b0c424c64 100644 --- a/Engine/Settings.cs +++ b/Engine/Settings.cs @@ -285,17 +285,6 @@ private Dictionary GetDictionaryFromHashtable(Hashtable hashtabl return dictionary; } - private bool IsStringOrStringArray(object val) - { - if (val is string) - { - return true; - } - - var valArr = val as object[]; - return val == null ? false : valArr.All(x => x is string); - } - private List GetData(object val, string key) { // value must be either string or or an array of strings @@ -508,6 +497,13 @@ private static bool IsBuiltinSettingPreset(object settingPreset) internal static SettingsMode FindSettingsMode(object settings, string path, out object settingsFound) { var settingsMode = SettingsMode.None; + + // if the provided settings argument is wrapped in an expressions then PowerShell resolves it but it will be of type PSObject and we have to operate then on the BaseObject + if (settings is PSObject settingsFoundPSObject) + { + settings = settingsFoundPSObject.BaseObject; + } + settingsFound = settings; if (settingsFound == null) { @@ -543,11 +539,6 @@ internal static SettingsMode FindSettingsMode(object settings, string path, out { settingsMode = SettingsMode.Hashtable; } - // if the provided argument is wrapped in an expressions then PowerShell resolves it but it will be of type PSObject and we have to operate then on the BaseObject - else if (settingsFound is PSObject settingsFoundPSObject) - { - TryResolveSettingForStringType(settingsFoundPSObject.BaseObject, ref settingsMode, ref settingsFound); - } } } diff --git a/Engine/SpecialVars.cs b/Engine/SpecialVars.cs index 26f1afcf7..3416a5ad2 100644 --- a/Engine/SpecialVars.cs +++ b/Engine/SpecialVars.cs @@ -91,6 +91,8 @@ static SpecialVars() internal const string ConfirmPreference = "ConfirmPreference"; internal const string ProgressPreference = "ProgressPreference"; internal const string InformationPreference = "InformationPreference"; + internal const string ErrorView = "ErrorView"; + internal const string PSNativeCommandUseErrorActionPreference = "PSNativeCommandUseErrorActionPreference"; internal static readonly string[] PreferenceVariables = new string[] { @@ -101,7 +103,9 @@ static SpecialVars() WarningPreference, ConfirmPreference, ProgressPreference, - InformationPreference + InformationPreference, + ErrorView, + PSNativeCommandUseErrorActionPreference, }; internal static readonly Type[] PreferenceVariableTypes = new Type[] @@ -114,6 +118,8 @@ static SpecialVars() /* ConfirmPreference */ typeof(ConfirmImpact), /* ProgressPreference */ typeof(Enum), /* InformationPreference */ typeof(ActionPreference), + /* ErrorView */ typeof(Enum), //ErrorView type not available on PS3 + /* PSNativeCommandUseErrorActionPreference */ typeof(bool), }; internal enum AutomaticVariable @@ -154,6 +160,7 @@ internal enum PreferenceVariable internal const string PSEmailServer = "PSEmailServer"; internal const string PSDefaultParameterValues = "PSDefaultParameterValues"; internal const string PSModuleAutoLoadingPreference = "PSModuleAutoLoadingPreference"; + internal const string PSNativeCommandArgumentPassing = "PSNativeCommandArgumentPassing"; internal const string pwd = "PWD"; internal const string Null = "null"; internal const string True = "true"; @@ -176,6 +183,7 @@ internal enum PreferenceVariable PSEmailServer, PSDefaultParameterValues, PSModuleAutoLoadingPreference, + PSNativeCommandArgumentPassing, pwd, Null, True, diff --git a/Engine/Strings.Designer.cs b/Engine/Strings.Designer.cs deleted file mode 100644 index 1fed6e234..000000000 --- a/Engine/Strings.Designer.cs +++ /dev/null @@ -1,684 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Strings() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Windows.PowerShell.ScriptAnalyzer.Strings", typeof(Strings).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Checking assembly file '{0}' .... - /// - internal static string CheckAssemblyFile { - get { - return ResourceManager.GetString("CheckAssemblyFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checking module '{0}' .... - /// - internal static string CheckModuleName { - get { - return ResourceManager.GetString("CheckModuleName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CommandInfo not found for function: {0}. - /// - internal static string CommandInfoNotFound { - get { - return ResourceManager.GetString("CommandInfoNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to "Argument should not be null.".. - /// - internal static string ConfigurableScriptRuleNRE { - get { - return ResourceManager.GetString("ConfigurableScriptRuleNRE", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to "Cannot find a ConfigurableRuleProperty attribute on property {0}".. - /// - internal static string ConfigurableScriptRulePropertyHasNotAttribute { - get { - return ResourceManager.GetString("ConfigurableScriptRulePropertyHasNotAttribute", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SettingsFileHasInvalidHashtable. - /// - internal static string ConfigurationFileHasInvalidHashtable { - get { - return ResourceManager.GetString("ConfigurationFileHasInvalidHashtable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SettingsFileHasNoHashTable. - /// - internal static string ConfigurationFileHasNoHashTable { - get { - return ResourceManager.GetString("ConfigurationFileHasNoHashTable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SettingsFileNotFound. - /// - internal static string ConfigurationFileNotFound { - get { - return ResourceManager.GetString("ConfigurationFileNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SettingsKeyNotAString. - /// - internal static string ConfigurationKeyNotAString { - get { - return ResourceManager.GetString("ConfigurationKeyNotAString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SettingsValueNotAString. - /// - internal static string ConfigurationValueNotAString { - get { - return ResourceManager.GetString("ConfigurationValueNotAString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to SettingsValueWrongFormat. - /// - internal static string ConfigurationValueWrongFormat { - get { - return ResourceManager.GetString("ConfigurationValueWrongFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Writes all diagnostics to WriteObject.. - /// - internal static string DefaultLoggerDescription { - get { - return ResourceManager.GetString("DefaultLoggerDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to WriteObjects. - /// - internal static string DefaultLoggerName { - get { - return ResourceManager.GetString("DefaultLoggerName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Edge from {0} to {1} already exists.. - /// - internal static string DigraphEdgeAlreadyExists { - get { - return ResourceManager.GetString("DigraphEdgeAlreadyExists", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Vertex {0} already exists! Cannot add it to the digraph.. - /// - internal static string DigraphVertexAlreadyExists { - get { - return ResourceManager.GetString("DigraphVertexAlreadyExists", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Vertex {0} does not exist in the digraph.. - /// - internal static string DigraphVertexDoesNotExists { - get { - return ResourceManager.GetString("DigraphVertexDoesNotExists", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot determine line endings as the text probably contain mixed line endings.. - /// - internal static string EditableTextInvalidLineEnding { - get { - return ResourceManager.GetString("EditableTextInvalidLineEnding", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TextEdit extent not completely contained in EditableText.. - /// - internal static string EditableTextRangeIsNotContained { - get { - return ResourceManager.GetString("EditableTextRangeIsNotContained", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find file '{0}'.. - /// - internal static string FileNotFound { - get { - return ResourceManager.GetString("FileNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find the path '{0}'.. - /// - internal static string InvalidPath { - get { - return ResourceManager.GetString("InvalidPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Settings file '{0}' is invalid because it does not contain a hashtable.. - /// - internal static string InvalidProfile { - get { - return ResourceManager.GetString("InvalidProfile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Key {0} in the settings is not a string.. - /// - internal static string KeyNotString { - get { - return ResourceManager.GetString("KeyNotString", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No loggers found.. - /// - internal static string LoggersNotFound { - get { - return ResourceManager.GetString("LoggersNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find rule extension '{0}'.. - /// - internal static string MissingRuleExtension { - get { - return ResourceManager.GetString("MissingRuleExtension", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Temporary module location: {0}.. - /// - internal static string ModuleDepHandlerTempLocation { - get { - return ResourceManager.GetString("ModuleDepHandlerTempLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} cannot be set by both positional and named arguments.. - /// - internal static string NamedAndPositionalArgumentsConflictError { - get { - return ResourceManager.GetString("NamedAndPositionalArgumentsConflictError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Named arguments must always come after positional arguments.. - /// - internal static string NamedArgumentsBeforePositionalError { - get { - return ResourceManager.GetString("NamedArgumentsBeforePositionalError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to RuleName must not be null.. - /// - internal static string NullRuleNameError { - get { - return ResourceManager.GetString("NullRuleNameError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parse error in script definition: {0} at line {1} column {2}.. - /// - internal static string ParseErrorFormatForScriptDefinition { - get { - return ResourceManager.GetString("ParseErrorFormatForScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parse error in file {0}: {1} at line {2} column {3}.. - /// - internal static string ParserErrorFormat { - get { - return ResourceManager.GetString("ParserErrorFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are too many parser errors in {0}. Please correct them before running ScriptAnalyzer.. - /// - internal static string ParserErrorMessage { - get { - return ResourceManager.GetString("ParserErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There are too many parser errors in the script definition. Please correct them before running ScriptAnalyzer.. - /// - internal static string ParserErrorMessageForScriptDefinition { - get { - return ResourceManager.GetString("ParserErrorMessageForScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Column number cannot be less than 1.. - /// - internal static string PositionColumnLessThanOne { - get { - return ResourceManager.GetString("PositionColumnLessThanOne", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line number cannot be less than 1.. - /// - internal static string PositionLineLessThanOne { - get { - return ResourceManager.GetString("PositionLineLessThanOne", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Input position should be less than that of the invoking object.. - /// - internal static string PositionRefPosLessThanInputPos { - get { - return ResourceManager.GetString("PositionRefPosLessThanInputPos", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reference Position should begin before start Position of Range.. - /// - internal static string RangeRefPosShouldStartBeforeRangeStartPos { - get { - return ResourceManager.GetString("RangeRefPosShouldStartBeforeRangeStartPos", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Start position cannot be before End position.. - /// - internal static string RangeStartPosGreaterThanEndPos { - get { - return ResourceManager.GetString("RangeStartPosGreaterThanEndPos", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to RULE_ERROR. - /// - internal static string RuleErrorMessage { - get { - return ResourceManager.GetString("RuleErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find ScriptAnalyzer rules in the specified path. - /// - internal static string RulesNotFound { - get { - return ResourceManager.GetString("RulesNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Suppression Message Attribute error at line {0} in {1} : {2}. - /// - internal static string RuleSuppressionErrorFormat { - get { - return ResourceManager.GetString("RuleSuppressionErrorFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Suppression Message Attribute error at line {0} in script definition : {1}. - /// - internal static string RuleSuppressionErrorFormatScriptDefinition { - get { - return ResourceManager.GetString("RuleSuppressionErrorFormatScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find any DiagnosticRecord with the Rule Suppression ID {0}.. - /// - internal static string RuleSuppressionIDError { - get { - return ResourceManager.GetString("RuleSuppressionIDError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Found {0}. Will use it to provide settings for this invocation.. - /// - internal static string SettingsAutoDiscovered { - get { - return ResourceManager.GetString("SettingsAutoDiscovered", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot resolve settings file path '{0}'.. - /// - internal static string SettingsCannotFindFile { - get { - return ResourceManager.GetString("SettingsCannotFindFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Dictionary should be indexable in a case-insensitive manner.. - /// - internal static string SettingsDictionaryShouldBeCaseInsesitive { - get { - return ResourceManager.GetString("SettingsDictionaryShouldBeCaseInsesitive", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Input should be a dictionary type.. - /// - internal static string SettingsInputShouldBeDictionary { - get { - return ResourceManager.GetString("SettingsInputShouldBeDictionary", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Settings should be either a file path, built-in preset or a hashtable.. - /// - internal static string SettingsInvalidType { - get { - return ResourceManager.GetString("SettingsInvalidType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot parse settings. Will abort the invocation.. - /// - internal static string SettingsNotParsable { - get { - return ResourceManager.GetString("SettingsNotParsable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Settings not provided. Will look for settings file in the given path {0}.. - /// - internal static string SettingsNotProvided { - get { - return ResourceManager.GetString("SettingsNotProvided", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Settings object could not be resolved.. - /// - internal static string SettingsObjectCouldNotBResolved { - get { - return ResourceManager.GetString("SettingsObjectCouldNotBResolved", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using settings file at {0}.. - /// - internal static string SettingsUsingFile { - get { - return ResourceManager.GetString("SettingsUsingFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using settings hashtable.. - /// - internal static string SettingsUsingHashtable { - get { - return ResourceManager.GetString("SettingsUsingHashtable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} property must be of type bool.. - /// - internal static string SettingsValueTypeMustBeBool { - get { - return ResourceManager.GetString("SettingsValueTypeMustBeBool", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to All the arguments of the Suppress Message Attribute should be string constants.. - /// - internal static string StringConstantArgumentsSuppressionAttributeError { - get { - return ResourceManager.GetString("StringConstantArgumentsSuppressionAttributeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find any Targets {0} that match the Scope {1} to apply the SuppressMessageAttribute.. - /// - internal static string TargetCannotBeFoundError { - get { - return ResourceManager.GetString("TargetCannotBeFoundError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If Target is specified, Scope must be specified.. - /// - internal static string TargetWithoutScopeSuppressionAttributeError { - get { - return ResourceManager.GetString("TargetWithoutScopeSuppressionAttributeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line element cannot be null.. - /// - internal static string TextEditNoNullItem { - get { - return ResourceManager.GetString("TextEditNoNullItem", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line element cannot be null.. - /// - internal static string TextLinesNoNullItem { - get { - return ResourceManager.GetString("TextLinesNoNullItem", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ignoring 'TypeNotFound' parse error on type '{0}'. Check if the specified type is correct. This can also be due the type not being known at parse time due to types imported by 'using' statements.. - /// - internal static string TypeNotFoundParseErrorFound { - get { - return ResourceManager.GetString("TypeNotFoundParseErrorFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Analyzing file: {0}. - /// - internal static string VerboseFileMessage { - get { - return ResourceManager.GetString("VerboseFileMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Running {0} rule.. - /// - internal static string VerboseRunningMessage { - get { - return ResourceManager.GetString("VerboseRunningMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Analyzing Script Definition.. - /// - internal static string VerboseScriptDefinitionMessage { - get { - return ResourceManager.GetString("VerboseScriptDefinitionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to WrongSettingsKey. - /// - internal static string WrongConfigurationKey { - get { - return ResourceManager.GetString("WrongConfigurationKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable: file {3}. Valid keys are ExcludeRules, IncludeRules and Severity.. - /// - internal static string WrongKey { - get { - return ResourceManager.GetString("WrongKey", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Key in the settings hashtable should be a string: line {0} column {1} in file {2}. - /// - internal static string WrongKeyFormat { - get { - return ResourceManager.GetString("WrongKeyFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} is not a valid key in the settings hashtable. Valid keys are CustomRulePath, ExcludeRules, IncludeRules, IncludeDefaultRules, RecurseCustomRulePath, Rules and Severity.. - /// - internal static string WrongKeyHashTable { - get { - return ResourceManager.GetString("WrongKeyHashTable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Scope can only be either function or class.. - /// - internal static string WrongScopeArgumentSuppressionAttributeError { - get { - return ResourceManager.GetString("WrongScopeArgumentSuppressionAttributeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value in the settings hashtable should be a string or an array of strings: line {0} column {1} in file {2}. - /// - internal static string WrongValueFormat { - get { - return ResourceManager.GetString("WrongValueFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Value {0} for key {1} has the wrong data type.. - /// - internal static string WrongValueHashTable { - get { - return ResourceManager.GetString("WrongValueHashTable", resourceCulture); - } - } - } -} diff --git a/Engine/TokenOperations.cs b/Engine/TokenOperations.cs index fa9a1978a..4845ab8c4 100644 --- a/Engine/TokenOperations.cs +++ b/Engine/TokenOperations.cs @@ -245,5 +245,165 @@ public Ast GetAstPosition(Token token) return findAstVisitor.AstPosition; } + /// + /// Returns a list of non-overlapping ranges (startOffset,endOffset) representing the start + /// and end of braced member access expressions. These are member accesses where the name is + /// enclosed in braces. The contents of such braces are treated literally as a member name. + /// Altering the contents of these braces by formatting is likely to break code. + /// + public List> GetBracedMemberAccessRanges() + { + // A list of (startOffset, endOffset) pairs representing the start + // and end braces of braced member access expressions. + var ranges = new List>(); + + var node = tokensLL.Value.First; + while (node != null) + { + switch (node.Value.Kind) + { +#if CORECLR + // TokenKind added in PS7 + case TokenKind.QuestionDot: +#endif + case TokenKind.Dot: + break; + default: + node = node.Next; + continue; + } + + // Note: We don't check if the dot is part of an existing range. When we find + // a valid range, we skip all tokens inside it - so we won't ever evaluate a token + // which already part of a previously found range. + + // Backward scan: + // Determine if this 'dot' is part of a member access. + // Walk left over contiguous comment tokens that are 'touching'. + // After skipping comments, the preceding non-comment token must also be 'touching' + // and one of the expected TokenKinds. + var leftToken = node.Previous; + var rightToken = node; + while (leftToken != null && leftToken.Value.Kind == TokenKind.Comment) + { + if (leftToken.Value.Extent.EndOffset != rightToken.Value.Extent.StartOffset) + { + leftToken = null; + break; + } + rightToken = leftToken; + leftToken = leftToken.Previous; + } + if (leftToken == null) + { + // We ran out of tokens before finding a non-comment token to the left or there + // was intervening whitespace. + node = node.Next; + continue; + } + + if (leftToken.Value.Extent.EndOffset != rightToken.Value.Extent.StartOffset) + { + // There's whitespace between the two tokens + node = node.Next; + continue; + } + + // Limit to valid token kinds that can precede a 'dot' in a member access. + switch (leftToken.Value.Kind) + { + // Note: TokenKind.Number isn't in the list as 5.{Prop} is a syntax error + // (Unexpected token). Numbers also have no properties - only methods. + case TokenKind.Variable: + case TokenKind.Identifier: + case TokenKind.StringLiteral: + case TokenKind.StringExpandable: + case TokenKind.HereStringLiteral: + case TokenKind.HereStringExpandable: + case TokenKind.RParen: + case TokenKind.RCurly: + case TokenKind.RBracket: + // allowed + break; + default: + // not allowed + node = node.Next; + continue; + } + + // Forward Scan: + // Check that the next significant token is an LCurly + // Starting from the token after the 'dot', walk right skipping trivia tokens: + // - Comment + // - NewLine + // - LineContinuation (`) + // These may be multi-line and need not be 'touching' the dot. + // The first non-trivia token encountered must be an opening curly brace (LCurly) for + // this dot to begin a braced member access. If it is not LCurly or we run out + // of tokens, this dot is ignored. + var scan = node.Next; + while (scan != null) + { + if ( + scan.Value.Kind == TokenKind.Comment || + scan.Value.Kind == TokenKind.NewLine || + scan.Value.Kind == TokenKind.LineContinuation + ) + { + scan = scan.Next; + continue; + } + break; + } + + // If we reached the end without finding a significant token, or if the found token + // is not LCurly, continue. + if (scan == null || scan.Value.Kind != TokenKind.LCurly) + { + node = node.Next; + continue; + } + + // We have a valid token, followed by a dot, followed by an LCurly. + // Find the matching RCurly and create the range. + var lCurlyNode = scan; + + // Depth count braces to find the RCurly which closes the LCurly. + int depth = 0; + LinkedListNode rcurlyNode = null; + while (scan != null) + { + if (scan.Value.Kind == TokenKind.LCurly) depth++; + else if (scan.Value.Kind == TokenKind.RCurly) + { + depth--; + if (depth == 0) + { + rcurlyNode = scan; + break; + } + } + scan = scan.Next; + } + + // If we didn't find a matching RCurly, something has gone wrong. + // Should an unmatched pair be caught by the parser as a parse error? + if (rcurlyNode == null) + { + node = node.Next; + continue; + } + + ranges.Add(new Tuple( + lCurlyNode.Value.Extent.StartOffset, + rcurlyNode.Value.Extent.EndOffset + )); + + // Skip all tokens inside the excluded range. + node = rcurlyNode.Next; + } + + return ranges; + } } } diff --git a/Engine/VariableAnalysis.cs b/Engine/VariableAnalysis.cs index fd66ea2c4..2870d442f 100644 --- a/Engine/VariableAnalysis.cs +++ b/Engine/VariableAnalysis.cs @@ -375,7 +375,7 @@ public bool IsUninitialized(VariableExpressionAst varTarget) /// /// /// - public bool IsGlobalOrEnvironment(VariableExpressionAst varTarget) + public static bool IsGlobalOrEnvironment(VariableExpressionAst varTarget) { if (varTarget != null) { diff --git a/Engine/VariableAnalysisBase.cs b/Engine/VariableAnalysisBase.cs index b55119d7a..77c421f3c 100644 --- a/Engine/VariableAnalysisBase.cs +++ b/Engine/VariableAnalysisBase.cs @@ -995,7 +995,7 @@ internal static Tuple, Dictionary String.Equals(item.Name, analysis.Type.FullName, StringComparison.OrdinalIgnoreCase)); + TypeDefinitionAst psClass = Classes.FirstOrDefault(item => String.Equals(item.Name, analysis.Type?.FullName, StringComparison.OrdinalIgnoreCase)); Type possibleType = AssignmentTarget.GetTypeFromMemberExpressionAst(memAst, analysis, psClass); #endif diff --git a/LICENSE b/LICENSE index cec380d8e..48ea6616b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2015 Microsoft Corporation. +Copyright (c) Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE diff --git a/NuGet.Config b/NuGet.Config index 80f5bd7fc..f003b0fbd 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,7 +1,7 @@ - + - + diff --git a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Collection/PlatformInformationCollector.cs b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Collection/PlatformInformationCollector.cs index 9a7eab952..26aa0ee7a 100644 --- a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Collection/PlatformInformationCollector.cs +++ b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Collection/PlatformInformationCollector.cs @@ -351,18 +351,6 @@ private Architecture GetOSArchitecture() #endif } - private DotnetRuntime GetDotnetRuntime() - { -#if CoreCLR - // Our CoreCLR is actuall .NET Standard, so we could be loaded into net47 - return RuntimeInformation.FrameworkDescription.StartsWith(".NET Core") - ? DotnetRuntime.Core - : DotnetRuntime.Framework; -#else - return DotnetRuntime.Framework; -#endif - } - /// /// Get the Windows SKU ID of the current PowerShell session. /// diff --git a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Commands/CommandUtilities.cs b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Commands/CommandUtilities.cs index 3e14494f1..6a957f7cc 100644 --- a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Commands/CommandUtilities.cs +++ b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Commands/CommandUtilities.cs @@ -13,8 +13,6 @@ namespace Microsoft.PowerShell.CrossCompatibility.Commands /// internal static class CommandUtilities { - private const string COMPATIBILITY_ERROR_ID = "CompatibilityAnalysisError"; - public const string MODULE_PREFIX = "PSCompatibility"; /// diff --git a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Microsoft.PowerShell.CrossCompatibility.csproj b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Microsoft.PowerShell.CrossCompatibility.csproj index eaa88be73..c4667a950 100644 --- a/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Microsoft.PowerShell.CrossCompatibility.csproj +++ b/PSCompatibilityCollector/Microsoft.PowerShell.CrossCompatibility/Microsoft.PowerShell.CrossCompatibility.csproj @@ -1,9 +1,9 @@  - 1.21.0 + $(ModuleVersion) netstandard2.0;net462 - 1.21.0 + $(ModuleVersion) @@ -11,21 +11,20 @@ - - - - - + + + + - - - + + + - + diff --git a/README.md b/README.md index 75dd8668b..d038ec756 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Build Status](https://dev.azure.com/powershell/psscriptanalyzer/_apis/build/status/psscriptanalyzer-ci?branchName=master)](https://dev.azure.com/powershell/psscriptanalyzer/_build/latest?definitionId=80&branchName=master) -[![Build status](https://ci.appveyor.com/api/projects/status/h5mot3vqtvxw5d7l/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/psscriptanalyzer/branch/master) [![Join the chat at https://gitter.im/PowerShell/PSScriptAnalyzer](https://badges.gitter.im/PowerShell/PSScriptAnalyzer.svg)](https://gitter.im/PowerShell/PSScriptAnalyzer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Table of Contents @@ -16,6 +15,7 @@ - [Contributions are welcome](#contributions-are-welcome) - [Creating a Release](#creating-a-release) - [Code of Conduct](#code-of-conduct) +- [Security Policy](#security-policy) @@ -72,13 +72,12 @@ To install **PSScriptAnalyzer** from source code: ### Requirements -- [.NET Core 3.1.424 SDK](https://www.microsoft.com/net/download/dotnet-core/3.1#sdk-3.1.424) or - newer patch release -* If building for Windows PowerShell versions, then the .NET Framework 4.6.2 [targeting pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net462) (also referred to as developer/targeting pack) need to be installed. This is only possible on Windows. -* Optionally but recommended for development: [Visual Studio 2017/2019](https://www.visualstudio.com/downloads) +- [Latest .NET 8.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) +- If building for Windows PowerShell versions, then the .NET Framework 4.6.2 [targeting pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net462) (also referred to as developer/targeting pack) need to be installed. This is only possible on Windows. +- Optional but recommended for development: [Visual Studio 2022](https://www.visualstudio.com/downloads) +- Or [Visual Studio Code](https://code.visualstudio.com/download) - [Pester v5 PowerShell module, available on PowerShell Gallery](https://github.com/pester/Pester) - [PlatyPS PowerShell module, available on PowerShell Gallery](https://github.com/PowerShell/platyPS/releases) -- Optionally but recommended for development: [Visual Studio](https://www.visualstudio.com/downloads) ### Steps @@ -111,18 +110,6 @@ To install **PSScriptAnalyzer** from source code: .\build.ps1 -PSVersion 5 ``` - - Windows PowerShell version 4.0 - - ```powershell - .\build.ps1 -PSVersion 4 - ``` - - - Windows PowerShell version 3.0 - - ```powershell - .\build.ps1 -PSVersion 3 - ``` - - PowerShell 7 ```powershell @@ -135,7 +122,7 @@ To install **PSScriptAnalyzer** from source code: .\build.ps1 -Documentation ``` -- Build all versions (PowerShell v3, v4, v5, and v6) and documentation +- Build all versions (PowerShell v5 and v7) and documentation ```powershell .\build.ps1 -All @@ -144,7 +131,7 @@ To install **PSScriptAnalyzer** from source code: - Import the module ```powershell - Import-Module .\out\PSScriptAnalyzer\PSScriptAnalyzer.psd1 + Import-Module .\out\PSScriptAnalyzer\[version]\PSScriptAnalyzer.psd1 ``` To confirm installation: run `Get-ScriptAnalyzerRule` in the PowerShell console to obtain the @@ -229,10 +216,12 @@ New-Release ## Code of Conduct -This project has adopted the -[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more -information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or -comments. +Please see our [Code of Conduct](CODE_OF_CONDUCT.md) before participating in this project. + +[Back to ToC](#table-of-contents) + +## Security Policy + +For any security issues, please see our [Security Policy](SECURITY.md). [Back to ToC](#table-of-contents) diff --git a/Rules/AlignAssignmentStatement.cs b/Rules/AlignAssignmentStatement.cs index d8b1623d6..1d79870f2 100644 --- a/Rules/AlignAssignmentStatement.cs +++ b/Rules/AlignAssignmentStatement.cs @@ -314,6 +314,11 @@ private static List> GetExtents( private bool HasPropertiesOnSeparateLines(IEnumerable> tuples) { + if (tuples.Count() == 1) + { + // If the hashtable has just a single key-value pair, it does not have properties on separate lines + return false; + } var lines = new HashSet(); foreach (var kvp in tuples) { diff --git a/Rules/AvoidAssignmentToAutomaticVariable.cs b/Rules/AvoidAssignmentToAutomaticVariable.cs index 1f2a784dd..c1ce88462 100644 --- a/Rules/AvoidAssignmentToAutomaticVariable.cs +++ b/Rules/AvoidAssignmentToAutomaticVariable.cs @@ -62,20 +62,45 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (_readOnlyAutomaticVariables.Contains(variableName, StringComparer.OrdinalIgnoreCase)) { yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToReadOnlyAutomaticVariableError, variableName), - variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Error, fileName); + variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Error, fileName, variableName); } if (_readOnlyAutomaticVariablesIntroducedInVersion6_0.Contains(variableName, StringComparer.OrdinalIgnoreCase)) { var severity = IsPowerShellVersion6OrGreater() ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning; yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error, variableName), - variableExpressionAst.Extent, GetName(), severity, fileName); + variableExpressionAst.Extent, GetName(), severity, fileName, variableName); } if (_writableAutomaticVariables.Contains(variableName, StringComparer.OrdinalIgnoreCase)) { yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToWritableAutomaticVariableError, variableName), - variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, variableName); + } + } + + IEnumerable forEachStatementAsts = ast.FindAll(testAst => testAst is ForEachStatementAst, searchNestedScriptBlocks: true); + foreach (ForEachStatementAst forEachStatementAst in forEachStatementAsts) + { + var variableExpressionAst = forEachStatementAst.Variable; + var variableName = variableExpressionAst.VariablePath.UserPath; + if (_readOnlyAutomaticVariables.Contains(variableName, StringComparer.OrdinalIgnoreCase)) + { + yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToReadOnlyAutomaticVariableError, variableName), + variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Error, fileName, variableName); + } + + if (_readOnlyAutomaticVariablesIntroducedInVersion6_0.Contains(variableName, StringComparer.OrdinalIgnoreCase)) + { + var severity = IsPowerShellVersion6OrGreater() ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning; + yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error, variableName), + variableExpressionAst.Extent, GetName(), severity, fileName, variableName); + } + + if (_writableAutomaticVariables.Contains(variableName, StringComparer.OrdinalIgnoreCase)) + { + yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToWritableAutomaticVariableError, variableName), + variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, variableName); } } @@ -89,24 +114,29 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { continue; } - + // also check the parent to exclude variableExpressions that appear within attributes, + // such as '[ValidateSet($True,$False)]' where the read-only variables $true,$false appear. + if (variableExpressionAst.Parent is AttributeAst) + { + continue; + } if (_readOnlyAutomaticVariables.Contains(variableName, StringComparer.OrdinalIgnoreCase)) { yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToReadOnlyAutomaticVariableError, variableName), - variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Error, fileName); + variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Error, fileName, variableName); } if (_readOnlyAutomaticVariablesIntroducedInVersion6_0.Contains(variableName, StringComparer.OrdinalIgnoreCase)) { var severity = IsPowerShellVersion6OrGreater() ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning; yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error, variableName), - variableExpressionAst.Extent, GetName(), severity, fileName); + variableExpressionAst.Extent, GetName(), severity, fileName, variableName); } if (_writableAutomaticVariables.Contains(variableName, StringComparer.OrdinalIgnoreCase)) { yield return new DiagnosticRecord(DiagnosticRecordHelper.FormatError(Strings.AvoidAssignmentToWritableAutomaticVariableError, variableName), - variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + variableExpressionAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName, variableName); } } } diff --git a/Rules/AvoidDefaultValueForMandatoryParameter.cs b/Rules/AvoidDefaultValueForMandatoryParameter.cs index f3d66d973..17925ae97 100644 --- a/Rules/AvoidDefaultValueForMandatoryParameter.cs +++ b/Rules/AvoidDefaultValueForMandatoryParameter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Management.Automation.Language; #if !CORECLR using System.ComponentModel.Composition; @@ -27,59 +28,73 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - // Finds all functionAst - IEnumerable functionAsts = ast.FindAll(testAst => testAst is FunctionDefinitionAst, true); - - foreach (FunctionDefinitionAst funcAst in functionAsts) + // Find all ParameterAst which are children of a ParamBlockAst. This + // doesn't pick up where they appear as children of a + // FunctionDefinitionAst. i.e. + // + // function foo ($a,$b){} -> $a and $b are `ParameterAst` + // + // Include only parameters which have a default value (as without + // one this rule would never alert) + // Include only parameters where ALL parameter attributes have the + // mandatory named argument set to true (implicitly or explicitly) + + var mandatoryParametersWithDefaultValues = + ast.FindAll(testAst => testAst is ParamBlockAst, true) + .Cast() + .Where(pb => pb.Parameters?.Count > 0) + .SelectMany(pb => pb.Parameters) + .Where(paramAst => + paramAst.DefaultValue != null && + HasMandatoryInAllParameterAttributes(paramAst) + ); + + // Report diagnostics for each parameter that violates the rule + foreach (var parameter in mandatoryParametersWithDefaultValues) { - if (funcAst.Body != null && funcAst.Body.ParamBlock != null - && funcAst.Body.ParamBlock.Attributes != null && funcAst.Body.ParamBlock.Parameters != null) - { - foreach (var paramAst in funcAst.Body.ParamBlock.Parameters) - { - bool mandatory = false; - - // check that param is mandatory - foreach (var paramAstAttribute in paramAst.Attributes) - { - if (paramAstAttribute is AttributeAst) - { - var namedArguments = (paramAstAttribute as AttributeAst).NamedArguments; - if (namedArguments != null) - { - foreach (NamedAttributeArgumentAst namedArgument in namedArguments) - { - if (String.Equals(namedArgument.ArgumentName, "mandatory", StringComparison.OrdinalIgnoreCase)) - { - // 3 cases: [Parameter(Mandatory)], [Parameter(Mandatory=$true)] and [Parameter(Mandatory=value)] where value is not equal to 0. - if (namedArgument.ExpressionOmitted - || (String.Equals(namedArgument.Argument.Extent.Text, "$true", StringComparison.OrdinalIgnoreCase)) - || (int.TryParse(namedArgument.Argument.Extent.Text, out int mandatoryValue) && mandatoryValue != 0)) - { - mandatory = true; - break; - } - } - } - } - } - } - - if (!mandatory) - { - break; - } - - if (paramAst.DefaultValue != null) - { - yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.AvoidDefaultValueForMandatoryParameterError, paramAst.Name.VariablePath.UserPath), - paramAst.Name.Extent, GetName(), DiagnosticSeverity.Warning, fileName, paramAst.Name.VariablePath.UserPath); - } - } - } + yield return new DiagnosticRecord( + string.Format( + CultureInfo.CurrentCulture, + Strings.AvoidDefaultValueForMandatoryParameterError, + parameter.Name.VariablePath.UserPath + ), + parameter.Name.Extent, + GetName(), + DiagnosticSeverity.Warning, + fileName, + parameter.Name.VariablePath.UserPath + ); } } + /// + /// Determines if a parameter is mandatory in all of its Parameter attributes. + /// A parameter may have multiple [Parameter] attributes for different parameter sets. + /// This method returns true only if ALL [Parameter] attributes have Mandatory=true. + /// + /// The parameter AST to examine + /// String comparer for case-insensitive attribute name matching + /// + /// True if the parameter has at least one [Parameter] attribute and ALL of them + /// have the Mandatory named argument set to true (explicitly or implicitly). + /// False if the parameter has no [Parameter] attributes or if any [Parameter] + /// attribute does not have Mandatory=true. + /// + private static bool HasMandatoryInAllParameterAttributes(ParameterAst paramAst) + { + var parameterAttributes = paramAst.Attributes.OfType() + .Where(attr => string.Equals(attr.TypeName?.Name, "parameter", StringComparison.OrdinalIgnoreCase)); + + return parameterAttributes.Any() && + parameterAttributes.All(attr => + attr.NamedArguments.OfType() + .Any(namedArg => + string.Equals(namedArg.ArgumentName, "mandatory", StringComparison.OrdinalIgnoreCase) && + Helper.Instance.GetNamedArgumentAttributeValue(namedArg) + ) + ); + } + /// /// GetName: Retrieves the name of this rule. /// @@ -134,6 +149,3 @@ public string GetSourceName() } } - - - diff --git a/Rules/AvoidExclaimOperator.cs b/Rules/AvoidExclaimOperator.cs new file mode 100644 index 000000000..5521463ea --- /dev/null +++ b/Rules/AvoidExclaimOperator.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +#if !CORECLR +using System.ComponentModel.Composition; +#endif +using System.Globalization; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Language; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ + /// + /// AvoidExclaimOperator: Checks for use of the exclaim operator + /// +#if !CORECLR + [Export(typeof(IScriptRule))] +#endif + public class AvoidExclaimOperator : ConfigurableRule + { + + /// + /// Construct an object of AvoidExclaimOperator type. + /// + public AvoidExclaimOperator() { + Enable = false; + } + + /// + /// Analyzes the given ast to find the [violation] + /// + /// AST to be analyzed. This should be non-null + /// Name of file that corresponds to the input AST. + /// A an enumerable type containing the violations + public override IEnumerable AnalyzeScript(Ast ast, string fileName) + { + if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); + + var diagnosticRecords = new List(); + + IEnumerable foundAsts = ast.FindAll(testAst => testAst is UnaryExpressionAst, true); + if (foundAsts != null) { + var correctionDescription = Strings.AvoidExclaimOperatorCorrectionDescription; + foreach (UnaryExpressionAst unaryExpressionAst in foundAsts) { + if (unaryExpressionAst.TokenKind == TokenKind.Exclaim) { + var replaceWith = "-not"; + // The UnaryExpressionAST should have a single child, the argument that the unary operator is acting upon. + // If the child's extent starts 1 after the parent's extent then there's no whitespace between the exclaim + // token and any variable/expression; in that case the replacement -not should include a space + if (unaryExpressionAst.Child != null && unaryExpressionAst.Child.Extent.StartColumnNumber == unaryExpressionAst.Extent.StartColumnNumber + 1) { + replaceWith = "-not "; + } + var corrections = new List { + new CorrectionExtent( + unaryExpressionAst.Extent.StartLineNumber, + unaryExpressionAst.Extent.EndLineNumber, + unaryExpressionAst.Extent.StartColumnNumber, + unaryExpressionAst.Extent.StartColumnNumber + 1, + replaceWith, + fileName, + correctionDescription + ) + }; + diagnosticRecords.Add(new DiagnosticRecord( + string.Format( + CultureInfo.CurrentCulture, + Strings.AvoidExclaimOperatorError + ), + unaryExpressionAst.Extent, + GetName(), + GetDiagnosticSeverity(), + fileName, + suggestedCorrections: corrections + )); + } + } + } + return diagnosticRecords; + } + + /// + /// Retrieves the common name of this rule. + /// + public override string GetCommonName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidExclaimOperatorCommonName); + } + + /// + /// Retrieves the description of this rule. + /// + public override string GetDescription() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidExclaimOperatorDescription); + } + + /// + /// Retrieves the name of this rule. + /// + public override string GetName() + { + return string.Format( + CultureInfo.CurrentCulture, + Strings.NameSpaceFormat, + GetSourceName(), + Strings.AvoidExclaimOperatorName); + } + + /// + /// Retrieves the severity of the rule: error, warning or information. + /// + public override RuleSeverity GetSeverity() + { + return RuleSeverity.Warning; + } + + /// + /// Gets the severity of the returned diagnostic record: error, warning, or information. + /// + /// + public DiagnosticSeverity GetDiagnosticSeverity() + { + return DiagnosticSeverity.Warning; + } + + /// + /// Retrieves the name of the module/assembly the rule is from. + /// + public override string GetSourceName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); + } + + /// + /// Retrieves the type of the rule, Builtin, Managed or Module. + /// + public override SourceType GetSourceType() + { + return SourceType.Builtin; + } + } +} + diff --git a/Rules/AvoidPositionalParameters.cs b/Rules/AvoidPositionalParameters.cs index 3c6ec9626..2071baebd 100644 --- a/Rules/AvoidPositionalParameters.cs +++ b/Rules/AvoidPositionalParameters.cs @@ -6,6 +6,7 @@ using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; using System.Linq; +using System.Management.Automation; #if !CORECLR using System.ComponentModel.Composition; #endif @@ -21,7 +22,7 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #endif public class AvoidPositionalParameters : ConfigurableRule { - [ConfigurableRuleProperty(defaultValue: new string[] { "az" })] + [ConfigurableRuleProperty(defaultValue: new string[] { })] public string[] CommandAllowList { get; set; } public AvoidPositionalParameters() @@ -61,9 +62,11 @@ public override IEnumerable AnalyzeScript(Ast ast, string file // MSDN: CommandAst.GetCommandName Method if (cmdAst.GetCommandName() == null) continue; - if ((Helper.Instance.IsKnownCmdletFunctionOrExternalScript(cmdAst) || declaredFunctionNames.Contains(cmdAst.GetCommandName())) && + if ((Helper.Instance.IsKnownCmdletFunctionOrExternalScript(cmdAst, out CommandInfo commandInfo) || declaredFunctionNames.Contains(cmdAst.GetCommandName())) && (Helper.Instance.PositionalParameterUsed(cmdAst, true))) { + if (commandInfo?.CommandType == CommandTypes.Application) continue; + PipelineAst parent = cmdAst.Parent as PipelineAst; string commandName = cmdAst.GetCommandName(); diff --git a/Rules/AvoidReservedParams.cs b/Rules/AvoidReservedParams.cs index 7582d4571..4035a9c89 100644 --- a/Rules/AvoidReservedParams.cs +++ b/Rules/AvoidReservedParams.cs @@ -60,7 +60,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { if (commonParamNames.Contains(paramName, StringComparer.OrdinalIgnoreCase)) { yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.ReservedParamsError, funcAst.Name, paramName), - paramAst.Extent, GetName(), DiagnosticSeverity.Warning, fileName); + paramAst.Extent, GetName(), GetDiagnosticSeverity(), fileName); } } } @@ -107,7 +107,16 @@ public SourceType GetSourceType() /// public RuleSeverity GetSeverity() { - return RuleSeverity.Warning; + return RuleSeverity.Error; + } + + /// + /// Gets the severity of the returned diagnostic record: error, warning, or information. + /// + /// + public DiagnosticSeverity GetDiagnosticSeverity() + { + return DiagnosticSeverity.Error; } /// diff --git a/Rules/AvoidReservedWordsAsFunctionNames.cs b/Rules/AvoidReservedWordsAsFunctionNames.cs new file mode 100644 index 000000000..921909704 --- /dev/null +++ b/Rules/AvoidReservedWordsAsFunctionNames.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Management.Automation.Language; +using System.Linq; + +#if !CORECLR +using System.ComponentModel.Composition; +#endif + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ +#if !CORECLR + [Export(typeof(IScriptRule))] +#endif + + /// + /// Rule that warns when reserved words are used as function names + /// + public class AvoidReservedWordsAsFunctionNames : IScriptRule + { + + // The list of PowerShell reserved words. + // https://learn.microsoft.com/en-gb/powershell/module/microsoft.powershell.core/about/about_reserved_words + // + // The Below are omitted as they don't pose an issue being a function + // name: + // assembly, base, command, hidden, in, inlinescript, interface, module, + // namespace, private, public, static + static readonly HashSet reservedWords = new HashSet( + new[] { + "begin", "break", "catch", "class", "configuration", + "continue", "data", "define", "do", + "dynamicparam", "else", "elseif", "end", + "enum", "exit", "filter", "finally", + "for", "foreach", "from", "function", + "if", "parallel", "param", "process", + "return", "sequence", "switch", + "throw", "trap", "try", "type", + "until", "using","var", "while", "workflow" + }, + StringComparer.OrdinalIgnoreCase + ); + + /// + /// Analyzes the PowerShell AST for uses of reserved words as function names. + /// + /// The PowerShell Abstract Syntax Tree to analyze. + /// The name of the file being analyzed (for diagnostic reporting). + /// A collection of diagnostic records for each violation. + public IEnumerable AnalyzeScript(Ast ast, string fileName) + { + if (ast == null) + { + throw new ArgumentNullException(Strings.NullAstErrorMessage); + } + + // Find all FunctionDefinitionAst in the Ast + var functionDefinitions = ast.FindAll( + astNode => astNode is FunctionDefinitionAst, + true + ).Cast(); + + foreach (var function in functionDefinitions) + { + string functionName = Helper.Instance.FunctionNameWithoutScope(function.Name); + if (reservedWords.Contains(functionName)) + { + yield return new DiagnosticRecord( + string.Format( + CultureInfo.CurrentCulture, + Strings.AvoidReservedWordsAsFunctionNamesError, + functionName), + Helper.Instance.GetScriptExtentForFunctionName(function) ?? function.Extent, + GetName(), + DiagnosticSeverity.Warning, + fileName + ); + } + } + } + + public string GetCommonName() => Strings.AvoidReservedWordsAsFunctionNamesCommonName; + + public string GetDescription() => Strings.AvoidReservedWordsAsFunctionNamesDescription; + + public string GetName() => string.Format( + CultureInfo.CurrentCulture, + Strings.NameSpaceFormat, + GetSourceName(), + Strings.AvoidReservedWordsAsFunctionNamesName); + + public RuleSeverity GetSeverity() => RuleSeverity.Warning; + + public string GetSourceName() => Strings.SourceName; + + public SourceType GetSourceType() => SourceType.Builtin; + } +} \ No newline at end of file diff --git a/Rules/AvoidTrailingWhitespace.cs b/Rules/AvoidTrailingWhitespace.cs index 47f576d5b..a7567d6e6 100644 --- a/Rules/AvoidTrailingWhitespace.cs +++ b/Rules/AvoidTrailingWhitespace.cs @@ -54,7 +54,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) } int startColumnOfTrailingWhitespace = 1; - for (int i = line.Length - 2; i > 0; i--) + for (int i = line.Length - 2; i >= 0; i--) { if (line[i] != ' ' && line[i] != '\t') { diff --git a/Rules/AvoidUserNameAndPasswordParams.cs b/Rules/AvoidUserNameAndPasswordParams.cs index 0609505fd..ced0c8ee1 100644 --- a/Rules/AvoidUserNameAndPasswordParams.cs +++ b/Rules/AvoidUserNameAndPasswordParams.cs @@ -86,7 +86,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) { yield return new DiagnosticRecord( String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsernameAndPasswordParamsError, funcAst.Name), - GetExtent(usernameAst, passwordAst, ast), GetName(), DiagnosticSeverity.Error, fileName); + GetExtent(usernameAst, passwordAst), GetName(), DiagnosticSeverity.Error, fileName); } } } @@ -111,7 +111,7 @@ private bool IsAttributeOfType(AttributeBaseAst attributeAst, Type type) /// /// /// IScriptExtent - private IScriptExtent GetExtent(ParameterAst usernameAst, ParameterAst passwordAst, Ast scriptAst) + private IScriptExtent GetExtent(ParameterAst usernameAst, ParameterAst passwordAst) { var usrExt = usernameAst.Extent; var pwdExt = passwordAst.Extent; diff --git a/Rules/AvoidUsingAllowUnencryptedAuthentication.cs b/Rules/AvoidUsingAllowUnencryptedAuthentication.cs new file mode 100644 index 000000000..955de8113 --- /dev/null +++ b/Rules/AvoidUsingAllowUnencryptedAuthentication.cs @@ -0,0 +1,117 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation.Language; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +#if !CORECLR +using System.ComponentModel.Composition; +#endif +using System.Globalization; + +namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules +{ + /// + /// AvoidUsingAllowUnencryptedAuthentication: Avoid sending credentials and secrets over unencrypted connections. + /// +#if !CORECLR +[Export(typeof(IScriptRule))] +#endif + public class AvoidUsingAllowUnencryptedAuthentication : AvoidParameterGeneric + { + /// + /// Condition on the cmdlet that must be satisfied for the error to be raised + /// + /// + /// + public override bool CommandCondition(CommandAst CmdAst) + { + return true; + } + + /// + /// Condition on the parameter that must be satisfied for the error to be raised. + /// + /// + /// + /// + public override bool ParameterCondition(CommandAst CmdAst, CommandElementAst CeAst) + { + return CeAst is CommandParameterAst && String.Equals((CeAst as CommandParameterAst).ParameterName, "AllowUnencryptedAuthentication", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Retrieves the error message + /// + /// + /// + /// + public override string GetError(string fileName, CommandAst cmdAst) + { + return String.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingAllowUnencryptedAuthenticationError); + } + + /// + /// GetName: Retrieves the name of this rule. + /// + /// The name of this rule + public override string GetName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.AvoidUsingAllowUnencryptedAuthenticationName); + } + + /// + /// GetCommonName: Retrieves the common name of this rule. + /// + /// The common name of this rule + public override string GetCommonName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingAllowUnencryptedAuthenticationCommonName); + } + + /// + /// GetDescription: Retrieves the description of this rule. + /// + /// The description of this rule + public override string GetDescription() + { + return string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingAllowUnencryptedAuthenticationDescription); + } + + /// + /// GetSourceType: Retrieves the type of the rule: builtin, managed or module. + /// + public override SourceType GetSourceType() + { + return SourceType.Builtin; + } + + /// + /// GetSeverity: Retrieves the severity of the rule: error, warning or information. + /// + /// + public override RuleSeverity GetSeverity() + { + return RuleSeverity.Warning; + } + + /// + /// DiagnosticSeverity: Retrieves the severity of the rule of type DiagnosticSeverity: error, warning or information. + /// + /// + public override DiagnosticSeverity GetDiagnosticSeverity() + { + return DiagnosticSeverity.Warning; + } + + /// + /// GetSourceName: Retrieves the module/assembly name the rule is from. + /// + public override string GetSourceName() + { + return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); + } + } +} diff --git a/Rules/AvoidUsingDeprecatedManifestFields.cs b/Rules/AvoidUsingDeprecatedManifestFields.cs index 8856745d2..fd3431251 100644 --- a/Rules/AvoidUsingDeprecatedManifestFields.cs +++ b/Rules/AvoidUsingDeprecatedManifestFields.cs @@ -70,10 +70,9 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (value != null) { - Version psVersion = null; // get the version - if (Version.TryParse((value as StringConstantExpressionAst).Value, out psVersion)) + if (Version.TryParse((value as StringConstantExpressionAst).Value, out Version psVersion)) { // if version exists and version less than 3, don't raise rule if (psVersion.Major < 3) diff --git a/Rules/CompatibilityRules/UseCompatibleCommands.cs b/Rules/CompatibilityRules/UseCompatibleCommands.cs index 0f7cfadbf..bad708e2c 100644 --- a/Rules/CompatibilityRules/UseCompatibleCommands.cs +++ b/Rules/CompatibilityRules/UseCompatibleCommands.cs @@ -263,8 +263,7 @@ public static CommandCompatibilityDiagnostic CreateForParameter( PlatformData platform, IScriptExtent extent, string analyzedFileName, - IRule rule, - IEnumerable suggestedCorrections = null) + IRule rule) { string message = string.Format( CultureInfo.CurrentCulture, diff --git a/Rules/ReviewUnusedParameter.cs b/Rules/ReviewUnusedParameter.cs index ffaaa1334..9b727e7fc 100644 --- a/Rules/ReviewUnusedParameter.cs +++ b/Rules/ReviewUnusedParameter.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Management.Automation.Language; using Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic; +using Microsoft.Windows.PowerShell.ScriptAnalyzer.Extensions; #if !CORECLR using System.ComponentModel.Composition; #endif @@ -21,8 +22,60 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #endif public class ReviewUnusedParameter : IScriptRule { + private readonly string TraverseArgName = "CommandsToTraverse"; + public List TraverseCommands { get; private set; } + + /// + /// Configure the rule. + /// + /// Sets the list of commands to traverse of this rule + /// + private void SetProperties() + { + TraverseCommands = new List() { "Where-Object", "ForEach-Object" }; + + Dictionary ruleArgs = Helper.Instance.GetRuleArguments(GetName()); + if (ruleArgs == null) + { + return; + } + + if (!ruleArgs.TryGetValue(TraverseArgName, out object obj)) + { + return; + } + IEnumerable commands = obj as IEnumerable; + if (commands == null) + { + // try with enumerable objects + var enumerableObjs = obj as IEnumerable; + if (enumerableObjs == null) + { + return; + } + foreach (var x in enumerableObjs) + { + var y = x as string; + if (y == null) + { + return; + } + else + { + TraverseCommands.Add(y); + } + } + } + else + { + TraverseCommands.AddRange(commands); + } + } + public IEnumerable AnalyzeScript(Ast ast, string fileName) { + SetProperties(); + if (ast == null) { throw new ArgumentNullException(Strings.NullAstErrorMessage); @@ -45,14 +98,40 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) // find all declared parameters IEnumerable parameterAsts = scriptBlockAst.FindAll(oneAst => oneAst is ParameterAst, false); + // does the scriptblock have a process block where either $PSItem or $_ is referenced + bool hasProcessBlockWithPSItemOrUnderscore = false; + if (scriptBlockAst.ProcessBlock != null) + { + IDictionary processBlockVariableCount = GetVariableCount(scriptBlockAst.ProcessBlock); + processBlockVariableCount.TryGetValue("_", out int underscoreVariableCount); + processBlockVariableCount.TryGetValue("psitem", out int psitemVariableCount); + if (underscoreVariableCount > 0 || psitemVariableCount > 0) + { + hasProcessBlockWithPSItemOrUnderscore = true; + } + } + // list all variables - IDictionary variableCount = scriptBlockAst.FindAll(oneAst => oneAst is VariableExpressionAst, false) - .Select(variableExpressionAst => ((VariableExpressionAst)variableExpressionAst).VariablePath.UserPath) - .GroupBy(variableName => variableName, StringComparer.OrdinalIgnoreCase) - .ToDictionary(variableName => variableName.Key, variableName => variableName.Count(), StringComparer.OrdinalIgnoreCase); + IDictionary variableCount = GetVariableCount(scriptBlockAst); foreach (ParameterAst parameterAst in parameterAsts) { + // Check if the parameter has the ValueFromPipeline attribute + NamedAttributeArgumentAst valueFromPipeline = (NamedAttributeArgumentAst)parameterAst.Find( + valFromPipelineAst => valFromPipelineAst is NamedAttributeArgumentAst namedAttrib && string.Equals( + namedAttrib.ArgumentName, "ValueFromPipeline", + StringComparison.OrdinalIgnoreCase + ), + false + ); + // If the parameter has the ValueFromPipeline attribute and the scriptblock has a process block with + // $_ or $PSItem usage, then the parameter is considered used + if (valueFromPipeline != null && valueFromPipeline.GetValue() && hasProcessBlockWithPSItemOrUnderscore) + + { + continue; + } + // there should be at least two usages of the variable since the parameter declaration counts as one variableCount.TryGetValue(parameterAst.Name.VariablePath.UserPath, out int variableUsageCount); if (variableUsageCount >= 2) @@ -164,5 +243,39 @@ public string GetSourceName() { return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } + + /// + /// Returns a dictionary including all variables in the scriptblock and their count + /// + /// The scriptblock ast to scan + /// Previously generated data. New findings are added to any existing dictionary if present + /// a dictionary including all variables in the scriptblock and their count + IDictionary GetVariableCount(Ast ast, Dictionary data = null) + { + Dictionary content = data; + if (null == data) + content = new Dictionary(StringComparer.OrdinalIgnoreCase); + IDictionary result = ast.FindAll(oneAst => oneAst is VariableExpressionAst, false) + .Select(variableExpressionAst => ((VariableExpressionAst)variableExpressionAst).VariablePath.UserPath) + .GroupBy(variableName => variableName, StringComparer.OrdinalIgnoreCase) + .ToDictionary(variableName => variableName.Key, variableName => variableName.Count(), StringComparer.OrdinalIgnoreCase); + + foreach (string key in result.Keys) + { + if (content.ContainsKey(key)) + content[key] = content[key] + result[key]; + else + content[key] = result[key]; + } + + IEnumerable foundScriptBlocks = ast.FindAll(oneAst => oneAst is ScriptBlockExpressionAst, false) + .Where(oneAst => oneAst?.Parent is CommandAst && ((CommandAst)oneAst.Parent).CommandElements[0] is StringConstantExpressionAst && TraverseCommands.Contains(((StringConstantExpressionAst)((CommandAst)oneAst.Parent).CommandElements[0]).Value, StringComparer.OrdinalIgnoreCase)) + .Select(oneAst => ((ScriptBlockExpressionAst)oneAst).ScriptBlock); + foreach (Ast astItem in foundScriptBlocks) + if (astItem != ast) + GetVariableCount((ScriptBlockAst)astItem, content); + + return content; + } } } diff --git a/Rules/Rules.csproj b/Rules/Rules.csproj index 038d4b149..6e485c4e9 100644 --- a/Rules/Rules.csproj +++ b/Rules/Rules.csproj @@ -1,10 +1,10 @@ - + - 1.21.0 - netcoreapp3.1;net462 + $(ModuleVersion) + net8;net462 Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules - 1.21.0 + $(ModuleVersion) Rules Microsoft.Windows.PowerShell.ScriptAnalyzer true @@ -16,16 +16,15 @@ - - - - - - + + + + + - + @@ -34,21 +33,26 @@ portable - - - True - True - Strings.resx - - - + - ResXFileCodeGenerator + + MSBuild:Compile Strings.Designer.cs + $(IntermediateOutputPath)\Strings.Designer.cs + CSharp + Microsoft.Windows.PowerShell.ScriptAnalyzer + Strings - + + + PrepareResources;$(CompileDependsOn) + + $(DefineConstants);PSV3 @@ -57,7 +61,7 @@ $(DefineConstants);PSV3;PSV4 - + $(DefineConstants);PSV7;CORECLR diff --git a/Rules/Strings.Designer.cs b/Rules/Strings.Designer.cs deleted file mode 100644 index 30d0a7321..000000000 --- a/Rules/Strings.Designer.cs +++ /dev/null @@ -1,3285 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.Windows.PowerShell.ScriptAnalyzer { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Strings { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Strings() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Windows.PowerShell.ScriptAnalyzer.Strings", typeof(Strings).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Align assignment statement. - /// - internal static string AlignAssignmentStatementCommonName { - get { - return ResourceManager.GetString("AlignAssignmentStatementCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line up assignment statements such that the assignment operator are aligned.. - /// - internal static string AlignAssignmentStatementDescription { - get { - return ResourceManager.GetString("AlignAssignmentStatementDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Assignment statements are not aligned. - /// - internal static string AlignAssignmentStatementError { - get { - return ResourceManager.GetString("AlignAssignmentStatementError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AlignAssignmentStatement. - /// - internal static string AlignAssignmentStatementName { - get { - return ResourceManager.GetString("AlignAssignmentStatementName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidAssignmentToAutomaticVariable. - /// - internal static string AvoidAssignmentToAutomaticVariableName { - get { - return ResourceManager.GetString("AvoidAssignmentToAutomaticVariableName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use a different variable name. - /// - internal static string AvoidAssignmentToReadOnlyAutomaticVariable { - get { - return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariable", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Changing automtic variables might have undesired side effects. - /// - internal static string AvoidAssignmentToReadOnlyAutomaticVariableCommonName { - get { - return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This automatic variables is built into PowerShell and readonly.. - /// - internal static string AvoidAssignmentToReadOnlyAutomaticVariableDescription { - get { - return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Variable '{0}' cannot be assigned since it is a readonly automatic variable that is built into PowerShell, please use a different name.. - /// - internal static string AvoidAssignmentToReadOnlyAutomaticVariableError { - get { - return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Starting from PowerShell 6.0, the Variable '{0}' cannot be assigned any more since it is a readonly automatic variable that is built into PowerShell, please use a different name.. - /// - internal static string AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error { - get { - return ResourceManager.GetString("AvoidAssignmentToReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Variable '{0}' is an automatic variable that is built into PowerShell, assigning to it might have undesired side effects. If assignment is not by design, please use a different name.. - /// - internal static string AvoidAssignmentToWritableAutomaticVariableError { - get { - return ResourceManager.GetString("AvoidAssignmentToWritableAutomaticVariableError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using ComputerName Hardcoded. - /// - internal static string AvoidComputerNameHardcodedCommonName { - get { - return ResourceManager.GetString("AvoidComputerNameHardcodedCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ComputerName parameter of a cmdlet should not be hardcoded as this will expose sensitive information about the system.. - /// - internal static string AvoidComputerNameHardcodedDescription { - get { - return ResourceManager.GetString("AvoidComputerNameHardcodedDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The ComputerName parameter of cmdlet '{0}' is hardcoded. This will expose sensitive information about the system if the script is shared.. - /// - internal static string AvoidComputerNameHardcodedError { - get { - return ResourceManager.GetString("AvoidComputerNameHardcodedError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingComputerNameHardcoded. - /// - internal static string AvoidComputerNameHardcodedName { - get { - return ResourceManager.GetString("AvoidComputerNameHardcodedName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Default Value For Mandatory Parameter. - /// - internal static string AvoidDefaultValueForMandatoryParameterCommonName { - get { - return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mandatory parameter should not be initialized with a default value in the param block because this value will be ignored.. To fix a violation of this rule, please avoid initializing a value for the mandatory parameter in the param block.. - /// - internal static string AvoidDefaultValueForMandatoryParameterDescription { - get { - return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Mandatory Parameter '{0}' is initialized in the Param block. To fix a violation of this rule, please leave it uninitialized.. - /// - internal static string AvoidDefaultValueForMandatoryParameterError { - get { - return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidDefaultValueForMandatoryParameter. - /// - internal static string AvoidDefaultValueForMandatoryParameterName { - get { - return ResourceManager.GetString("AvoidDefaultValueForMandatoryParameterName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Switch Parameters Should Not Default To True. - /// - internal static string AvoidDefaultValueSwitchParameterCommonName { - get { - return ResourceManager.GetString("AvoidDefaultValueSwitchParameterCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Switch parameter should not default to true.. - /// - internal static string AvoidDefaultValueSwitchParameterDescription { - get { - return ResourceManager.GetString("AvoidDefaultValueSwitchParameterDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File '{0}' has a switch parameter default to true.. - /// - internal static string AvoidDefaultValueSwitchParameterError { - get { - return ResourceManager.GetString("AvoidDefaultValueSwitchParameterError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Script definition has a switch parameter default to true.. - /// - internal static string AvoidDefaultValueSwitchParameterErrorScriptDefinition { - get { - return ResourceManager.GetString("AvoidDefaultValueSwitchParameterErrorScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidDefaultValueSwitchParameter. - /// - internal static string AvoidDefaultValueSwitchParameterName { - get { - return ResourceManager.GetString("AvoidDefaultValueSwitchParameterName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty catch block is used. Please use Write-Error or throw statements in catch blocks.. - /// - internal static string AvoidEmptyCatchBlockError { - get { - return ResourceManager.GetString("AvoidEmptyCatchBlockError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid global aliases.. - /// - internal static string AvoidGlobalAliasesCommonName { - get { - return ResourceManager.GetString("AvoidGlobalAliasesCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that global aliases are not used. Global aliases are strongly discouraged as they overwrite desired aliases with name conflicts.. - /// - internal static string AvoidGlobalAliasesDescription { - get { - return ResourceManager.GetString("AvoidGlobalAliasesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid creating aliases with a Global scope.. - /// - internal static string AvoidGlobalAliasesError { - get { - return ResourceManager.GetString("AvoidGlobalAliasesError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidGlobalAliases. - /// - internal static string AvoidGlobalAliasesName { - get { - return ResourceManager.GetString("AvoidGlobalAliasesName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid global functiosn and aliases. - /// - internal static string AvoidGlobalFunctionsCommonName { - get { - return ResourceManager.GetString("AvoidGlobalFunctionsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that global functions and aliases are not used. Global functions are strongly discouraged as they can cause errors across different systems.. - /// - internal static string AvoidGlobalFunctionsDescription { - get { - return ResourceManager.GetString("AvoidGlobalFunctionsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid creating functions with a Global scope.. - /// - internal static string AvoidGlobalFunctionsError { - get { - return ResourceManager.GetString("AvoidGlobalFunctionsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidGlobalFunctions. - /// - internal static string AvoidGlobalFunctionsName { - get { - return ResourceManager.GetString("AvoidGlobalFunctionsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No Global Variables. - /// - internal static string AvoidGlobalVarsCommonName { - get { - return ResourceManager.GetString("AvoidGlobalVarsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that global variables are not used. Global variables are strongly discouraged as they can cause errors across different systems.. - /// - internal static string AvoidGlobalVarsDescription { - get { - return ResourceManager.GetString("AvoidGlobalVarsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Found global variable '{0}'.. - /// - internal static string AvoidGlobalVarsError { - get { - return ResourceManager.GetString("AvoidGlobalVarsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidGlobalVars. - /// - internal static string AvoidGlobalVarsName { - get { - return ResourceManager.GetString("AvoidGlobalVarsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Invoking Empty Members. - /// - internal static string AvoidInvokingEmptyMembersCommonName { - get { - return ResourceManager.GetString("AvoidInvokingEmptyMembersCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invoking non-constant members would cause potential bugs. Please double check the syntax to make sure members invoked are non-constant.. - /// - internal static string AvoidInvokingEmptyMembersDescription { - get { - return ResourceManager.GetString("AvoidInvokingEmptyMembersDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' has non-constant members. Invoking non-constant members may cause bugs in the script.. - /// - internal static string AvoidInvokingEmptyMembersError { - get { - return ResourceManager.GetString("AvoidInvokingEmptyMembersError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidInvokingEmptyMembers. - /// - internal static string AvoidInvokingEmptyMembersName { - get { - return ResourceManager.GetString("AvoidInvokingEmptyMembersName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid long lines. - /// - internal static string AvoidLongLinesCommonName { - get { - return ResourceManager.GetString("AvoidLongLinesCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line lengths should be less than the configured maximum. - /// - internal static string AvoidLongLinesDescription { - get { - return ResourceManager.GetString("AvoidLongLinesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line exceeds the configured maximum length of {0} characters. - /// - internal static string AvoidLongLinesError { - get { - return ResourceManager.GetString("AvoidLongLinesError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidLongLines. - /// - internal static string AvoidLongLinesName { - get { - return ResourceManager.GetString("AvoidLongLinesName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid multiple type specifiers on parameters. - /// - internal static string AvoidMultipleTypeAttributesCommonName { - get { - return ResourceManager.GetString("AvoidMultipleTypeAttributesCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Prameter should not have more than one type specifier.. - /// - internal static string AvoidMultipleTypeAttributesDescription { - get { - return ResourceManager.GetString("AvoidMultipleTypeAttributesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' has more than one type specifier.. - /// - internal static string AvoidMultipleTypeAttributesError { - get { - return ResourceManager.GetString("AvoidMultipleTypeAttributesError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidMultipleTypeAttributes. - /// - internal static string AvoidMultipleTypeAttributesName { - get { - return ResourceManager.GetString("AvoidMultipleTypeAttributesName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid using null or empty HelpMessage parameter attribute.. - /// - internal static string AvoidNullOrEmptyHelpMessageAttributeCommonName { - get { - return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Setting the HelpMessage attribute to an empty string or null value causes PowerShell interpreter to throw an error while executing the corresponding function.. - /// - internal static string AvoidNullOrEmptyHelpMessageAttributeDescription { - get { - return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to HelpMessage parameter attribute should not be null or empty. To fix a violation of this rule, please set its value to a non-empty string.. - /// - internal static string AvoidNullOrEmptyHelpMessageAttributeError { - get { - return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidNullOrEmptyHelpMessageAttribute. - /// - internal static string AvoidNullOrEmptyHelpMessageAttributeName { - get { - return ResourceManager.GetString("AvoidNullOrEmptyHelpMessageAttributeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid overwriting built in cmdlets. - /// - internal static string AvoidOverwritingBuiltInCmdletsCommonName { - get { - return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Do not overwrite the definition of a cmdlet that is included with PowerShell. - /// - internal static string AvoidOverwritingBuiltInCmdletsDescription { - get { - return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is a cmdlet that is included with PowerShell (version {1}) whose definition should not be overridden. - /// - internal static string AvoidOverwritingBuiltInCmdletsError { - get { - return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidOverwritingBuiltInCmdlets. - /// - internal static string AvoidOverwritingBuiltInCmdletsName { - get { - return ResourceManager.GetString("AvoidOverwritingBuiltInCmdletsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid semicolons as line terminators. - /// - internal static string AvoidSemicolonsAsLineTerminatorsCommonName { - get { - return ResourceManager.GetString("AvoidSemicolonsAsLineTerminatorsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line should not end with a semicolon. - /// - internal static string AvoidSemicolonsAsLineTerminatorsDescription { - get { - return ResourceManager.GetString("AvoidSemicolonsAsLineTerminatorsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line ends with a semicolon. - /// - internal static string AvoidSemicolonsAsLineTerminatorsError { - get { - return ResourceManager.GetString("AvoidSemicolonsAsLineTerminatorsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidSemicolonsAsLineTerminators. - /// - internal static string AvoidSemicolonsAsLineTerminatorsName { - get { - return ResourceManager.GetString("AvoidSemicolonsAsLineTerminatorsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using ShouldContinue Without Boolean Force Parameter. - /// - internal static string AvoidShouldContinueWithoutForceCommonName { - get { - return ResourceManager.GetString("AvoidShouldContinueWithoutForceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Functions that use ShouldContinue should have a boolean force parameter to allow user to bypass it.. - /// - internal static string AvoidShouldContinueWithoutForceDescription { - get { - return ResourceManager.GetString("AvoidShouldContinueWithoutForceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function '{0}' in file '{1}' uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt. - /// - internal static string AvoidShouldContinueWithoutForceError { - get { - return ResourceManager.GetString("AvoidShouldContinueWithoutForceError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function '{0}' in script definition uses ShouldContinue but does not have a boolean force parameter. The force parameter will allow users of the script to bypass ShouldContinue prompt. - /// - internal static string AvoidShouldContinueWithoutForceErrorScriptDefinition { - get { - return ResourceManager.GetString("AvoidShouldContinueWithoutForceErrorScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidShouldContinueWithoutForce. - /// - internal static string AvoidShouldContinueWithoutForceName { - get { - return ResourceManager.GetString("AvoidShouldContinueWithoutForceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid trailing whitespace. - /// - internal static string AvoidTrailingWhitespaceCommonName { - get { - return ResourceManager.GetString("AvoidTrailingWhitespaceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Each line should have no trailing whitespace.. - /// - internal static string AvoidTrailingWhitespaceDescription { - get { - return ResourceManager.GetString("AvoidTrailingWhitespaceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Line has trailing whitespace. - /// - internal static string AvoidTrailingWhitespaceError { - get { - return ResourceManager.GetString("AvoidTrailingWhitespaceError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidTrailingWhitespace. - /// - internal static string AvoidTrailingWhitespaceName { - get { - return ResourceManager.GetString("AvoidTrailingWhitespaceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Module Must Be Loadable. - /// - internal static string AvoidUnloadableModuleCommonName { - get { - return ResourceManager.GetString("AvoidUnloadableModuleCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If a script file is in a PowerShell module folder, then that folder must be loadable.. - /// - internal static string AvoidUnloadableModuleDescription { - get { - return ResourceManager.GetString("AvoidUnloadableModuleDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot load the module '{0}' that file '{1}' is in.. - /// - internal static string AvoidUnloadableModuleError { - get { - return ResourceManager.GetString("AvoidUnloadableModuleError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUnloadableModule. - /// - internal static string AvoidUnloadableModuleName { - get { - return ResourceManager.GetString("AvoidUnloadableModuleName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Username and Password Parameters. - /// - internal static string AvoidUsernameAndPasswordParamsCommonName { - get { - return ResourceManager.GetString("AvoidUsernameAndPasswordParamsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Functions should take in a Credential parameter of type PSCredential (with a Credential transformation attribute defined after it in PowerShell 4.0 or earlier) or set the Password parameter to type SecureString.. - /// - internal static string AvoidUsernameAndPasswordParamsDescription { - get { - return ResourceManager.GetString("AvoidUsernameAndPasswordParamsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function '{0}' has both Username and Password parameters. Either set the type of the Password parameter to SecureString or replace the Username and Password parameters with a Credential parameter of type PSCredential. If using a Credential parameter in PowerShell 4.0 or earlier, please define a credential transformation attribute after the PSCredential type attribute.. - /// - internal static string AvoidUsernameAndPasswordParamsError { - get { - return ResourceManager.GetString("AvoidUsernameAndPasswordParamsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingUsernameAndPasswordParams. - /// - internal static string AvoidUsernameAndPasswordParamsName { - get { - return ResourceManager.GetString("AvoidUsernameAndPasswordParamsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Broken Hash Algorithms. - /// - internal static string AvoidUsingBrokenHashAlgorithmsCommonName { - get { - return ResourceManager.GetString("AvoidUsingBrokenHashAlgorithmsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid using the broken algorithms MD5 or SHA-1.. - /// - internal static string AvoidUsingBrokenHashAlgorithmsDescription { - get { - return ResourceManager.GetString("AvoidUsingBrokenHashAlgorithmsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Algorithm parameter of cmdlet '{0}' was used with the broken algorithm '{1}'.. - /// - internal static string AvoidUsingBrokenHashAlgorithmsError { - get { - return ResourceManager.GetString("AvoidUsingBrokenHashAlgorithmsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingBrokenHashAlgorithms. - /// - internal static string AvoidUsingBrokenHashAlgorithmsName { - get { - return ResourceManager.GetString("AvoidUsingBrokenHashAlgorithmsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Clear-Host. - /// - internal static string AvoidUsingClearHostCommonName { - get { - return ResourceManager.GetString("AvoidUsingClearHostCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using Clear-Host is not recommended because the cmdlet may not work in some hosts or there may even be no hosts at all.. - /// - internal static string AvoidUsingClearHostDescription { - get { - return ResourceManager.GetString("AvoidUsingClearHostDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File '{0}' uses Clear-Host. This is not recommended because it may not work in some hosts or there may even be no hosts at all.. - /// - internal static string AvoidUsingClearHostError { - get { - return ResourceManager.GetString("AvoidUsingClearHostError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingClearHost. - /// - internal static string AvoidUsingClearHostName { - get { - return ResourceManager.GetString("AvoidUsingClearHostName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Cmdlet Aliases or omitting the 'Get-' prefix.. - /// - internal static string AvoidUsingCmdletAliasesCommonName { - get { - return ResourceManager.GetString("AvoidUsingCmdletAliasesCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Replace {0} with {1}. - /// - internal static string AvoidUsingCmdletAliasesCorrectionDescription { - get { - return ResourceManager.GetString("AvoidUsingCmdletAliasesCorrectionDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An alias is an alternate name or nickname for a cmdlet or for a command element, such as a function, script, file, or executable file. An implicit alias is also the omission of the 'Get-' prefix for commands with this prefix. But when writing scripts that will potentially need to be maintained over time, either by the original author or another Windows PowerShell scripter, please consider using full cmdlet name instead of alias. Aliases can introduce these problems, readability, understandability and availa [rest of string was truncated]";. - /// - internal static string AvoidUsingCmdletAliasesDescription { - get { - return ResourceManager.GetString("AvoidUsingCmdletAliasesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is an alias of '{1}'. Alias can introduce possible problems and make scripts hard to maintain. Please consider changing alias to its full content.. - /// - internal static string AvoidUsingCmdletAliasesError { - get { - return ResourceManager.GetString("AvoidUsingCmdletAliasesError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is implicitly aliasing '{1}' because it is missing the 'Get-' prefix. This can introduce possible problems and make scripts hard to maintain. Please consider changing command to its full name.. - /// - internal static string AvoidUsingCmdletAliasesMissingGetPrefixError { - get { - return ResourceManager.GetString("AvoidUsingCmdletAliasesMissingGetPrefixError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingCmdletAliases. - /// - internal static string AvoidUsingCmdletAliasesName { - get { - return ResourceManager.GetString("AvoidUsingCmdletAliasesName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File '{0}' uses Console.'{1}'. Using Console to write is not recommended because it may not work in all hosts or there may even be no hosts at all. Use Write-Output instead.. - /// - internal static string AvoidUsingConsoleWriteError { - get { - return ResourceManager.GetString("AvoidUsingConsoleWriteError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using SecureString With Plain Text. - /// - internal static string AvoidUsingConvertToSecureStringWithPlainTextCommonName { - get { - return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using ConvertTo-SecureString with plain text will expose secure information.. - /// - internal static string AvoidUsingConvertToSecureStringWithPlainTextDescription { - get { - return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File '{0}' uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead.. - /// - internal static string AvoidUsingConvertToSecureStringWithPlainTextError { - get { - return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Script definition uses ConvertTo-SecureString with plaintext. This will expose secure information. Encrypted standard strings should be used instead.. - /// - internal static string AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition { - get { - return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextErrorScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingConvertToSecureStringWithPlainText. - /// - internal static string AvoidUsingConvertToSecureStringWithPlainTextName { - get { - return ResourceManager.GetString("AvoidUsingConvertToSecureStringWithPlainTextName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Deprecated Manifest Fields. - /// - internal static string AvoidUsingDeprecatedManifestFieldsCommonName { - get { - return ResourceManager.GetString("AvoidUsingDeprecatedManifestFieldsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to "ModuleToProcess" is obsolete in the latest PowerShell version. Please update with the latest field "RootModule" in manifest files to avoid PowerShell version inconsistency.. - /// - internal static string AvoidUsingDeprecatedManifestFieldsDescription { - get { - return ResourceManager.GetString("AvoidUsingDeprecatedManifestFieldsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingDeprecatedManifestFields. - /// - internal static string AvoidUsingDeprecatedManifestFieldsName { - get { - return ResourceManager.GetString("AvoidUsingDeprecatedManifestFieldsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid using double quotes if the string is constant.. - /// - internal static string AvoidUsingDoubleQuotesForConstantStringCommonName { - get { - return ResourceManager.GetString("AvoidUsingDoubleQuotesForConstantStringCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use single quotes if the string is constant.. - /// - internal static string AvoidUsingDoubleQuotesForConstantStringDescription { - get { - return ResourceManager.GetString("AvoidUsingDoubleQuotesForConstantStringDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use single quotes when a string is constant.. - /// - internal static string AvoidUsingDoubleQuotesForConstantStringError { - get { - return ResourceManager.GetString("AvoidUsingDoubleQuotesForConstantStringError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingDoubleQuotesForConstantString. - /// - internal static string AvoidUsingDoubleQuotesForConstantStringName { - get { - return ResourceManager.GetString("AvoidUsingDoubleQuotesForConstantStringName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Empty Catch Block. - /// - internal static string AvoidUsingEmptyCatchBlockCommonName { - get { - return ResourceManager.GetString("AvoidUsingEmptyCatchBlockCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Empty catch blocks are considered poor design decisions because if an error occurs in the try block, this error is simply swallowed and not acted upon. While this does not inherently lead to bad things. It can and this should be avoided if possible. To fix a violation of this rule, using Write-Error or throw statements in catch blocks.. - /// - internal static string AvoidUsingEmptyCatchBlockDescription { - get { - return ResourceManager.GetString("AvoidUsingEmptyCatchBlockDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingEmptyCatchBlock. - /// - internal static string AvoidUsingEmptyCatchBlockName { - get { - return ResourceManager.GetString("AvoidUsingEmptyCatchBlockName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Internal URLs. - /// - internal static string AvoidUsingInternalURLsCommonName { - get { - return ResourceManager.GetString("AvoidUsingInternalURLsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Using Internal URLs in the scripts may cause security problems.. - /// - internal static string AvoidUsingInternalURLsDescription { - get { - return ResourceManager.GetString("AvoidUsingInternalURLsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' could be an internal URL. Using internal URL directly in the script may cause potential information disclosure.. - /// - internal static string AvoidUsingInternalURLsError { - get { - return ResourceManager.GetString("AvoidUsingInternalURLsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingInternalURLs. - /// - internal static string AvoidUsingInternalURLsName { - get { - return ResourceManager.GetString("AvoidUsingInternalURLsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Invoke-Expression is used. Please remove Invoke-Expression from script and find other options instead.. - /// - internal static string AvoidUsingInvokeExpressionError { - get { - return ResourceManager.GetString("AvoidUsingInvokeExpressionError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Invoke-Expression. - /// - internal static string AvoidUsingInvokeExpressionRuleCommonName { - get { - return ResourceManager.GetString("AvoidUsingInvokeExpressionRuleCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Invoke-Expression cmdlet evaluates or runs a specified string as a command and returns the results of the expression or command. It can be extraordinarily powerful so it is not that you want to never use it but you need to be very careful about using it. In particular, you are probably on safe ground if the data only comes from the program itself. If you include any data provided from the user - you need to protect yourself from Code Injection. To fix a violation of this rule, please remove Invoke-Exp [rest of string was truncated]";. - /// - internal static string AvoidUsingInvokeExpressionRuleDescription { - get { - return ResourceManager.GetString("AvoidUsingInvokeExpressionRuleDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingInvokeExpression. - /// - internal static string AvoidUsingInvokeExpressionRuleName { - get { - return ResourceManager.GetString("AvoidUsingInvokeExpressionRuleName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Plain Text For Password Parameter. - /// - internal static string AvoidUsingPlainTextForPasswordCommonName { - get { - return ResourceManager.GetString("AvoidUsingPlainTextForPasswordCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Set {0} type to {1}. - /// - internal static string AvoidUsingPlainTextForPasswordCorrectionDescription { - get { - return ResourceManager.GetString("AvoidUsingPlainTextForPasswordCorrectionDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Password parameters that take in plaintext will expose passwords and compromise the security of your system.. - /// - internal static string AvoidUsingPlainTextForPasswordDescription { - get { - return ResourceManager.GetString("AvoidUsingPlainTextForPasswordDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' should not use String type but either SecureString or PSCredential, otherwise it increases the chance to to expose this sensitive information.. - /// - internal static string AvoidUsingPlainTextForPasswordError { - get { - return ResourceManager.GetString("AvoidUsingPlainTextForPasswordError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingPlainTextForPassword. - /// - internal static string AvoidUsingPlainTextForPasswordName { - get { - return ResourceManager.GetString("AvoidUsingPlainTextForPasswordName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Positional Parameters. - /// - internal static string AvoidUsingPositionalParametersCommonName { - get { - return ResourceManager.GetString("AvoidUsingPositionalParametersCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Readability and clarity should be the goal of any script we expect to maintain over time. When calling a command that takes parameters, where possible consider using name parameters as opposed to positional parameters. To fix a violation of this rule, please use named parameters instead of positional parameters when calling a command.. - /// - internal static string AvoidUsingPositionalParametersDescription { - get { - return ResourceManager.GetString("AvoidUsingPositionalParametersDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cmdlet '{0}' has positional parameter. Please use named parameters instead of positional parameters when calling a command.. - /// - internal static string AvoidUsingPositionalParametersError { - get { - return ResourceManager.GetString("AvoidUsingPositionalParametersError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingPositionalParameters. - /// - internal static string AvoidUsingPositionalParametersName { - get { - return ResourceManager.GetString("AvoidUsingPositionalParametersName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance. - /// - internal static string AvoidUsingWMICmdletCommonName { - get { - return ResourceManager.GetString("AvoidUsingWMICmdletCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Deprecated. Starting in Windows PowerShell 3.0, these cmdlets have been superseded by CIM cmdlets.. - /// - internal static string AvoidUsingWMICmdletDescription { - get { - return ResourceManager.GetString("AvoidUsingWMICmdletDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File '{0}' uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems.. - /// - internal static string AvoidUsingWMICmdletError { - get { - return ResourceManager.GetString("AvoidUsingWMICmdletError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Script definition uses WMI cmdlet. For PowerShell 3.0 and above, use CIM cmdlet which perform the same tasks as the WMI cmdlets. The CIM cmdlets comply with WS-Management (WSMan) standards and with the Common Information Model (CIM) standard, which enables the cmdlets to use the same techniques to manage Windows computers and those running other operating systems.. - /// - internal static string AvoidUsingWMICmdletErrorScriptDefinition { - get { - return ResourceManager.GetString("AvoidUsingWMICmdletErrorScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingWMICmdlet. - /// - internal static string AvoidUsingWMICmdletName { - get { - return ResourceManager.GetString("AvoidUsingWMICmdletName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid Using Write-Host. - /// - internal static string AvoidUsingWriteHostCommonName { - get { - return ResourceManager.GetString("AvoidUsingWriteHostCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Avoid using the Write-Host cmdlet. Instead, use Write-Output, Write-Verbose, or Write-Information. Because Write-Host is host-specific, its implementation might vary unpredictably. Also, prior to PowerShell 5.0, Write-Host did not write to a stream, so users cannot suppress it, capture its value, or redirect it.. - /// - internal static string AvoidUsingWriteHostDescription { - get { - return ResourceManager.GetString("AvoidUsingWriteHostDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File '{0}' uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.. - /// - internal static string AvoidUsingWriteHostError { - get { - return ResourceManager.GetString("AvoidUsingWriteHostError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Script definition uses Write-Host. Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.. - /// - internal static string AvoidUsingWriteHostErrorScriptDefinition { - get { - return ResourceManager.GetString("AvoidUsingWriteHostErrorScriptDefinition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to AvoidUsingWriteHost. - /// - internal static string AvoidUsingWriteHostName { - get { - return ResourceManager.GetString("AvoidUsingWriteHostName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command Not Found. - /// - internal static string CommandNotFoundCommonName { - get { - return ResourceManager.GetString("CommandNotFoundCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Commands that are undefined or do not exist should not be used.. - /// - internal static string CommandNotFoundDescription { - get { - return ResourceManager.GetString("CommandNotFoundDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command '{0}' Is Not Found. - /// - internal static string CommandNotFoundError { - get { - return ResourceManager.GetString("CommandNotFoundError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to CommandNotFound. - /// - internal static string CommandNotFoundName { - get { - return ResourceManager.GetString("CommandNotFoundName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DscExamplesPresent. - /// - internal static string DscExamplesPresent { - get { - return ResourceManager.GetString("DscExamplesPresent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DSC examples are present. - /// - internal static string DscExamplesPresentCommonName { - get { - return ResourceManager.GetString("DscExamplesPresentCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Every DSC resource module should contain folder "Examples" with sample configurations for every resource. Sample configurations should have resource name they are demonstrating in the title.. - /// - internal static string DscExamplesPresentDescription { - get { - return ResourceManager.GetString("DscExamplesPresentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No examples found for resource '{0}'. - /// - internal static string DscExamplesPresentNoExamplesError { - get { - return ResourceManager.GetString("DscExamplesPresentNoExamplesError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PSDSC. - /// - internal static string DSCSourceName { - get { - return ResourceManager.GetString("DSCSourceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DscTestsPresent. - /// - internal static string DscTestsPresent { - get { - return ResourceManager.GetString("DscTestsPresent", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Dsc tests are present. - /// - internal static string DscTestsPresentCommonName { - get { - return ResourceManager.GetString("DscTestsPresentCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Every DSC resource module should contain folder "Tests" with tests for every resource. Test scripts should have resource name they are testing in the file name.. - /// - internal static string DscTestsPresentDescription { - get { - return ResourceManager.GetString("DscTestsPresentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No tests found for resource '{0}'. - /// - internal static string DscTestsPresentNoTestsError { - get { - return ResourceManager.GetString("DscTestsPresentNoTestsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When using an explicit process block, no preceding code is allowed, only begin, end and dynamicparams blocks.. - /// - internal static string InvalidSyntaxAroundProcessBlockError { - get { - return ResourceManager.GetString("InvalidSyntaxAroundProcessBlockError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Misleading Backtick. - /// - internal static string MisleadingBacktickCommonName { - get { - return ResourceManager.GetString("MisleadingBacktickCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ending a line with an escaped whitepsace character is misleading. A trailing backtick is usually used for line continuation. Users typically don't intend to end a line with escaped whitespace.. - /// - internal static string MisleadingBacktickDescription { - get { - return ResourceManager.GetString("MisleadingBacktickDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This line has a backtick at the end trailed by a whitespace character. Did you mean for this to be a line continuation?. - /// - internal static string MisleadingBacktickError { - get { - return ResourceManager.GetString("MisleadingBacktickError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MisleadingBacktick. - /// - internal static string MisleadingBacktickName { - get { - return ResourceManager.GetString("MisleadingBacktickName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Module Manifest Fields. - /// - internal static string MissingModuleManifestFieldCommonName { - get { - return ResourceManager.GetString("MissingModuleManifestFieldCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Add {0} = {1} to the module manifest. - /// - internal static string MissingModuleManifestFieldCorrectionDescription { - get { - return ResourceManager.GetString("MissingModuleManifestFieldCorrectionDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Some fields of the module manifest (such as ModuleVersion) are required.. - /// - internal static string MissingModuleManifestFieldDescription { - get { - return ResourceManager.GetString("MissingModuleManifestFieldDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to MissingModuleManifestField. - /// - internal static string MissingModuleManifestFieldName { - get { - return ResourceManager.GetString("MissingModuleManifestFieldName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}{1}. - /// - internal static string NameSpaceFormat { - get { - return ResourceManager.GetString("NameSpaceFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Not all code path in {0} function in DSC Class {1} returns a value. - /// - internal static string NotAllCodePathReturnsDSCFunctionsError { - get { - return ResourceManager.GetString("NotAllCodePathReturnsDSCFunctionsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot process null Ast. - /// - internal static string NullAstErrorMessage { - get { - return ResourceManager.GetString("NullAstErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot process null CommandInfo. - /// - internal static string NullCommandInfoError { - get { - return ResourceManager.GetString("NullCommandInfoError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error Message is Null.. - /// - internal static string NullErrorMessage { - get { - return ResourceManager.GetString("NullErrorMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to One Char. - /// - internal static string OneCharCommonName { - get { - return ResourceManager.GetString("OneCharCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that cmdlets and parameters have more than one character.. - /// - internal static string OneCharDescription { - get { - return ResourceManager.GetString("OneCharDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet name '{0}' only has one character.. - /// - internal static string OneCharErrorCmdlet { - get { - return ResourceManager.GetString("OneCharErrorCmdlet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}' has a parameter '{1}' that only has one character.. - /// - internal static string OneCharErrorParameter { - get { - return ResourceManager.GetString("OneCharErrorParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A script block has a parameter '{0}' that only has one character.. - /// - internal static string OneCharErrorParameterSB { - get { - return ResourceManager.GetString("OneCharErrorParameterSB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to OneChar. - /// - internal static string OneCharName { - get { - return ResourceManager.GetString("OneCharName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Place close braces. - /// - internal static string PlaceCloseBraceCommonName { - get { - return ResourceManager.GetString("PlaceCloseBraceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Close brace should be on a new line by itself.. - /// - internal static string PlaceCloseBraceDescription { - get { - return ResourceManager.GetString("PlaceCloseBraceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Close brace is not on a new line.. - /// - internal static string PlaceCloseBraceErrorShouldBeOnNewLine { - get { - return ResourceManager.GetString("PlaceCloseBraceErrorShouldBeOnNewLine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Close brace before a branch statement is followed by a new line.. - /// - internal static string PlaceCloseBraceErrorShouldCuddleBranchStatement { - get { - return ResourceManager.GetString("PlaceCloseBraceErrorShouldCuddleBranchStatement", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Close brace does not follow a new line.. - /// - internal static string PlaceCloseBraceErrorShouldFollowNewLine { - get { - return ResourceManager.GetString("PlaceCloseBraceErrorShouldFollowNewLine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Close brace does not follow a non-empty line.. - /// - internal static string PlaceCloseBraceErrorShouldNotFollowEmptyLine { - get { - return ResourceManager.GetString("PlaceCloseBraceErrorShouldNotFollowEmptyLine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PlaceCloseBrace. - /// - internal static string PlaceCloseBraceName { - get { - return ResourceManager.GetString("PlaceCloseBraceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Place open braces consistently. - /// - internal static string PlaceOpenBraceCommonName { - get { - return ResourceManager.GetString("PlaceOpenBraceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Place open braces either on the same line as the preceding expression or on a new line.. - /// - internal static string PlaceOpenBraceDescription { - get { - return ResourceManager.GetString("PlaceOpenBraceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There is no new line after open brace.. - /// - internal static string PlaceOpenBraceErrorNoNewLineAfterBrace { - get { - return ResourceManager.GetString("PlaceOpenBraceErrorNoNewLineAfterBrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Open brace not on same line as preceding keyword. It should be on the same line.. - /// - internal static string PlaceOpenBraceErrorShouldBeOnSameLine { - get { - return ResourceManager.GetString("PlaceOpenBraceErrorShouldBeOnSameLine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Open brace is not on a new line.. - /// - internal static string PlaceOpenBraceErrorShouldNotBeOnSameLine { - get { - return ResourceManager.GetString("PlaceOpenBraceErrorShouldNotBeOnSameLine", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PlaceOpenBrace. - /// - internal static string PlaceOpenBraceName { - get { - return ResourceManager.GetString("PlaceOpenBraceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Null Comparison. - /// - internal static string PossibleIncorrectComparisonWithNullCommonName { - get { - return ResourceManager.GetString("PossibleIncorrectComparisonWithNullCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that $null is on the left side of any equaltiy comparisons (eq, ne, ceq, cne, ieq, ine). When there is an array on the left side of a null equality comparison, PowerShell will check for a $null IN the array rather than if the array is null. If the two sides of the comaprision are switched this is fixed. Therefore, $null should always be on the left side of equality comparisons just in case.. - /// - internal static string PossibleIncorrectComparisonWithNullDescription { - get { - return ResourceManager.GetString("PossibleIncorrectComparisonWithNullDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to $null should be on the left side of equality comparisons.. - /// - internal static string PossibleIncorrectComparisonWithNullError { - get { - return ResourceManager.GetString("PossibleIncorrectComparisonWithNullError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PossibleIncorrectComparisonWithNull. - /// - internal static string PossibleIncorrectComparisonWithNullName { - get { - return ResourceManager.GetString("PossibleIncorrectComparisonWithNullName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use $null on the left hand side for safe comparison with $null.. - /// - internal static string PossibleIncorrectComparisonWithNullSuggesteCorrectionDescription { - get { - return ResourceManager.GetString("PossibleIncorrectComparisonWithNullSuggesteCorrectionDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '=' is not an assignment operator. Did you mean the equality operator '-eq'?. - /// - internal static string PossibleIncorrectUsageOfAssignmentOperatorCommonName { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '=' or '==' are not comparison operators in the PowerShell language and rarely needed inside conditional statements.. - /// - internal static string PossibleIncorrectUsageOfAssignmentOperatorDescription { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Did you mean to use the assignment operator '='? The equality operator in PowerShell is 'eq'.. - /// - internal static string PossibleIncorrectUsageOfAssignmentOperatorError { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PossibleIncorrectUsageOfAssignmentOperator. - /// - internal static string PossibleIncorrectUsageOfAssignmentOperatorName { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfAssignmentOperatorName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '>' is not a comparison operator. Use '-gt' (greater than) or '-ge' (greater or equal).. - /// - internal static string PossibleIncorrectUsageOfRedirectionOperatorCommonName { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to When switching between different languages it is easy to forget that '>' does not mean 'great than' in PowerShell.. - /// - internal static string PossibleIncorrectUsageOfRedirectionOperatorDescription { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Did you mean to use the redirection operator '>'? The comparison operators in PowerShell are '-gt' (greater than) or '-ge' (greater or equal).. - /// - internal static string PossibleIncorrectUsageOfRedirectionOperatorError { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PossibleIncorrectUsageOfRedirectionOperator. - /// - internal static string PossibleIncorrectUsageOfRedirectionOperatorName { - get { - return ResourceManager.GetString("PossibleIncorrectUsageOfRedirectionOperatorName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Basic Comment Help. - /// - internal static string ProvideCommentHelpCommonName { - get { - return ResourceManager.GetString("ProvideCommentHelpCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that all cmdlets have a help comment. This rule only checks existence. It does not check the content of the comment.. - /// - internal static string ProvideCommentHelpDescription { - get { - return ResourceManager.GetString("ProvideCommentHelpDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}' does not have a help comment.. - /// - internal static string ProvideCommentHelpError { - get { - return ResourceManager.GetString("ProvideCommentHelpError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ProvideCommentHelp. - /// - internal static string ProvideCommentHelpName { - get { - return ResourceManager.GetString("ProvideCommentHelpName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reserved Cmdlet Chars. - /// - internal static string ReservedCmdletCharCommonName { - get { - return ResourceManager.GetString("ReservedCmdletCharCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks for reserved characters in cmdlet names. These characters usually cause a parsing error. Otherwise they will generally cause runtime errors.. - /// - internal static string ReservedCmdletCharDescription { - get { - return ResourceManager.GetString("ReservedCmdletCharDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}' uses a reserved char in its name.. - /// - internal static string ReservedCmdletCharError { - get { - return ResourceManager.GetString("ReservedCmdletCharError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ReservedCmdletChar. - /// - internal static string ReservedCmdletCharName { - get { - return ResourceManager.GetString("ReservedCmdletCharName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}'. - /// - internal static string ReservedParamsCmdletPrefix { - get { - return ResourceManager.GetString("ReservedParamsCmdletPrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reserved Parameters. - /// - internal static string ReservedParamsCommonName { - get { - return ResourceManager.GetString("ReservedParamsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks for reserved parameters in function definitions. If these parameters are defined by the user, an error generally occurs.. - /// - internal static string ReservedParamsDescription { - get { - return ResourceManager.GetString("ReservedParamsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' defines the reserved common parameter '{1}'.. - /// - internal static string ReservedParamsError { - get { - return ResourceManager.GetString("ReservedParamsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ReservedParams. - /// - internal static string ReservedParamsName { - get { - return ResourceManager.GetString("ReservedParamsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The script. - /// - internal static string ReservedParamsScriptPrefix { - get { - return ResourceManager.GetString("ReservedParamsScriptPrefix", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to #,(){}[]&/\\$^;:\"'<>|?@`*%+=~. - /// - internal static string ReserverCmdletChars { - get { - return ResourceManager.GetString("ReserverCmdletChars", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ReturnCorrectTypesForDSCFunctions. - /// - internal static string ReturnCorrectTypeDSCFunctionsName { - get { - return ResourceManager.GetString("ReturnCorrectTypeDSCFunctionsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Return Correct Types For DSC Functions. - /// - internal static string ReturnCorrectTypesForDSCFunctionsCommonName { - get { - return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Set function in DSC class and Set-TargetResource in DSC resource must not return anything. Get function in DSC class must return an instance of the DSC class and Get-TargetResource function in DSC resource must return a hashtable. Test function in DSC class and Get-TargetResource function in DSC resource must return a boolean.. - /// - internal static string ReturnCorrectTypesForDSCFunctionsDescription { - get { - return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} function in DSC Class {1} should return object of type {2}. - /// - internal static string ReturnCorrectTypesForDSCFunctionsNoTypeError { - get { - return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsNoTypeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} function in DSC Class {1} should return object of type {2} instead of type {3}. - /// - internal static string ReturnCorrectTypesForDSCFunctionsWrongTypeError { - get { - return ResourceManager.GetString("ReturnCorrectTypesForDSCFunctionsWrongTypeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0} function in DSC Resource should return object of type {1} instead of {2}. - /// - internal static string ReturnCorrectTypesForGetTestTargetResourceFunctionsDSCResourceError { - get { - return ResourceManager.GetString("ReturnCorrectTypesForGetTestTargetResourceFunctionsDSCResourceError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Set function in DSC Class {0} should not return anything. - /// - internal static string ReturnCorrectTypesForSetFunctionsDSCError { - get { - return ResourceManager.GetString("ReturnCorrectTypesForSetFunctionsDSCError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Set-TargetResource function in DSC Resource should not output anything to the pipeline.. - /// - internal static string ReturnCorrectTypesForSetTargetResourceFunctionsDSCError { - get { - return ResourceManager.GetString("ReturnCorrectTypesForSetTargetResourceFunctionsDSCError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ReviewUnusedParameter. - /// - internal static string ReviewUnusedParameterCommonName { - get { - return ResourceManager.GetString("ReviewUnusedParameterCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ensure all parameters are used within the same script, scriptblock, or function where they are declared.. - /// - internal static string ReviewUnusedParameterDescription { - get { - return ResourceManager.GetString("ReviewUnusedParameterDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter '{0}' has been declared but not used. . - /// - internal static string ReviewUnusedParameterError { - get { - return ResourceManager.GetString("ReviewUnusedParameterError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ReviewUnusedParameter. - /// - internal static string ReviewUnusedParameterName { - get { - return ResourceManager.GetString("ReviewUnusedParameterName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ScriptDefinition. - /// - internal static string ScriptDefinitionName { - get { - return ResourceManager.GetString("ScriptDefinitionName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to www.sharepoint.com. - /// - internal static string SharepointURL { - get { - return ResourceManager.GetString("SharepointURL", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Should Process. - /// - internal static string ShouldProcessCommonName { - get { - return ResourceManager.GetString("ShouldProcessCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that if the SupportsShouldProcess is present, the function calls ShouldProcess/ShouldContinue and vice versa. Scripts with one or the other but not both will generally run into an error or unexpected behavior.. - /// - internal static string ShouldProcessDescription { - get { - return ResourceManager.GetString("ShouldProcessDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' has the ShouldProcess attribute but does not call ShouldProcess/ShouldContinue.. - /// - internal static string ShouldProcessErrorHasAttribute { - get { - return ResourceManager.GetString("ShouldProcessErrorHasAttribute", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A script block has the ShouldProcess attribute but does not call ShouldProcess/ShouldContinue.. - /// - internal static string ShouldProcessErrorHasAttributeSB { - get { - return ResourceManager.GetString("ShouldProcessErrorHasAttributeSB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' calls ShouldProcess/ShouldContinue but does not have the ShouldProcess attribute.. - /// - internal static string ShouldProcessErrorHasCmdlet { - get { - return ResourceManager.GetString("ShouldProcessErrorHasCmdlet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A script block calls ShouldProcess/ShouldContinue but does not have the ShouldProcess attribute.. - /// - internal static string ShouldProcessErrorHasCmdletSB { - get { - return ResourceManager.GetString("ShouldProcessErrorHasCmdletSB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ShouldProcess. - /// - internal static string ShouldProcessName { - get { - return ResourceManager.GetString("ShouldProcessName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PS. - /// - internal static string SourceName { - get { - return ResourceManager.GetString("SourceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type Not Found. - /// - internal static string TypeNotFoundCommonName { - get { - return ResourceManager.GetString("TypeNotFoundCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Undefined type should not be used. - /// - internal static string TypeNotFoundDescription { - get { - return ResourceManager.GetString("TypeNotFoundDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Type '{0}' is not found. Please check that it is defined.. - /// - internal static string TypeNotFoundError { - get { - return ResourceManager.GetString("TypeNotFoundError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to TypeNotFound. - /// - internal static string TypeNotFoundName { - get { - return ResourceManager.GetString("TypeNotFoundName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cmdlet Verbs. - /// - internal static string UseApprovedVerbsCommonName { - get { - return ResourceManager.GetString("UseApprovedVerbsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Checks that all defined cmdlets use approved verbs. This is in line with PowerShell's best practices.. - /// - internal static string UseApprovedVerbsDescription { - get { - return ResourceManager.GetString("UseApprovedVerbsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}' uses an unapproved verb.. - /// - internal static string UseApprovedVerbsError { - get { - return ResourceManager.GetString("UseApprovedVerbsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseApprovedVerbs. - /// - internal static string UseApprovedVerbsName { - get { - return ResourceManager.GetString("UseApprovedVerbsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use BOM encoding for non-ASCII files. - /// - internal static string UseBOMForUnicodeEncodedFileCommonName { - get { - return ResourceManager.GetString("UseBOMForUnicodeEncodedFileCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to For a file encoded with a format other than ASCII, ensure BOM is present to ensure that any application consuming this file can interpret it correctly.. - /// - internal static string UseBOMForUnicodeEncodedFileDescription { - get { - return ResourceManager.GetString("UseBOMForUnicodeEncodedFileDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Missing BOM encoding for non-ASCII encoded file '{0}'. - /// - internal static string UseBOMForUnicodeEncodedFileError { - get { - return ResourceManager.GetString("UseBOMForUnicodeEncodedFileError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseBOMForUnicodeEncodedFile. - /// - internal static string UseBOMForUnicodeEncodedFileName { - get { - return ResourceManager.GetString("UseBOMForUnicodeEncodedFileName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use Cmdlet Correctly. - /// - internal static string UseCmdletCorrectlyCommonName { - get { - return ResourceManager.GetString("UseCmdletCorrectlyCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cmdlet should be called with the mandatory parameters.. - /// - internal static string UseCmdletCorrectlyDescription { - get { - return ResourceManager.GetString("UseCmdletCorrectlyDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cmdlet '{0}' may be used incorrectly. Please check that all mandatory parameters are supplied.. - /// - internal static string UseCmdletCorrectlyError { - get { - return ResourceManager.GetString("UseCmdletCorrectlyError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseCmdletCorrectly. - /// - internal static string UseCmdletCorrectlyName { - get { - return ResourceManager.GetString("UseCmdletCorrectlyName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use compatible cmdlets. - /// - internal static string UseCompatibleCmdletsCommonName { - get { - return ResourceManager.GetString("UseCompatibleCmdletsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use cmdlets compatible with the given PowerShell version and edition and operating system. - /// - internal static string UseCompatibleCmdletsDescription { - get { - return ResourceManager.GetString("UseCompatibleCmdletsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to '{0}' is not compatible with PowerShell edition '{1}', version '{2}' and OS '{3}'. - /// - internal static string UseCompatibleCmdletsError { - get { - return ResourceManager.GetString("UseCompatibleCmdletsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseCompatibleCmdlets. - /// - internal static string UseCompatibleCmdletsName { - get { - return ResourceManager.GetString("UseCompatibleCmdletsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The command '{0}' is not available by default in PowerShell version '{1}' on platform '{2}'. - /// - internal static string UseCompatibleCommandsCommandError { - get { - return ResourceManager.GetString("UseCompatibleCommandsCommandError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use compatible commands. - /// - internal static string UseCompatibleCommandsCommonName { - get { - return ResourceManager.GetString("UseCompatibleCommandsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use commands compatible with the given PowerShell version and operating system. - /// - internal static string UseCompatibleCommandsDescription { - get { - return ResourceManager.GetString("UseCompatibleCommandsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseCompatibleCommands. - /// - internal static string UseCompatibleCommandsName { - get { - return ResourceManager.GetString("UseCompatibleCommandsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The parameter '{0}' is not available for command '{1}' by default in PowerShell version '{2}' on platform '{3}'. - /// - internal static string UseCompatibleCommandsParameterError { - get { - return ResourceManager.GetString("UseCompatibleCommandsParameterError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use compatible syntax. - /// - internal static string UseCompatibleSyntaxCommonName { - get { - return ResourceManager.GetString("UseCompatibleSyntaxCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use the '{0}' syntax instead for compatibility with PowerShell versions {1}. - /// - internal static string UseCompatibleSyntaxCorrection { - get { - return ResourceManager.GetString("UseCompatibleSyntaxCorrection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use script syntax compatible with the given PowerShell versions. - /// - internal static string UseCompatibleSyntaxDescription { - get { - return ResourceManager.GetString("UseCompatibleSyntaxDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The {0} syntax '{1}' is not available by default in PowerShell versions {2}. - /// - internal static string UseCompatibleSyntaxError { - get { - return ResourceManager.GetString("UseCompatibleSyntaxError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseCompatibleSyntax. - /// - internal static string UseCompatibleSyntaxName { - get { - return ResourceManager.GetString("UseCompatibleSyntaxName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use compatible types. - /// - internal static string UseCompatibleTypesCommonName { - get { - return ResourceManager.GetString("UseCompatibleTypesCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use types compatible with the given PowerShell version and operating system. - /// - internal static string UseCompatibleTypesDescription { - get { - return ResourceManager.GetString("UseCompatibleTypesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The member '{0}' is not available on type '{1}' by default in PowerShell version '{2}' on platform '{3}'. - /// - internal static string UseCompatibleTypesMemberError { - get { - return ResourceManager.GetString("UseCompatibleTypesMemberError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The method '{0}' is not available on type '{1}' by default in PowerShell version '{2}' on platform '{3}'. - /// - internal static string UseCompatibleTypesMethodError { - get { - return ResourceManager.GetString("UseCompatibleTypesMethodError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseCompatibleTypes. - /// - internal static string UseCompatibleTypesName { - get { - return ResourceManager.GetString("UseCompatibleTypesName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type accelerator '{0}' is not available by default in PowerShell version '{1}' on platform '{2}'. - /// - internal static string UseCompatibleTypesTypeAcceleratorError { - get { - return ResourceManager.GetString("UseCompatibleTypesTypeAcceleratorError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The type '{0}' is not available by default in PowerShell version '{1}' on platform '{2}'. - /// - internal static string UseCompatibleTypesTypeError { - get { - return ResourceManager.GetString("UseCompatibleTypesTypeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use consistent indentation. - /// - internal static string UseConsistentIndentationCommonName { - get { - return ResourceManager.GetString("UseConsistentIndentationCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Each statement block should have a consistent indenation.. - /// - internal static string UseConsistentIndentationDescription { - get { - return ResourceManager.GetString("UseConsistentIndentationDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Indentation not consistent. - /// - internal static string UseConsistentIndentationError { - get { - return ResourceManager.GetString("UseConsistentIndentationError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseConsistentIndentation. - /// - internal static string UseConsistentIndentationName { - get { - return ResourceManager.GetString("UseConsistentIndentationName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use whitespaces. - /// - internal static string UseConsistentWhitespaceCommonName { - get { - return ResourceManager.GetString("UseConsistentWhitespaceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Check for whitespace between keyword and open paren/curly, around assigment operator ('='), around arithmetic operators and after separators (',' and ';'). - /// - internal static string UseConsistentWhitespaceDescription { - get { - return ResourceManager.GetString("UseConsistentWhitespaceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space after open brace.. - /// - internal static string UseConsistentWhitespaceErrorAfterOpeningBrace { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorAfterOpeningBrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space before closing brace.. - /// - internal static string UseConsistentWhitespaceErrorBeforeClosingInnerBrace { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorBeforeClosingInnerBrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space before open brace.. - /// - internal static string UseConsistentWhitespaceErrorBeforeOpeningBrace { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorBeforeOpeningBrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space before open parenthesis.. - /// - internal static string UseConsistentWhitespaceErrorBeforeParen { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorBeforeParen", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space before and after binary and assignment operators.. - /// - internal static string UseConsistentWhitespaceErrorOperator { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorOperator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space after a comma.. - /// - internal static string UseConsistentWhitespaceErrorSeparatorComma { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorSeparatorComma", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space after a semicolon.. - /// - internal static string UseConsistentWhitespaceErrorSeparatorSemi { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorSeparatorSemi", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space after pipe.. - /// - internal static string UseConsistentWhitespaceErrorSpaceAfterPipe { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorSpaceAfterPipe", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use space before pipe.. - /// - internal static string UseConsistentWhitespaceErrorSpaceBeforePipe { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorSpaceBeforePipe", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use only 1 whitespace between parameter names or values.. - /// - internal static string UseConsistentWhitespaceErrorSpaceBetweenParameter { - get { - return ResourceManager.GetString("UseConsistentWhitespaceErrorSpaceBetweenParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseConsistentWhitespace. - /// - internal static string UseConsistentWhitespaceName { - get { - return ResourceManager.GetString("UseConsistentWhitespaceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use exact casing of cmdlet/function/parameter name.. - /// - internal static string UseCorrectCasingCommonName { - get { - return ResourceManager.GetString("UseCorrectCasingCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to For better readability and consistency, use the exact casing of the cmdlet/function/parameter.. - /// - internal static string UseCorrectCasingDescription { - get { - return ResourceManager.GetString("UseCorrectCasingDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function/Cmdlet '{0}' does not match its exact casing '{1}'.. - /// - internal static string UseCorrectCasingError { - get { - return ResourceManager.GetString("UseCorrectCasingError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseCorrectCasing. - /// - internal static string UseCorrectCasingName { - get { - return ResourceManager.GetString("UseCorrectCasingName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parameter '{0}' of function/cmdlet '{1}' does not match its exact casing '{2}'.. - /// - internal static string UseCorrectCasingParameterError { - get { - return ResourceManager.GetString("UseCorrectCasingParameterError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Extra Variables. - /// - internal static string UseDeclaredVarsMoreThanAssignmentsCommonName { - get { - return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Ensure declared variables are used elsewhere in the script and not just during assignment.. - /// - internal static string UseDeclaredVarsMoreThanAssignmentsDescription { - get { - return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The variable '{0}' is assigned but never used.. - /// - internal static string UseDeclaredVarsMoreThanAssignmentsError { - get { - return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseDeclaredVarsMoreThanAssignments. - /// - internal static string UseDeclaredVarsMoreThanAssignmentsName { - get { - return ResourceManager.GetString("UseDeclaredVarsMoreThanAssignmentsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use identical mandatory parameters for DSC Get/Test/Set TargetResource functions. - /// - internal static string UseIdenticalMandatoryParametersDSCCommonName { - get { - return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Get/Test/Set TargetResource functions of DSC resource must have the same mandatory parameters.. - /// - internal static string UseIdenticalMandatoryParametersDSCDescription { - get { - return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The '{0}' parameter '{1}' is not present in '{2}' DSC resource function(s).. - /// - internal static string UseIdenticalMandatoryParametersDSCError { - get { - return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseIdenticalMandatoryParametersForDSC. - /// - internal static string UseIdenticalMandatoryParametersDSCName { - get { - return ResourceManager.GetString("UseIdenticalMandatoryParametersDSCName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use Identical Parameters For DSC Test and Set Functions. - /// - internal static string UseIdenticalParametersDSCCommonName { - get { - return ResourceManager.GetString("UseIdenticalParametersDSCCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Test and Set-TargetResource functions of DSC Resource must have the same parameters.. - /// - internal static string UseIdenticalParametersDSCDescription { - get { - return ResourceManager.GetString("UseIdenticalParametersDSCDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Test and Set-TargetResource functions of DSC Resource must have the same parameters.. - /// - internal static string UseIdenticalParametersDSCError { - get { - return ResourceManager.GetString("UseIdenticalParametersDSCError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseIdenticalParametersForDSC. - /// - internal static string UseIdenticalParametersDSCName { - get { - return ResourceManager.GetString("UseIdenticalParametersDSCName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Create hashtables with literal initializers. - /// - internal static string UseLiteralInitilializerForHashtableCommonName { - get { - return ResourceManager.GetString("UseLiteralInitilializerForHashtableCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use literal initializer, @{{}}, for creating a hashtable as they are case-insensitive by default. - /// - internal static string UseLiteralInitilializerForHashtableDescription { - get { - return ResourceManager.GetString("UseLiteralInitilializerForHashtableDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Create hashtables with literal initliazers. - /// - internal static string UseLiteralInitilializerForHashtableError { - get { - return ResourceManager.GetString("UseLiteralInitilializerForHashtableError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseLiteralInitializerForHashtable. - /// - internal static string UseLiteralInitilializerForHashtableName { - get { - return ResourceManager.GetString("UseLiteralInitilializerForHashtableName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use OutputType Correctly. - /// - internal static string UseOutputTypeCorrectlyCommonName { - get { - return ResourceManager.GetString("UseOutputTypeCorrectlyCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The return types of a cmdlet should be declared using the OutputType attribute.. - /// - internal static string UseOutputTypeCorrectlyDescription { - get { - return ResourceManager.GetString("UseOutputTypeCorrectlyDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}' returns an object of type '{1}' but this type is not declared in the OutputType attribute.. - /// - internal static string UseOutputTypeCorrectlyError { - get { - return ResourceManager.GetString("UseOutputTypeCorrectlyError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseOutputTypeCorrectly. - /// - internal static string UseOutputTypeCorrectlyName { - get { - return ResourceManager.GetString("UseOutputTypeCorrectlyName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use process block for command that accepts input from pipeline.. - /// - internal static string UseProcessBlockForPipelineCommandCommonName { - get { - return ResourceManager.GetString("UseProcessBlockForPipelineCommandCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If a command parameter takes its value from the pipeline, the command must use a process block to bind the input objects from the pipeline to that parameter.. - /// - internal static string UseProcessBlockForPipelineCommandDescription { - get { - return ResourceManager.GetString("UseProcessBlockForPipelineCommandDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Command accepts pipeline input but has not defined a process block.. - /// - internal static string UseProcessBlockForPipelineCommandError { - get { - return ResourceManager.GetString("UseProcessBlockForPipelineCommandError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseProcessBlockForPipelineCommand. - /// - internal static string UseProcessBlockForPipelineCommandName { - get { - return ResourceManager.GetString("UseProcessBlockForPipelineCommandName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use PSCredential type.. - /// - internal static string UsePSCredentialTypeCommonName { - get { - return ResourceManager.GetString("UsePSCredentialTypeCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to For PowerShell 4.0 and earlier, a parameter named Credential with type PSCredential must have a credential transformation attribute defined after the PSCredential type attribute. . - /// - internal static string UsePSCredentialTypeDescription { - get { - return ResourceManager.GetString("UsePSCredentialTypeDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Credential parameter in '{0}' must be of type PSCredential. For PowerShell 4.0 and earlier, please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute.. - /// - internal static string UsePSCredentialTypeError { - get { - return ResourceManager.GetString("UsePSCredentialTypeError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Credential parameter found in the script block must be of type PSCredential. For PowerShell 4.0 and earlier please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. . - /// - internal static string UsePSCredentialTypeErrorSB { - get { - return ResourceManager.GetString("UsePSCredentialTypeErrorSB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UsePSCredentialType. - /// - internal static string UsePSCredentialTypeName { - get { - return ResourceManager.GetString("UsePSCredentialTypeName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use ShouldProcess For State Changing Functions. - /// - internal static string UseShouldProcessForStateChangingFunctionsCommonName { - get { - return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Functions that have verbs like New, Start, Stop, Set, Reset, Restart that change system state should support 'ShouldProcess'.. - /// - internal static string UseShouldProcessForStateChangingFunctionsDescrption { - get { - return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsDescrption", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function '{0}' has verb that could change system state. Therefore, the function has to support 'ShouldProcess'.. - /// - internal static string UseShouldProcessForStateChangingFunctionsError { - get { - return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseShouldProcessForStateChangingFunctions. - /// - internal static string UseShouldProcessForStateChangingFunctionsName { - get { - return ResourceManager.GetString("UseShouldProcessForStateChangingFunctionsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cmdlet Singular Noun. - /// - internal static string UseSingularNounsCommonName { - get { - return ResourceManager.GetString("UseSingularNounsCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cmdlet should use singular instead of plural nouns.. - /// - internal static string UseSingularNounsDescription { - get { - return ResourceManager.GetString("UseSingularNounsDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The cmdlet '{0}' uses a plural noun. A singular noun should be used instead.. - /// - internal static string UseSingularNounsError { - get { - return ResourceManager.GetString("UseSingularNounsError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseSingularNouns. - /// - internal static string UseSingularNounsName { - get { - return ResourceManager.GetString("UseSingularNounsName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Missing '{0}' function. DSC Class must implement Get, Set and Test functions.. - /// - internal static string UseStandardDSCFunctionsInClassError { - get { - return ResourceManager.GetString("UseStandardDSCFunctionsInClassError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use Standard Get/Set/Test TargetResource functions in DSC Resource . - /// - internal static string UseStandardDSCFunctionsInResourceCommonName { - get { - return ResourceManager.GetString("UseStandardDSCFunctionsInResourceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to DSC Resource must implement Get, Set and Test-TargetResource functions. DSC Class must implement Get, Set and Test functions.. - /// - internal static string UseStandardDSCFunctionsInResourceDescription { - get { - return ResourceManager.GetString("UseStandardDSCFunctionsInResourceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Missing '{0}' function. DSC Resource must implement Get, Set and Test-TargetResource functions.. - /// - internal static string UseStandardDSCFunctionsInResourceError { - get { - return ResourceManager.GetString("UseStandardDSCFunctionsInResourceError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to StandardDSCFunctionsInResource. - /// - internal static string UseStandardDSCFunctionsInResourceName { - get { - return ResourceManager.GetString("UseStandardDSCFunctionsInResourceName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use SupportsShouldProcess. - /// - internal static string UseSupportsShouldProcessCommonName { - get { - return ResourceManager.GetString("UseSupportsShouldProcessCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Commands typically provide Confirm and Whatif parameters to give more control on its execution in an interactive environment. In PowerShell, a command can use a SupportsShouldProcess attribute to provide this capability. Hence, manual addition of these parameters to a command is discouraged. If a commands need Confirm and Whatif parameters, then it should support ShouldProcess.. - /// - internal static string UseSupportsShouldProcessDescription { - get { - return ResourceManager.GetString("UseSupportsShouldProcessDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Whatif and/or Confirm manually defined in function {0}. Instead, please use SupportsShouldProcess attribute.. - /// - internal static string UseSupportsShouldProcessError { - get { - return ResourceManager.GetString("UseSupportsShouldProcessError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseSupportsShouldProcess. - /// - internal static string UseSupportsShouldProcessName { - get { - return ResourceManager.GetString("UseSupportsShouldProcessName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use the *ToExport module manifest fields.. - /// - internal static string UseToExportFieldsInManifestCommonName { - get { - return ResourceManager.GetString("UseToExportFieldsInManifestCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Replace {0} with {1}. - /// - internal static string UseToExportFieldsInManifestCorrectionDescription { - get { - return ResourceManager.GetString("UseToExportFieldsInManifestCorrectionDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to In a module manifest, AliasesToExport, CmdletsToExport, FunctionsToExport and VariablesToExport fields should not use wildcards or $null in their entries. During module auto-discovery, if any of these entries are missing or $null or wildcard, PowerShell does some potentially expensive work to analyze the rest of the module.. - /// - internal static string UseToExportFieldsInManifestDescription { - get { - return ResourceManager.GetString("UseToExportFieldsInManifestDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Do not use wildcard or $null in this field. Explicitly specify a list for {0}. . - /// - internal static string UseToExportFieldsInManifestError { - get { - return ResourceManager.GetString("UseToExportFieldsInManifestError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseToExportFieldsInManifest. - /// - internal static string UseToExportFieldsInManifestName { - get { - return ResourceManager.GetString("UseToExportFieldsInManifestName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use Type At Variable Assignment. - /// - internal static string UseTypeAtVariableAssignmentCommonName { - get { - return ResourceManager.GetString("UseTypeAtVariableAssignmentCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Types should be specified at variable assignments to maintain readability and maintainability of script.. - /// - internal static string UseTypeAtVariableAssignmentDescription { - get { - return ResourceManager.GetString("UseTypeAtVariableAssignmentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Specify type at the assignment of variable '{0}'. - /// - internal static string UseTypeAtVariableAssignmentError { - get { - return ResourceManager.GetString("UseTypeAtVariableAssignmentError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseTypeAtVariableAssignment. - /// - internal static string UseTypeAtVariableAssignmentName { - get { - return ResourceManager.GetString("UseTypeAtVariableAssignmentName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use 'Using:' scope modifier in RunSpace ScriptBlocks. - /// - internal static string UseUsingScopeModifierInNewRunspacesCommonName { - get { - return ResourceManager.GetString("UseUsingScopeModifierInNewRunspacesCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Replace {0} with {1}. - /// - internal static string UseUsingScopeModifierInNewRunspacesCorrectionDescription { - get { - return ResourceManager.GetString("UseUsingScopeModifierInNewRunspacesCorrectionDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to If a ScriptBlock is intended to be run as a new RunSpace, variables inside it should use 'Using:' scope modifier, or be initialized within the ScriptBlock.. - /// - internal static string UseUsingScopeModifierInNewRunspacesDescription { - get { - return ResourceManager.GetString("UseUsingScopeModifierInNewRunspacesDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The variable '{0}' is not declared within this ScriptBlock, and is missing the 'Using:' scope modifier.. - /// - internal static string UseUsingScopeModifierInNewRunspacesError { - get { - return ResourceManager.GetString("UseUsingScopeModifierInNewRunspacesError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseUsingScopeModifierInNewRunspaces. - /// - internal static string UseUsingScopeModifierInNewRunspacesName { - get { - return ResourceManager.GetString("UseUsingScopeModifierInNewRunspacesName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use UTF8 Encoding For Help File. - /// - internal static string UseUTF8EncodingForHelpFileCommonName { - get { - return ResourceManager.GetString("UseUTF8EncodingForHelpFileCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to PowerShell help file needs to use UTF8 Encoding.. - /// - internal static string UseUTF8EncodingForHelpFileDescription { - get { - return ResourceManager.GetString("UseUTF8EncodingForHelpFileDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to File {0} has to use UTF8 instead of {1} encoding because it is a powershell help file.. - /// - internal static string UseUTF8EncodingForHelpFileError { - get { - return ResourceManager.GetString("UseUTF8EncodingForHelpFileError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseUTF8EncodingForHelpFile. - /// - internal static string UseUTF8EncodingForHelpFileName { - get { - return ResourceManager.GetString("UseUTF8EncodingForHelpFileName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Use verbose message in DSC resource. - /// - internal static string UseVerboseMessageInDSCResourceCommonName { - get { - return ResourceManager.GetString("UseVerboseMessageInDSCResourceCommonName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to It is a best practice to emit informative, verbose messages in DSC resource functions. This helps in debugging issues when a DSC configuration is executed.. - /// - internal static string UseVerboseMessageInDSCResourceDescription { - get { - return ResourceManager.GetString("UseVerboseMessageInDSCResourceDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to There is no call to Write-Verbose in DSC function '{0}'. If you are using Write-Verbose in a helper function, suppress this rule application.. - /// - internal static string UseVerboseMessageInDSCResourceErrorFunction { - get { - return ResourceManager.GetString("UseVerboseMessageInDSCResourceErrorFunction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to UseVerboseMessageInDSCResource. - /// - internal static string UseVerboseMessageInDSCResourceName { - get { - return ResourceManager.GetString("UseVerboseMessageInDSCResourceName", resourceCulture); - } - } - } -} diff --git a/Rules/Strings.resx b/Rules/Strings.resx index 20b04712d..c7645c9cf 100644 --- a/Rules/Strings.resx +++ b/Rules/Strings.resx @@ -1,17 +1,17 @@  - @@ -202,13 +202,13 @@ One Char - For PowerShell 4.0 and earlier, a parameter named Credential with type PSCredential must have a credential transformation attribute defined after the PSCredential type attribute. + For PowerShell 4.0 and earlier, a parameter named Credential with type PSCredential must have a credential transformation attribute defined after the PSCredential type attribute. The Credential parameter in '{0}' must be of type PSCredential. For PowerShell 4.0 and earlier, please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. - The Credential parameter found in the script block must be of type PSCredential. For PowerShell 4.0 and earlier please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. + The Credential parameter found in the script block must be of type PSCredential. For PowerShell 4.0 and earlier please define a credential transformation attribute, e.g. [System.Management.Automation.Credential()], after the PSCredential type attribute. Use PSCredential type. @@ -292,7 +292,7 @@ Password parameters that take in plaintext will expose passwords and compromise the security of your system. - Parameter '{0}' should not use String type but either SecureString or PSCredential, otherwise it increases the chance to to expose this sensitive information. + Parameter '{0}' should not use String type but either SecureString or PSCredential, otherwise it increases the chance to expose this sensitive information. Avoid Using Plain Text For Password Parameter @@ -535,7 +535,7 @@ PSDSC - Use Standard Get/Set/Test TargetResource functions in DSC Resource + Use Standard Get/Set/Test TargetResource functions in DSC Resource DSC Resource must implement Get, Set and Test-TargetResource functions. DSC Class must implement Get, Set and Test functions. @@ -769,7 +769,7 @@ In a module manifest, AliasesToExport, CmdletsToExport, FunctionsToExport and VariablesToExport fields should not use wildcards or $null in their entries. During module auto-discovery, if any of these entries are missing or $null or wildcard, PowerShell does some potentially expensive work to analyze the rest of the module. - Do not use wildcard or $null in this field. Explicitly specify a list for {0}. + Do not use wildcard or $null in this field. Explicitly specify a list for {0}. UseToExportFieldsInManifest @@ -1069,7 +1069,7 @@ '>' is not a comparison operator. Use '-gt' (greater than) or '-ge' (greater or equal). - When switching between different languages it is easy to forget that '>' does not mean 'great than' in PowerShell. + When switching between different languages it is easy to forget that '>' does not mean 'greater than' in PowerShell. Did you mean to use the redirection operator '>'? The comparison operators in PowerShell are '-gt' (greater than) or '-ge' (greater or equal). @@ -1096,7 +1096,7 @@ Use exact casing of cmdlet/function/parameter name. - For better readability and consistency, use the exact casing of the cmdlet/function/parameter. + For better readability and consistency, use consistent casing. Function/Cmdlet '{0}' does not match its exact casing '{1}'. @@ -1104,6 +1104,15 @@ UseCorrectCasing + + Keyword '{0}' does not match the expected case '{1}'. + + + Operator '{0}' does not match the expected case '{1}'. + + + Parameter '{0}' of function/cmdlet '{1}' does not match its exact casing '{2}'. + Use process block for command that accepts input from pipeline. @@ -1129,7 +1138,7 @@ Ensure all parameters are used within the same script, scriptblock, or function where they are declared. - The parameter '{0}' has been declared but not used. + The parameter '{0}' has been declared but not used. ReviewUnusedParameter @@ -1188,7 +1197,43 @@ AvoidUsingBrokenHashAlgorithms - - Parameter '{0}' of function/cmdlet '{1}' does not match its exact casing '{2}'. + + AvoidExclaimOperator + + + Avoid exclaim operator + + + The negation operator ! should not be used for readability purposes. Use -not instead. + + + Avoid using the ! negation operator + + + Replace ! with -not + + + Avoid AllowUnencryptedAuthentication Switch + + + Avoid sending credentials and secrets over unencrypted connections. + + + The insecure AllowUsingUnencryptedAuthentication switch was used. This should be avoided except for compatability with legacy systems. + + + AvoidUsingAllowUnencryptedAuthentication + + + Avoid reserved words as function names + + + Avoid using reserved words as function names. Using reserved words as function names can cause errors or unexpected behavior in scripts. + + + AvoidReservedWordsAsFunctionNames + + + The reserved word '{0}' was used as a function name. This should be avoided. \ No newline at end of file diff --git a/Rules/UseCmdletCorrectly.cs b/Rules/UseCmdletCorrectly.cs index 0239d95fc..ccec27e0b 100644 --- a/Rules/UseCmdletCorrectly.cs +++ b/Rules/UseCmdletCorrectly.cs @@ -100,7 +100,7 @@ private bool MandatoryParameterExists(CommandAst cmdAst) } // Positional parameters could be mandatory, so we assume all is well - if (Helper.Instance.PositionalParameterUsed(cmdAst) && Helper.Instance.IsKnownCmdletFunctionOrExternalScript(cmdAst)) + if (Helper.Instance.PositionalParameterUsed(cmdAst) && Helper.Instance.IsKnownCmdletFunctionOrExternalScript(cmdAst, out _)) { return true; } diff --git a/Rules/UseConsistentIndentation.cs b/Rules/UseConsistentIndentation.cs index da5cfb8a5..41aa4ef4d 100644 --- a/Rules/UseConsistentIndentation.cs +++ b/Rules/UseConsistentIndentation.cs @@ -163,6 +163,7 @@ caused by tokens that require a closing RParen (which are LParen, AtParen and Do break; case TokenKind.LParen: + AddViolation(token, indentationLevel, diagnosticRecords, ref onNewLine); // When a line starts with a parenthesis and it is not the last non-comment token of that line, // then indentation does not need to be increased. if ((tokenIndex == 0 || tokens[tokenIndex - 1].Kind == TokenKind.NewLine) && @@ -173,7 +174,7 @@ caused by tokens that require a closing RParen (which are LParen, AtParen and Do break; } lParenSkippedIndentation.Push(false); - AddViolation(token, indentationLevel++, diagnosticRecords, ref onNewLine); + indentationLevel++; break; case TokenKind.Pipe: @@ -563,11 +564,6 @@ private static int ClipNegative(int x) return x > 0 ? x : 0; } - private int GetIndentationColumnNumber(int indentationLevel) - { - return GetIndentation(indentationLevel) + 1; - } - private int GetIndentation(int indentationLevel) { // todo if condition can be evaluated during rule configuration diff --git a/Rules/UseConsistentWhitespace.cs b/Rules/UseConsistentWhitespace.cs index 13613ae74..7f7550ffe 100644 --- a/Rules/UseConsistentWhitespace.cs +++ b/Rules/UseConsistentWhitespace.cs @@ -257,6 +257,9 @@ private IEnumerable FindOpenBraceViolations(TokenOperations to private IEnumerable FindInnerBraceViolations(TokenOperations tokenOperations) { + // Ranges which represent braced member access. Tokens within these ranges should be + // excluded from formatting. + var exclusionRanges = tokenOperations.GetBracedMemberAccessRanges(); foreach (var lCurly in tokenOperations.GetTokenNodes(TokenKind.LCurly)) { if (lCurly.Next == null @@ -264,6 +267,10 @@ private IEnumerable FindInnerBraceViolations(TokenOperations t || lCurly.Next.Value.Kind == TokenKind.NewLine || lCurly.Next.Value.Kind == TokenKind.LineContinuation || lCurly.Next.Value.Kind == TokenKind.RCurly + || exclusionRanges.Any(range => + lCurly.Value.Extent.StartOffset >= range.Item1 && + lCurly.Value.Extent.EndOffset <= range.Item2 + ) ) { continue; @@ -290,6 +297,10 @@ private IEnumerable FindInnerBraceViolations(TokenOperations t || rCurly.Previous.Value.Kind == TokenKind.NewLine || rCurly.Previous.Value.Kind == TokenKind.LineContinuation || rCurly.Previous.Value.Kind == TokenKind.AtCurly + || exclusionRanges.Any(range => + rCurly.Value.Extent.StartOffset >= range.Item1 && + rCurly.Value.Extent.EndOffset <= range.Item2 + ) ) { continue; @@ -396,8 +407,17 @@ private IEnumerable FindParameterViolations(Ast ast) testAst => testAst is CommandAst, true); foreach (CommandAst commandAst in commandAsts) { + /// When finding all the command parameter elements, there is no guarantee that + /// we will read them from the AST in the order they appear in the script (in token + /// order). So we first sort the tokens by their starting line number, followed by + /// their starting column number. List commandParameterAstElements = commandAst.FindAll( - testAst => testAst.Parent == commandAst, searchNestedScriptBlocks: false).ToList(); + testAst => testAst.Parent == commandAst, searchNestedScriptBlocks: false + ).OrderBy( + e => e.Extent.StartLineNumber + ).ThenBy( + e => e.Extent.StartColumnNumber + ).ToList(); for (int i = 0; i < commandParameterAstElements.Count - 1; i++) { IScriptExtent leftExtent = commandParameterAstElements[i].Extent; @@ -412,8 +432,8 @@ private IEnumerable FindParameterViolations(Ast ast) { int numberOfRedundantWhiteSpaces = rightExtent.StartColumnNumber - expectedStartColumnNumberOfRightExtent; var correction = new CorrectionExtent( - startLineNumber: leftExtent.StartLineNumber, - endLineNumber: leftExtent.EndLineNumber, + startLineNumber: leftExtent.EndLineNumber, + endLineNumber: rightExtent.StartLineNumber, startColumnNumber: leftExtent.EndColumnNumber + 1, endColumnNumber: leftExtent.EndColumnNumber + 1 + numberOfRedundantWhiteSpaces, text: string.Empty, @@ -442,6 +462,7 @@ private IEnumerable FindSeparatorViolations(TokenOperations to { return node.Next != null && node.Next.Value.Kind != TokenKind.NewLine + && node.Next.Value.Kind != TokenKind.Comment && node.Next.Value.Kind != TokenKind.EndOfInput // semicolon can be followed by end of input && !IsPreviousTokenApartByWhitespace(node.Next); }; diff --git a/Rules/UseCorrectCasing.cs b/Rules/UseCorrectCasing.cs index 9d3abd098..f4f2c40b7 100644 --- a/Rules/UseCorrectCasing.cs +++ b/Rules/UseCorrectCasing.cs @@ -22,82 +22,139 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #endif public class UseCorrectCasing : ConfigurableRule { + + /// If true, require the case of all operators to be lowercase. + [ConfigurableRuleProperty(defaultValue: true)] + public bool CheckOperator { get; set; } + + /// If true, require the case of all keywords to be lowercase. + [ConfigurableRuleProperty(defaultValue: true)] + public bool CheckKeyword { get; set; } + + /// If true, require the case of all commands to match their actual casing. + [ConfigurableRuleProperty(defaultValue: true)] + public bool CheckCommands { get; set; } + + private TokenFlags operators = TokenFlags.BinaryOperator | TokenFlags.UnaryOperator; + /// /// AnalyzeScript: Analyze the script to check if cmdlet alias is used. /// public override IEnumerable AnalyzeScript(Ast ast, string fileName) { - if (ast == null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - - IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); + if (ast is null) throw new ArgumentNullException(Strings.NullAstErrorMessage); - // Iterates all CommandAsts and check the command name. - foreach (CommandAst commandAst in commandAsts) + if (CheckOperator || CheckKeyword) { - string commandName = commandAst.GetCommandName(); - - // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. - // You can also review the remark section in following document, - // MSDN: CommandAst.GetCommandName Method - if (commandName == null) + // Iterate tokens to look for the keywords and operators + for (int i = 0; i < Helper.Instance.Tokens.Length; i++) { - continue; - } + Token token = Helper.Instance.Tokens[i]; - var commandInfo = Helper.Instance.GetCommandInfo(commandName); - if (commandInfo == null || commandInfo.CommandType == CommandTypes.ExternalScript || commandInfo.CommandType == CommandTypes.Application) - { - continue; + if (CheckKeyword && ((token.TokenFlags & TokenFlags.Keyword) != 0)) + { + string correctCase = token.Text.ToLowerInvariant(); + if (!token.Text.Equals(correctCase, StringComparison.Ordinal)) + { + yield return GetDiagnosticRecord(token, fileName, correctCase, Strings.UseCorrectCasingKeywordError); + } + continue; + } + + if (CheckOperator && ((token.TokenFlags & operators) != 0)) + { + string correctCase = token.Text.ToLowerInvariant(); + if (!token.Text.Equals(correctCase, StringComparison.Ordinal)) + { + yield return GetDiagnosticRecord(token, fileName, correctCase, Strings.UseCorrectCasingOperatorError); + } + } } + } - var shortName = commandInfo.Name; - var fullyqualifiedName = $"{commandInfo.ModuleName}\\{shortName}"; - var isFullyQualified = commandName.Equals(fullyqualifiedName, StringComparison.OrdinalIgnoreCase); - var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName; + if (CheckCommands) + { + // Iterate command ASTs for command and parameter names + IEnumerable commandAsts = ast.FindAll(testAst => testAst is CommandAst, true); - if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) + // Iterates all CommandAsts and check the command name. + foreach (CommandAst commandAst in commandAsts) { - yield return new DiagnosticRecord( - string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingError, commandName, correctlyCasedCommandName), - GetCommandExtent(commandAst), - GetName(), - DiagnosticSeverity.Warning, - fileName, - commandName, - suggestedCorrections: GetCorrectionExtent(commandAst, correctlyCasedCommandName)); - } + string commandName = commandAst.GetCommandName(); - var commandParameterAsts = commandAst.FindAll( - testAst => testAst is CommandParameterAst, true).Cast(); - Dictionary availableParameters; - try - { - availableParameters = commandInfo.Parameters; - } - // It's a known issue that objects from PowerShell can have a runspace affinity, - // therefore if that happens, we query a fresh object instead of using the cache. - // https://github.com/PowerShell/PowerShell/issues/4003 - catch (InvalidOperationException) - { - commandInfo = Helper.Instance.GetCommandInfo(commandName, bypassCache: true); - availableParameters = commandInfo.Parameters; - } - foreach (var commandParameterAst in commandParameterAsts) - { - var parameterName = commandParameterAst.ParameterName; - if (availableParameters.TryGetValue(parameterName, out ParameterMetadata parameterMetaData)) + // Handles the exception caused by commands like, {& $PLINK $args 2> $TempErrorFile}. + // You can also review the remark section in following document, + // MSDN: CommandAst.GetCommandName Method + if (commandName == null) { - var correctlyCasedParameterName = parameterMetaData.Name; - if (!parameterName.Equals(correctlyCasedParameterName, StringComparison.Ordinal)) - { - yield return new DiagnosticRecord( - string.Format(CultureInfo.CurrentCulture, Strings.UseCorrectCasingParameterError, parameterName, commandName, correctlyCasedParameterName), - GetCommandExtent(commandAst), - GetName(), - DiagnosticSeverity.Warning, - fileName, + continue; + } + + var commandInfo = Helper.Instance.GetCommandInfo(commandName); + if (commandInfo == null || commandInfo.CommandType == CommandTypes.ExternalScript || commandInfo.CommandType == CommandTypes.Application) + { + continue; + } + + var shortName = commandInfo.Name; + var fullyqualifiedName = $"{commandInfo.ModuleName}\\{shortName}"; + var isFullyQualified = commandName.Equals(fullyqualifiedName, StringComparison.OrdinalIgnoreCase); + var correctlyCasedCommandName = isFullyQualified ? fullyqualifiedName : shortName; + + if (!commandName.Equals(correctlyCasedCommandName, StringComparison.Ordinal)) + { + var extent = GetCommandExtent(commandAst); + yield return new DiagnosticRecord( + string.Format( + CultureInfo.CurrentCulture, + Strings.UseCorrectCasingError, commandName, - suggestedCorrections: GetCorrectionExtent(commandParameterAst, correctlyCasedParameterName)); + correctlyCasedCommandName), + extent, + GetName(), + DiagnosticSeverity.Information, + fileName, + correctlyCasedCommandName, + GetCorrectionExtent(commandAst, extent, correctlyCasedCommandName)); + } + + var commandParameterAsts = commandAst.FindAll( + testAst => testAst is CommandParameterAst, true).Cast(); + Dictionary availableParameters; + try + { + availableParameters = commandInfo.Parameters; + } + // It's a known issue that objects from PowerShell can have a runspace affinity, + // therefore if that happens, we query a fresh object instead of using the cache. + // https://github.com/PowerShell/PowerShell/issues/4003 + catch (InvalidOperationException) + { + commandInfo = Helper.Instance.GetCommandInfo(commandName, bypassCache: true); + availableParameters = commandInfo.Parameters; + } + foreach (var commandParameterAst in commandParameterAsts) + { + var parameterName = commandParameterAst.ParameterName; + if (availableParameters.TryGetValue(parameterName, out ParameterMetadata parameterMetaData)) + { + var correctlyCasedParameterName = parameterMetaData.Name; + if (!parameterName.Equals(correctlyCasedParameterName, StringComparison.Ordinal)) + { + yield return new DiagnosticRecord( + string.Format( + CultureInfo.CurrentCulture, + Strings.UseCorrectCasingParameterError, + commandParameterAst.Extent.Text, + commandName, + correctlyCasedParameterName), + commandParameterAst.Extent, + GetName(), + DiagnosticSeverity.Information, + fileName, + correctlyCasedParameterName, + GetCorrectionExtent(commandParameterAst, commandParameterAst.Extent, correctlyCasedParameterName)); + } } } } @@ -124,44 +181,43 @@ private IScriptExtent GetCommandExtent(CommandAst commandAst) return commandAst.Extent; } - private IEnumerable GetCorrectionExtent(CommandAst commandAst, string correctlyCaseName) + private IEnumerable GetCorrectionExtent(Ast ast, IScriptExtent extent, string correctlyCaseName) { - var description = string.Format( - CultureInfo.CurrentCulture, - Strings.UseCorrectCasingDescription, - correctlyCaseName, - correctlyCaseName); - var cmdExtent = GetCommandExtent(commandAst); var correction = new CorrectionExtent( - cmdExtent.StartLineNumber, - cmdExtent.EndLineNumber, - cmdExtent.StartColumnNumber, - cmdExtent.EndColumnNumber, + extent.StartLineNumber, + extent.EndLineNumber, + // For parameters, add +1 because of the dash before the parameter name + (ast is CommandParameterAst ? extent.StartColumnNumber + 1 : extent.StartColumnNumber), + // and do not use EndColumnNumber property, because sometimes it's all of: -ParameterName:$ParameterValue + (ast is CommandParameterAst ? extent.StartColumnNumber + 1 + ((CommandParameterAst)ast).ParameterName.Length : extent.EndColumnNumber), correctlyCaseName, - commandAst.Extent.File, - description); + extent.File, + GetDescription()); yield return correction; } - private IEnumerable GetCorrectionExtent(CommandParameterAst commandParameterAst, string correctlyCaseName) + private DiagnosticRecord GetDiagnosticRecord(Token token, string fileName, string correction, string message) { - var description = string.Format( - CultureInfo.CurrentCulture, - Strings.UseCorrectCasingDescription, - correctlyCaseName, - correctlyCaseName); - var cmdExtent = commandParameterAst.Extent; - var correction = new CorrectionExtent( - cmdExtent.StartLineNumber, - cmdExtent.EndLineNumber, - // +1 because of the dash before the parameter name - cmdExtent.StartColumnNumber + 1, - // do not use EndColumnNumber property as it would not cover the case where the colon syntax: -ParameterName:$ParameterValue - cmdExtent.StartColumnNumber + 1 + commandParameterAst.ParameterName.Length, - correctlyCaseName, - commandParameterAst.Extent.File, - description); - yield return correction; + var extents = new[] + { + new CorrectionExtent( + token.Extent.StartLineNumber, + token.Extent.EndLineNumber, + token.Extent.StartColumnNumber, + token.Extent.EndColumnNumber, + correction, + token.Extent.File, + GetDescription()) + }; + + return new DiagnosticRecord( + string.Format(CultureInfo.CurrentCulture, message, token.Text, correction), + token.Extent, + GetName(), + DiagnosticSeverity.Information, + fileName, + correction, // return the keyword case as the id, so you can turn this off for specific keywords... + suggestedCorrections: extents); } /// diff --git a/Rules/UseDeclaredVarsMoreThanAssignments.cs b/Rules/UseDeclaredVarsMoreThanAssignments.cs index 5a8440ada..b35caafbc 100644 --- a/Rules/UseDeclaredVarsMoreThanAssignments.cs +++ b/Rules/UseDeclaredVarsMoreThanAssignments.cs @@ -143,7 +143,7 @@ private IEnumerable AnalyzeScriptBlockAst(ScriptBlockAst scrip if (assignmentVarAst != null) { // Ignore if variable is global or environment variable or scope is drive qualified variable - if (!Helper.Instance.IsVariableGlobalOrEnvironment(assignmentVarAst, scriptBlockAst) + if (!Helper.Instance.IsVariableGlobalOrEnvironment(assignmentVarAst) && !assignmentVarAst.VariablePath.IsScript && assignmentVarAst.VariablePath.DriveName == null) { diff --git a/Rules/UseIdenticalMandatoryParametersDSC.cs b/Rules/UseIdenticalMandatoryParametersDSC.cs index 713b86814..56acb5b48 100644 --- a/Rules/UseIdenticalMandatoryParametersDSC.cs +++ b/Rules/UseIdenticalMandatoryParametersDSC.cs @@ -32,7 +32,6 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules public class UseIdenticalMandatoryParametersDSC : IDSCResourceRule { private bool isDSCClassCacheInitialized = false; - private Ast ast; private string fileName; private IDictionary propAttrDict; private IEnumerable resourceFunctions; @@ -94,7 +93,6 @@ public IEnumerable AnalyzeDSCResource(Ast ast, string fileName } // Get the keys in the corresponding mof file - this.ast = ast; this.fileName = fileName; this.propAttrDict = GetKeys(fileName); this.resourceFunctions = Helper.Instance.DscResourceFunctions(ast) diff --git a/Rules/UseSingularNouns.cs b/Rules/UseSingularNouns.cs index de9264d35..21a6afa90 100644 --- a/Rules/UseSingularNouns.cs +++ b/Rules/UseSingularNouns.cs @@ -32,13 +32,15 @@ namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules #if !CORECLR [Export(typeof(IScriptRule))] #endif - public class CmdletSingularNoun : IScriptRule + public class CmdletSingularNoun : ConfigurableRule { + [ConfigurableRuleProperty(defaultValue: new string[] { "Data", "Windows" })] + public string[] NounAllowList { get; set; } - private readonly string[] nounAllowList = + public CmdletSingularNoun() { - "Data" - }; + Enable = true; + } /// /// Checks that all defined cmdlet use singular noun @@ -46,7 +48,7 @@ public class CmdletSingularNoun : IScriptRule /// /// /// - public IEnumerable AnalyzeScript(Ast ast, string fileName) + public override IEnumerable AnalyzeScript(Ast ast, string fileName) { if (ast == null) throw new ArgumentNullException(Strings.NullCommandInfoError); @@ -70,7 +72,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) if (pluralizer.CanOnlyBePlural(noun)) { - if (nounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) + if (NounAllowList.Contains(noun, StringComparer.OrdinalIgnoreCase)) { continue; } @@ -88,6 +90,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) GetName(), DiagnosticSeverity.Warning, fileName, + funcAst.Name, suggestedCorrections: new CorrectionExtent[] { GetCorrection(pluralizer, extent, funcAst.Name, noun) }); } } @@ -98,7 +101,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) /// GetName: Retrieves the name of this rule. /// /// The name of this rule - public string GetName() + public override string GetName() { return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.UseSingularNounsName); } @@ -107,7 +110,7 @@ public string GetName() /// GetName: Retrieves the common name of this rule. /// /// The common name of this rule - public string GetCommonName() + public override string GetCommonName() { return string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsCommonName); } @@ -116,7 +119,7 @@ public string GetCommonName() /// GetDescription: Retrieves the description of this rule. /// /// The description of this rule - public string GetDescription() + public override string GetDescription() { return string.Format(CultureInfo.CurrentCulture, Strings.UseSingularNounsDescription); } @@ -124,7 +127,7 @@ public string GetDescription() /// /// GetSourceType: Retrieves the type of the rule: builtin, managed or module. /// - public SourceType GetSourceType() + public override SourceType GetSourceType() { return SourceType.Builtin; } @@ -133,7 +136,7 @@ public SourceType GetSourceType() /// GetSeverity: Retrieves the severity of the rule: error, warning of information. /// /// - public RuleSeverity GetSeverity() + public override RuleSeverity GetSeverity() { return RuleSeverity.Warning; } @@ -141,7 +144,7 @@ public RuleSeverity GetSeverity() /// /// GetSourceName: Retrieves the module/assembly name the rule is from. /// - public string GetSourceName() + public override string GetSourceName() { return string.Format(CultureInfo.CurrentCulture, Strings.SourceName); } diff --git a/Rules/UseToExportFieldsInManifest.cs b/Rules/UseToExportFieldsInManifest.cs index bfd99db9e..9bf612f83 100644 --- a/Rules/UseToExportFieldsInManifest.cs +++ b/Rules/UseToExportFieldsInManifest.cs @@ -69,7 +69,7 @@ public IEnumerable AnalyzeScript(Ast ast, string fileName) foreach(string field in manifestFields) { IScriptExtent extent; - if (!HasAcceptableExportField(field, hashtableAst, ast.Extent.Text, out extent) && extent != null) + if (!HasAcceptableExportField(field, hashtableAst, out extent) && extent != null) { yield return new DiagnosticRecord( GetError(field), @@ -200,10 +200,9 @@ private bool HasNullInExpression(Ast ast) /// /// /// - /// /// /// A boolean value indicating if the the ToExport fields are explicitly set to arrays or not. - private bool HasAcceptableExportField(string key, HashtableAst hast, string scriptText, out IScriptExtent extent) + private bool HasAcceptableExportField(string key, HashtableAst hast, out IScriptExtent extent) { extent = null; foreach (var pair in hast.KeyValuePairs) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..f941d308b --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin) and [PowerShell](https://github.com/PowerShell). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). + + diff --git a/Tests/Build/BuildModule.tests.ps1 b/Tests/Build/BuildModule.tests.ps1 index 542ac2a52..ebc4e2a6a 100644 --- a/Tests/Build/BuildModule.tests.ps1 +++ b/Tests/Build/BuildModule.tests.ps1 @@ -48,60 +48,6 @@ Describe "Build Module Tests" { } } - Context "Test-DotnetInstallation" { - BeforeAll { - $availableVersions = ConvertTo-PortableVersion -strVersion "2.2.400","2.2.401","2.2.405" - $foundVersion = ConvertTo-PortableVersion -strVersion 2.2.402 - $missingVersion = ConvertTo-PortableVersion -strVersion 2.2.410 - } - - It "Test-DotnetInstallation finds a good version" { - Mock Get-InstalledCLIVersion { return $availableVersions } - Mock Get-GlobalJSonSdkVersion { return $foundVersion } - $result = Test-DotnetInstallation -requestedVersion (Get-GlobalJsonSdkVersion) -installedVersions (Get-InstalledCLIVersion) - Assert-MockCalled "Get-InstalledCLIVersion" -Times 1 - Assert-MockCalled "Get-GlobalJsonSdkVersion" -Times 1 - $result | Should -Be $true - } - - It "Test-DotnetInstallation cannot find a good version should return false" { - Mock Get-InstalledCLIVersion { return $availableVersions } - Mock Get-GlobalJSonSdkVersion { return $missingVersion } - $result = Test-DotnetInstallation -requestedVersion (Get-GlobalJsonSdkVersion) -installedVersions (Get-InstalledCLIVersion) - Assert-MockCalled "Get-InstalledCLIVersion" -Times 1 - Assert-MockCalled "Get-GlobalJsonSdkVersion" -Times 1 - $result | Should -Be $false - } - } - - Context "Receive-DotnetInstallScript" { - - Mock -ModuleName Build Receive-File { new-item -type file TestDrive:/dotnet-install.sh } - It "Downloads the proper non-Windows file" { - try { - push-location TestDrive: - Receive-DotnetInstallScript -platform NonWindows - "TestDrive:/dotnet-install.sh" | Should -Exist - } - finally { - Pop-Location - } - } - - Mock -ModuleName Build Receive-File { new-item -type file TestDrive:/dotnet-install.ps1 } - It "Downloads the proper file Windows file" { - try { - push-location TestDrive: - Receive-DotnetInstallScript -platform "Windows" - "TestDrive:/dotnet-install.ps1" | Should -Exist - } - finally { - Pop-Location - } - } - - } - Context "Test result functions" { BeforeAll { $xmlFile = @' diff --git a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 index 0ca6e569f..c3b744803 100644 --- a/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 +++ b/Tests/Engine/GetScriptAnalyzerRule.tests.ps1 @@ -63,7 +63,7 @@ Describe "Test Name parameters" { It "get Rules with no parameters supplied" { $defaultRules = Get-ScriptAnalyzerRule - $expectedNumRules = 68 + $expectedNumRules = 71 if ($PSVersionTable.PSVersion.Major -le 4) { # for PSv3 PSAvoidGlobalAliases is not shipped because @@ -154,17 +154,17 @@ Describe "Test RuleExtension" { Describe "TestSeverity" { It "filters rules based on the specified rule severity" { $rules = Get-ScriptAnalyzerRule -Severity Error - $rules.Count | Should -Be 7 + $rules.Count | Should -Be 8 } It "filters rules based on multiple severity inputs"{ $rules = Get-ScriptAnalyzerRule -Severity Error,Information - $rules.Count | Should -Be 18 + $rules.Count | Should -Be 19 } It "takes lower case inputs" { $rules = Get-ScriptAnalyzerRule -Severity error - $rules.Count | Should -Be 7 + $rules.Count | Should -Be 8 } } diff --git a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 index 06b94cb78..b930c9980 100644 --- a/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 +++ b/Tests/Engine/InvokeScriptAnalyzer.tests.ps1 @@ -372,6 +372,7 @@ Describe "Test CustomizedRulePath" { BeforeAll { $measureRequired = "CommunityAnalyzerRules\Measure-RequiresModules" } + Context "When used correctly" { It "with the module folder path" { $customizedRulePath = Invoke-ScriptAnalyzer $PSScriptRoot\TestScript.ps1 -CustomizedRulePath $PSScriptRoot\CommunityAnalyzerRules | Where-Object { $_.RuleName -eq $measureRequired } @@ -516,7 +517,6 @@ Describe "Test CustomizedRulePath" { } Describe "Test -Fix Switch" { - BeforeAll { $scriptName = "TestScriptWithFixableWarnings.ps1" $testSource = Join-Path $PSScriptRoot $scriptName @@ -561,69 +561,89 @@ Describe "Test -EnableExit Switch" { $pssaPath = (Get-Module PSScriptAnalyzer).Path - & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci -EnableExit" + & $pwshExe -NoProfile -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci -EnableExit" - $LASTEXITCODE | Should -Be 1 + $LASTEXITCODE | Should -Be 1 } - Describe "-ReportSummary switch" { - BeforeAll { - $pssaPath = (Get-Module PSScriptAnalyzer).Path + It "Returns exit code equivalent to number of warnings for multiple piped files" { + if ($IsCoreCLR) + { + $pwshExe = (Get-Process -Id $PID).Path + } + else + { + $pwshExe = 'powershell' + } - if ($IsCoreCLR) - { - $pwshExe = (Get-Process -Id $PID).Path - } - else - { - $pwshExe = 'powershell' - } + $pssaPath = (Get-Module PSScriptAnalyzer).Path - $reportSummaryFor1Warning = '*1 rule violation found. Severity distribution: Error = 0, Warning = 1, Information = 0*' - } + & $pwshExe -NoProfile { + Import-Module $Args[0] + Get-ChildItem $Args[1] | Invoke-ScriptAnalyzer -EnableExit + } -Args $pssaPath, "$PSScriptRoot\RecursionDirectoryTest" - It "prints the correct report summary using the -NoReportSummary switch" { - $result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci -ReportSummary" + $LASTEXITCODE | Should -Be 2 + } +} - "$result" | Should -BeLike $reportSummaryFor1Warning - } - It "does not print the report summary when not using -NoReportSummary switch" { - $result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci" +Describe "-ReportSummary switch" { + BeforeAll { + $pssaPath = (Get-Module PSScriptAnalyzer).Path - "$result" | Should -Not -BeLike $reportSummaryFor1Warning + if ($IsCoreCLR) + { + $pwshExe = (Get-Process -Id $PID).Path + } + else + { + $pwshExe = 'powershell' } + + $reportSummaryFor1Warning = '*1 rule violation found. Severity distribution: Error = 0, Warning = 1, Information = 0*' } - # using statements are only supported in v5+ - Describe "Handles parse errors due to unknown types" -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) { - BeforeAll { - $script = @' - using namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels - using namespace Microsoft.Azure.Commands.Common.Authentication.Abstractions - Import-Module "AzureRm" - class MyClass { [IStorageContext]$StorageContext } # This will result in a parser error due to [IStorageContext] type that comes from the using statement but is not known at parse time + It "prints the correct report summary using the -NoReportSummary switch" { + $result = & $pwshExe -NoProfile -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci -ReportSummary" + + "$result" | Should -BeLike $reportSummaryFor1Warning + } + It "does not print the report summary when not using -NoReportSummary switch" { + $result = & $pwshExe -Command "Import-Module '$pssaPath'; Invoke-ScriptAnalyzer -ScriptDefinition gci" + + "$result" | Should -Not -BeLike $reportSummaryFor1Warning + } +} + +# using statements are only supported in v5+ +Describe "Handles parse errors due to unknown types" -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) { + BeforeAll { + $script = @' + using namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels + using namespace Microsoft.Azure.Commands.Common.Authentication.Abstractions + Import-Module "AzureRm" + class MyClass { [IStorageContext]$StorageContext } # This will result in a parser error due to [IStorageContext] type that comes from the using statement but is not known at parse time '@ - } - It "does not throw and detect one expected warning after the parse error has occured when using -ScriptDefintion parameter set" { - $warnings = Invoke-ScriptAnalyzer -ScriptDefinition $script - $warnings.Count | Should -Be 1 - $warnings.RuleName | Should -Be 'TypeNotFound' - } + } + It "does not throw and detect one expected warning after the parse error has occured when using -ScriptDefintion parameter set" { + $warnings = Invoke-ScriptAnalyzer -ScriptDefinition $script + $warnings.Count | Should -Be 1 + $warnings.RuleName | Should -Be 'TypeNotFound' + } - It "does not throw and detect one expected warning after the parse error has occured when using -Path parameter set" { - $testFilePath = "TestDrive:\testfile.ps1" - Set-Content $testFilePath -Value $script - $warnings = Invoke-ScriptAnalyzer -Path $testFilePath - $warnings.Count | Should -Be 1 - $warnings.RuleName | Should -Be 'TypeNotFound' - } + It "does not throw and detect one expected warning after the parse error has occured when using -Path parameter set" { + $testFilePath = "TestDrive:\testfile.ps1" + Set-Content $testFilePath -Value $script + $warnings = Invoke-ScriptAnalyzer -Path $testFilePath + $warnings.Count | Should -Be 1 + $warnings.RuleName | Should -Be 'TypeNotFound' } +} - Describe 'Handles static Singleton (issue 1182)' -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) { - It 'Does not throw or return diagnostic record' { - $scriptDefinition = 'class T { static [T]$i }; function foo { [CmdletBinding()] param () $script:T.WriteLog() }' - Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -ErrorAction Stop | Should -BeNullOrEmpty - } +Describe 'Handles static Singleton (issue 1182)' -Skip:($testingLibraryUsage -or ($PSVersionTable.PSVersion -lt '5.0')) { + It 'Does not throw or return diagnostic record' { + $scriptDefinition = 'class T { static [T]$i }; function foo { [CmdletBinding()] param () $script:T.WriteLog() }' + Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -ErrorAction Stop | Should -BeNullOrEmpty } } diff --git a/Tests/Engine/ModuleHelp.Tests.ps1 b/Tests/Engine/ModuleHelp.Tests.ps1 index e2dbea8c4..ac40dcf94 100644 --- a/Tests/Engine/ModuleHelp.Tests.ps1 +++ b/Tests/Engine/ModuleHelp.Tests.ps1 @@ -175,7 +175,7 @@ Describe 'Cmdlet parameter help' { ) BEGIN { - $Common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' + $Common = [System.Management.Automation.PSCmdlet]::CommonParameters $parameters = @() } PROCESS { diff --git a/Tests/Engine/RuleSuppression.tests.ps1 b/Tests/Engine/RuleSuppression.tests.ps1 index c014dbc12..2d81f6305 100644 --- a/Tests/Engine/RuleSuppression.tests.ps1 +++ b/Tests/Engine/RuleSuppression.tests.ps1 @@ -56,28 +56,28 @@ Describe "RuleSuppressionWithoutScope" { It "Suppresses rule with extent created using ScriptExtent constructor" { Invoke-ScriptAnalyzer ` - -ScriptDefinition $ruleSuppressionAvoidUsernameAndPassword ` - -IncludeRule "PSAvoidUsingUserNameAndPassWordParams" ` - -OutVariable ruleViolations ` - -SuppressedOnly + -ScriptDefinition $ruleSuppressionAvoidUsernameAndPassword ` + -IncludeRule "PSAvoidUsingUserNameAndPassWordParams" ` + -OutVariable ruleViolations ` + -SuppressedOnly $ruleViolations.Count | Should -Be 1 - } + } } Context "Script" { It "Does not raise violations" { - $suppression = $violations | Where-Object {$_.RuleName -eq "PSProvideCommentHelp" } + $suppression = $violations | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" } $suppression.Count | Should -Be 0 - $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSProvideCommentHelp" } + $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSProvideCommentHelp" } $suppression.Count | Should -Be 0 } } Context "RuleSuppressionID" { It "Only suppress violations for that ID" { - $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidDefaultValueForMandatoryParameter" } + $suppression = $violations | Where-Object { $_.RuleName -eq "PSAvoidDefaultValueForMandatoryParameter" } $suppression.Count | Should -Be 1 - $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidDefaultValueForMandatoryParameter" } + $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidDefaultValueForMandatoryParameter" } $suppression.Count | Should -Be 1 } @@ -93,10 +93,10 @@ function SuppressPwdParam() } '@ Invoke-ScriptAnalyzer ` - -ScriptDefinition $ruleSuppressionIdAvoidPlainTextPassword ` - -IncludeRule "PSAvoidUsingPlainTextForPassword" ` - -OutVariable ruleViolations ` - -SuppressedOnly + -ScriptDefinition $ruleSuppressionIdAvoidPlainTextPassword ` + -IncludeRule "PSAvoidUsingPlainTextForPassword" ` + -OutVariable ruleViolations ` + -SuppressedOnly $ruleViolations.Count | Should -Be 1 } @@ -246,8 +246,165 @@ function MyFunc } } + Context "RuleSuppressionID with named arguments" { + It "Should work with named argument syntax" { + $scriptWithNamedArgs = @' +function SuppressPasswordParam() +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(RuleName="PSAvoidUsingPlainTextForPassword", RuleSuppressionId="password1")] + param( + [string] $password1, + [string] $password2 + ) +} +'@ + + $diagnostics = Invoke-ScriptAnalyzer ` + -ScriptDefinition $scriptWithNamedArgs ` + -IncludeRule "PSAvoidUsingPlainTextForPassword" + $suppressions = Invoke-ScriptAnalyzer ` + -ScriptDefinition $scriptWithNamedArgs ` + -IncludeRule "PSAvoidUsingPlainTextForPassword" ` + -SuppressedOnly + + # There should be one unsuppressed diagnostic (password2) and one suppressed diagnostic (password1) + $diagnostics | Should -HaveCount 1 + $diagnostics[0].RuleName | Should -BeExactly "PSAvoidUsingPlainTextForPassword" + $diagnostics[0].RuleSuppressionID | Should -BeExactly "password2" + + $suppressions | Should -HaveCount 1 + $suppressions[0].RuleName | Should -BeExactly "PSAvoidUsingPlainTextForPassword" + $suppressions[0].RuleSuppressionID | Should -BeExactly "password1" + } + + It "Should work with mixed positional and named argument syntax" { + $scriptWithMixedArgs = @' +function SuppressPasswordParam() +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", Scope="Function")] + param( + [string] $password1, + [string] $password2 + ) +} +'@ + + $diagnostics = Invoke-ScriptAnalyzer ` + -ScriptDefinition $scriptWithMixedArgs ` + -IncludeRule "PSAvoidUsingPlainTextForPassword" + + # All violations should be suppressed since there's no RuleSuppressionID filtering + $diagnostics | Should -HaveCount 0 + } + + It "Should work with custom rule from issue #1686 comment" { + # This test recreates the exact scenario from GitHub issue 1686 comment + # with a custom rule that populates RuleSuppressionID for targeted suppression + + # Custom rule module that creates violations with specific RuleSuppressionIDs + $customRuleScript = @' +function Measure-AvoidFooBarCommand { + [CmdletBinding()] + [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]])] + param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.Language.ScriptBlockAst] + $ScriptBlockAst + ) + + $results = @() + + # Find all command expressions + $commandAsts = $ScriptBlockAst.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.CommandAst] + }, $true) + + foreach ($commandAst in $commandAsts) { + $commandName = $commandAst.GetCommandName() + if ($commandName -match '^(Get-FooBar|Set-FooBar)$') { + # Create a diagnostic with the command name as RuleSuppressionID + $result = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]::new( + "Avoid using $commandName command", + $commandAst.Extent, + 'Measure-AvoidFooBarCommand', + 'Warning', + $null, + $commandName # This becomes the RuleSuppressionID + ) + $results += $result + } + } + + return $results +} + +Export-ModuleMember -Function Measure-AvoidFooBarCommand +'@ + + # Script that uses the custom rule with targeted suppression + $scriptWithCustomRuleSuppression = @' +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('Measure-AvoidFooBarCommand', RuleSuppressionId = 'Get-FooBar', Scope = 'Function', Target = 'Allow-GetFooBar')] +param() + +function Test-BadCommands { + Get-FooBar # Line 6 - Should NOT be suppressed (wrong function) + Set-FooBar # Line 7 - Should NOT be suppressed (different RuleSuppressionID) +} + +function Allow-GetFooBar { + Get-FooBar # Line 11 - Should be suppressed (matches RuleSuppressionId and Target) + Set-FooBar # Line 12 - Should NOT be suppressed (different RuleSuppressionID) +} +'@ + + # Save custom rule to temporary file + $customRuleFile = [System.IO.Path]::GetTempFileName() + $customRuleModuleFile = [System.IO.Path]::ChangeExtension($customRuleFile, '.psm1') + Set-Content -Path $customRuleModuleFile -Value $customRuleScript + + try + { + # Check suppressed violations - this is the key test for our fix + $suppressions = Invoke-ScriptAnalyzer ` + -ScriptDefinition $scriptWithCustomRuleSuppression ` + -CustomRulePath $customRuleModuleFile ` + -SuppressedOnly ` + -ErrorAction SilentlyContinue + + # The core functionality: RuleSuppressionID with named arguments should work for custom rules + # We should have at least one suppressed Get-FooBar violation + $suppressions | Should -Not -BeNullOrEmpty -Because "RuleSuppressionID named arguments should work for custom rules" + + $getFooBarSuppressions = $suppressions | Where-Object { $_.RuleSuppressionID -eq 'Get-FooBar' } + $getFooBarSuppressions | Should -Not -BeNullOrEmpty -Because "Get-FooBar should be suppressed based on RuleSuppressionID" + + # Verify the suppression occurred in the right function (Allow-GetFooBar) + $getFooBarSuppressions | Should -Not -BeNullOrEmpty + $getFooBarSuppressions[0].RuleName | Should -BeExactly 'Measure-AvoidFooBarCommand' + + # Get unsuppressed violations to verify selective suppression + $diagnostics = Invoke-ScriptAnalyzer ` + -ScriptDefinition $scriptWithCustomRuleSuppression ` + -CustomRulePath $customRuleModuleFile ` + -ErrorAction SilentlyContinue + + # Should still have violations for Set-FooBar (different RuleSuppressionID) and Get-FooBar in wrong function + $setFooBarViolations = $diagnostics | Where-Object { $_.RuleSuppressionID -eq 'Set-FooBar' } + $setFooBarViolations | Should -Not -BeNullOrEmpty -Because "Set-FooBar should not be suppressed (different RuleSuppressionID)" + + } + finally + { + Remove-Item -Path $customRuleModuleFile -ErrorAction SilentlyContinue + Remove-Item -Path $customRuleFile -ErrorAction SilentlyContinue + } + } + } + Context "Rule suppression within DSC Configuration definition" { - It "Suppresses rule" -skip:($IsLinux -or $IsMacOS -or ($PSVersionTable.PSVersion.Major -lt 5)) { + It "Suppresses rule" -Skip:($IsLinux -or $IsMacOS -or ($PSVersionTable.PSVersion.Major -lt 5)) { $suppressedRule = Invoke-ScriptAnalyzer -ScriptDefinition $ruleSuppressionInConfiguration -SuppressedOnly $suppressedRule.Count | Should -Be 1 } @@ -281,9 +438,9 @@ function MyFunc Describe "RuleSuppressionWithScope" { Context "FunctionScope" { It "Does not raise violations" { - $suppression = $violations | Where-Object {$_.RuleName -eq "PSAvoidUsingPositionalParameters" } + $suppression = $violations | Where-Object { $_.RuleName -eq "PSAvoidUsingPositionalParameters" } $suppression.Count | Should -Be 0 - $suppression = $violationsUsingScriptDefinition | Where-Object {$_.RuleName -eq "PSAvoidUsingPositionalParameters" } + $suppression = $violationsUsingScriptDefinition | Where-Object { $_.RuleName -eq "PSAvoidUsingPositionalParameters" } $suppression.Count | Should -Be 0 } } @@ -353,4 +510,4 @@ Describe "RuleSuppressionWithScope" { $suppressed.Count | Should -Be 1 } } - } +} diff --git a/Tests/Engine/Settings.tests.ps1 b/Tests/Engine/Settings.tests.ps1 index 2e95bdd04..917b4ed8e 100644 --- a/Tests/Engine/Settings.tests.ps1 +++ b/Tests/Engine/Settings.tests.ps1 @@ -377,4 +377,34 @@ Describe "Settings Class" { @{ Expr = ';)' } ) } + + Context "FindSettingsMode" { + BeforeAll { + $findSettingsMode = ($settingsTypeName -as [type]).GetMethod( + 'FindSettingsMode', + [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Static) + + $outputObject = [System.Object]::new() + } + + It "Should detect hashtable" { + $settings = @{} + $findSettingsMode.Invoke($null, @($settings, $null, [ref]$outputObject)) | Should -Be "Hashtable" + } + + It "Should detect hashtable wrapped by a PSObject" { + $settings = [PSObject]@{} # Force the settings hashtable to be wrapped + $findSettingsMode.Invoke($null, @($settings, $null, [ref]$outputObject)) | Should -Be "Hashtable" + } + + It "Should detect string" { + $settings = "" + $findSettingsMode.Invoke($null, @($settings, $null, [ref]$outputObject)) | Should -Be "File" + } + + It "Should detect string wrapped by a PSObject" { + $settings = [PSObject]"" # Force the settings string to be wrapped + $findSettingsMode.Invoke($null, @($settings, $null, [ref]$outputObject)) | Should -Be "File" + } + } } diff --git a/Tests/Engine/TokenOperations.tests.ps1 b/Tests/Engine/TokenOperations.tests.ps1 index 97fef3958..1bb1d9298 100644 --- a/Tests/Engine/TokenOperations.tests.ps1 +++ b/Tests/Engine/TokenOperations.tests.ps1 @@ -18,4 +18,181 @@ $h = @{ $hashTableAst | Should -BeOfType [System.Management.Automation.Language.HashTableAst] $hashTableAst.Extent.Text | Should -Be '@{ z = "hi" }' } + + Context 'Braced Member Access Ranges' { + + BeforeDiscovery { + $RangeTests = @( + @{ + Name = 'No braced member access' + ScriptDef = '$object.Prop' + ExpectedRanges = @() + } + @{ + Name = 'No braced member access on braced variable name' + ScriptDef = '${object}.Prop' + ExpectedRanges = @() + } + @{ + Name = 'Braced member access' + ScriptDef = '$object.{Prop}' + ExpectedRanges = @( + ,@(8, 14) + ) + } + @{ + Name = 'Braced member access with spaces' + ScriptDef = '$object. { Prop }' + ExpectedRanges = @( + ,@(9, 17) + ) + } + @{ + Name = 'Braced member access with newline' + ScriptDef = "`$object.`n{ Prop }" + ExpectedRanges = @( + ,@(9, 17) + ) + } + @{ + Name = 'Braced member access with comment' + ScriptDef = "`$object. <#comment#>{Prop}" + ExpectedRanges = @( + ,@(20, 26) + ) + } + @{ + Name = 'Braced member access with multi-line comment' + ScriptDef = "`$object. <#`ncomment`n#>{Prop}" + ExpectedRanges = @( + ,@(22, 28) + ) + } + @{ + Name = 'Braced member access with inline comment' + ScriptDef = "`$object. #comment`n{Prop}" + ExpectedRanges = @( + ,@(18, 24) + ) + } + @{ + Name = 'Braced member access with inner curly braces' + ScriptDef = "`$object.{{Prop}}" + ExpectedRanges = @( + ,@(8, 16) + ) + } + @{ + Name = 'Indexed Braced member access' + ScriptDef = "`$object[0].{Prop}" + ExpectedRanges = @( + ,@(11, 17) + ) + } + @{ + Name = 'Parenthesized Braced member access' + ScriptDef = "(`$object).{Prop}" + ExpectedRanges = @( + ,@(10, 16) + ) + } + @{ + Name = 'Chained Braced member access' + ScriptDef = "`$object.{Prop}.{InnerProp}" + ExpectedRanges = @( + ,@(8, 14) + ,@(15, 26) + ) + } + @{ + Name = 'Multiple Braced member access in larger script' + ScriptDef = @' +$var = 1 +$a.prop.{{inner}} +$a.{ + $a.{Prop} +} +'@ + ExpectedRanges = @( + ,@(17, 26) + ,@(30, 47) + ) + } + ) + } + + It 'Should correctly identify range for ' -ForEach $RangeTests { + $tokens = $null + $parseErrors = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput($ScriptDef, [ref] $tokens, [ref] $parseErrors) + $tokenOperations = [Microsoft.Windows.PowerShell.ScriptAnalyzer.TokenOperations]::new($tokens, $scriptAst) + $ranges = $tokenOperations.GetBracedMemberAccessRanges() + $ranges.Count | Should -Be $ExpectedRanges.Count + for ($i = 0; $i -lt $ranges.Count; $i++) { + $ranges[$i].Item1 | Should -Be $ExpectedRanges[$i][0] + $ranges[$i].Item2 | Should -Be $ExpectedRanges[$i][1] + } + } + + It 'Should not identify dot-sourcing as braced member access' { + $scriptText = @' +. {5+5} +$a=4;. {10+15} +'@ + $tokens = $null + $parseErrors = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput($scriptText, [ref] $tokens, [ref] $parseErrors) + $tokenOperations = [Microsoft.Windows.PowerShell.ScriptAnalyzer.TokenOperations]::new($tokens, $scriptAst) + $ranges = $tokenOperations.GetBracedMemberAccessRanges() + $ranges.Count | Should -Be 0 + } + + It 'Should not return a range for an incomplete bracket pair (parse error)' { + $scriptText = @' +$object.{MemberName +'@ + $tokens = $null + $parseErrors = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput($scriptText, [ref] $tokens, [ref] $parseErrors) + $tokenOperations = [Microsoft.Windows.PowerShell.ScriptAnalyzer.TokenOperations]::new($tokens, $scriptAst) + $ranges = $tokenOperations.GetBracedMemberAccessRanges() + $ranges.Count | Should -Be 0 + } + + It 'Should find the correct range for null-conditional braced member access' { + $scriptText = '$object?.{Prop}' + $tokens = $null + $parseErrors = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput($scriptText, [ref] $tokens, [ref] $parseErrors) + $tokenOperations = [Microsoft.Windows.PowerShell.ScriptAnalyzer.TokenOperations]::new($tokens, $scriptAst) + $ranges = $tokenOperations.GetBracedMemberAccessRanges() + $ranges.Count | Should -Be 1 + $ExpectedRanges = @( + ,@(9, 15) + ) + for ($i = 0; $i -lt $ranges.Count; $i++) { + $ranges[$i].Item1 | Should -Be $ExpectedRanges[$i][0] + $ranges[$i].Item2 | Should -Be $ExpectedRanges[$i][1] + } + } -Skip:$($PSVersionTable.PSVersion.Major -lt 7) + + It 'Should find the correct range for nested null-conditional braced member access' { + $scriptText = '$object?.{Prop?.{InnerProp}}' + $tokens = $null + $parseErrors = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput($scriptText, [ref] $tokens, [ref] $parseErrors) + $tokenOperations = [Microsoft.Windows.PowerShell.ScriptAnalyzer.TokenOperations]::new($tokens, $scriptAst) + $ranges = $tokenOperations.GetBracedMemberAccessRanges() + $ranges.Count | Should -Be 1 + $ExpectedRanges = @( + ,@(9, 28) + ) + for ($i = 0; $i -lt $ranges.Count; $i++) { + $ranges[$i].Item1 | Should -Be $ExpectedRanges[$i][0] + $ranges[$i].Item2 | Should -Be $ExpectedRanges[$i][1] + } + } -Skip:$($PSVersionTable.PSVersion.Major -lt 7) + + } + } \ No newline at end of file diff --git a/Tests/Rules/AlignAssignmentStatement.tests.ps1 b/Tests/Rules/AlignAssignmentStatement.tests.ps1 index 9a94f48ce..7558abf88 100644 --- a/Tests/Rules/AlignAssignmentStatement.tests.ps1 +++ b/Tests/Rules/AlignAssignmentStatement.tests.ps1 @@ -75,6 +75,33 @@ $x = @{ } Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Get-Count | Should -Be 0 } + + It "Should ignore if a hashtable has a single key-value pair on a single line" { + $def = @' +$x = @{ 'key'="value" } +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Get-Count | Should -Be 0 + + } + + It "Should ignore if a hashtable has a single key-value pair across multiple lines" { + $def = @' +$x = @{ + 'key'="value" +} +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Get-Count | Should -Be 0 + + } + + It "Should ignore if a hashtable has multiple key-value pairs on a single line" { + $def = @' +$x = @{ 'key'="value"; 'key2'="value2"; 'key3WithLongerName'="value3" } +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Get-Count | Should -Be 0 + + } + } Context "When assignment statements are in DSC Configuration" { diff --git a/Tests/Rules/AvoidAssignmentToAutomaticVariable.tests.ps1 b/Tests/Rules/AvoidAssignmentToAutomaticVariable.tests.ps1 index 50aea15cd..98f82be41 100644 --- a/Tests/Rules/AvoidAssignmentToAutomaticVariable.tests.ps1 +++ b/Tests/Rules/AvoidAssignmentToAutomaticVariable.tests.ps1 @@ -7,70 +7,80 @@ BeforeAll { Describe "AvoidAssignmentToAutomaticVariables" { Context "ReadOnly Variables" { + BeforeDiscovery { + $excpectedSeverityForAutomaticVariablesInPowerShell6 = 'Warning' + if ($PSVersionTable.PSVersion.Major -ge 6) + { + $excpectedSeverityForAutomaticVariablesInPowerShell6 = 'Error' + } - $excpectedSeverityForAutomaticVariablesInPowerShell6 = 'Warning' - if ($PSVersionTable.PSVersion.Major -ge 6) - { - $excpectedSeverityForAutomaticVariablesInPowerShell6 = 'Error' + $testCases_AutomaticVariables = @( + @{ VariableName = '?'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'Error' ; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'ExecutionContext'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'false'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'Home'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'Host'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'PID'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'PSCulture'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'PSEdition'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'PSHome'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'PSUICulture'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'PSVersionTable'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'ShellId'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + @{ VariableName = 'true'; ExpectedSeverity = 'Error'; IsReadOnly = $true } + # Variables introduced only in PowerShell 6+ have a Severity of Warning only + @{ VariableName = 'IsCoreCLR'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } + @{ VariableName = 'IsLinux'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } + @{ VariableName = 'IsMacOS'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } + @{ VariableName = 'IsWindows'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } + @{ VariableName = '_'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'AllNodes'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'Args'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'ConsoleFilename'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'Event'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'EventArgs'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'EventSubscriber'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'ForEach'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'Input'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'Matches'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'MyInvocation'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'NestedPromptLevel'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'Profile'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'PSBoundParameters'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'PsCmdlet'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'PSCommandPath'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'ReportErrorShowExceptionClass'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'ReportErrorShowInnerException'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'ReportErrorShowSource'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'ReportErrorShowStackTrace'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'Sender'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'StackTrace'; ExpectedSeverity = 'Warning' } + @{ VariableName = 'This'; ExpectedSeverity = 'Warning' } + ) + + $testCases_ReadOnlyAutomaticVariables = $testCases_AutomaticVariables | Where-Object { $_.IsReadonly } } - $testCases_AutomaticVariables = @( - @{ VariableName = '?'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'Error' ; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'ExecutionContext'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'false'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'Home'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'Host'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'PID'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'PSCulture'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'PSEdition'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'PSHome'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'PSUICulture'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'PSVersionTable'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'ShellId'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - @{ VariableName = 'true'; ExpectedSeverity = 'Error'; IsReadOnly = $true } - # Variables introduced only in PowerShell 6+ have a Severity of Warning only - @{ VariableName = 'IsCoreCLR'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } - @{ VariableName = 'IsLinux'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } - @{ VariableName = 'IsMacOS'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } - @{ VariableName = 'IsWindows'; ExpectedSeverity = $excpectedSeverityForAutomaticVariablesInPowerShell6; OnlyPresentInCoreClr = $true } - @{ VariableName = '_'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'AllNodes'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'Args'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'ConsoleFilename'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'Event'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'EventArgs'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'EventSubscriber'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'ForEach'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'Input'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'Matches'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'MyInvocation'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'NestedPromptLevel'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'Profile'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'PSBoundParameters'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'PsCmdlet'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'PSCommandPath'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'ReportErrorShowExceptionClass'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'ReportErrorShowInnerException'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'ReportErrorShowSource'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'ReportErrorShowStackTrace'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'Sender'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'StackTrace'; ExpectedSeverity = 'Warning' } - @{ VariableName = 'This'; ExpectedSeverity = 'Warning' } - ) - - $testCases_ReadOnlyAutomaticVariables = $testCases_AutomaticVariables | Where-Object { $_.IsReadonly } - It "Variable produces warning of Severity " -TestCases $testCases_AutomaticVariables { param ($VariableName, $ExpectedSeverity) - $warnings = Invoke-ScriptAnalyzer -ScriptDefinition "`$${VariableName} = 'foo'" -ExcludeRule PSUseDeclaredVarsMoreThanAssignments + [System.Array] $warnings = Invoke-ScriptAnalyzer -ScriptDefinition "`$${VariableName} = 'foo'" -ExcludeRule PSUseDeclaredVarsMoreThanAssignments + $warnings.Count | Should -Be 1 + $warnings.Severity | Should -Be $ExpectedSeverity + $warnings.RuleName | Should -Be $ruleName + } + + It "Using Variable as foreach assignment produces warning of Severity " -TestCases $testCases_AutomaticVariables { + param ($VariableName, $ExpectedSeverity) + + [System.Array] $warnings = Invoke-ScriptAnalyzer -ScriptDefinition "foreach (`$$VariableName in `$foo) {}" $warnings.Count | Should -Be 1 $warnings.Severity | Should -Be $ExpectedSeverity $warnings.RuleName | Should -Be $ruleName } - It "Using Variable as parameter name produces warning of Severity error" -TestCases $testCases_AutomaticVariables { + It "Using Variable as parameter name produces warning of Severity " -TestCases $testCases_AutomaticVariables { param ($VariableName, $ExpectedSeverity) [System.Array] $warnings = Invoke-ScriptAnalyzer -ScriptDefinition "function foo{Param(`$$VariableName)}" -ExcludeRule PSReviewUnusedParameter @@ -79,7 +89,7 @@ Describe "AvoidAssignmentToAutomaticVariables" { $warnings.RuleName | Should -Be $ruleName } - It "Using Variable as parameter name in param block produces warning of Severity error" -TestCases $testCases_AutomaticVariables { + It "Using Variable as parameter name in param block produces warning of Severity " -TestCases $testCases_AutomaticVariables { param ($VariableName, $ExpectedSeverity) [System.Array] $warnings = Invoke-ScriptAnalyzer -ScriptDefinition "function foo(`$$VariableName){}" @@ -93,6 +103,13 @@ Describe "AvoidAssignmentToAutomaticVariables" { $warnings.Count | Should -Be 0 } + It "Does not flag true or false being used in ValidateSet" { + # All other read-only automatic variables cannot be used in ValidateSet + # they result in a ParseError. $true and $false are permitted however. + [System.Array] $warnings = Invoke-ScriptAnalyzer -ScriptDefinition 'param([ValidateSet($true,$false)]$MyVar)$MyVar' -ExcludeRule PSReviewUnusedParameter + $warnings.Count | Should -Be 0 + } + It "Does not throw a NullReferenceException when using assigning a .Net property to a .Net property (Bug in 1.17.0 - issue 1007)" { Invoke-ScriptAnalyzer -ScriptDefinition '[foo]::bar = [baz]::qux' -ErrorAction Stop } @@ -133,6 +150,29 @@ Describe "AvoidAssignmentToAutomaticVariables" { } } } + } + + Context 'Suppression' { + BeforeDiscovery { + $testCases_RuleSuppression = @( + @{ VariableName = 'true'; Type = 'ReadOnlyAutomaticVariableError' } + @{ VariableName = 'IsWindows'; Type = 'ReadOnlyAutomaticVariableIntroducedInPowerShell6_0Error' } + @{ VariableName = 'ForEach'; Type = 'WritableAutomaticVariableError' } + ) + } + + It 'Variable of type can be suppressed by RuleSuppressionId' -TestCases $testCases_RuleSuppression { + $scriptDef = @" +function suppressionTest { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('$ruleName', '$VariableName')] + param( + `$$VariableName + ) +} +"@ + $warnings = @(Invoke-ScriptAnalyzer -ScriptDefinition $scriptDef -ExcludeRule PSReviewUnusedParameter) + $warnings.Count | Should -Be 0 + } } } diff --git a/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 b/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 index a087a97f4..32a62f9be 100644 --- a/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 +++ b/Tests/Rules/AvoidDefaultValueForMandatoryParameter.tests.ps1 @@ -6,37 +6,176 @@ BeforeAll { } Describe "AvoidDefaultValueForMandatoryParameter" { - Context "When there are violations" { - It "has 1 provide default value for mandatory parameter violation with CmdletBinding" { - $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'Function foo{ [CmdletBinding()]Param([Parameter(Mandatory)]$Param1=''defaultValue'') }' | - Where-Object { $_.RuleName -eq $ruleName } + + Context "Basic mandatory parameter violations" { + It "should flag mandatory parameter with default value (implicit)" { + $script = 'Function Test { Param([Parameter(Mandatory)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 1 + } + + It "should flag mandatory parameter with default value (explicit true)" { + $script = 'Function Test { Param([Parameter(Mandatory=$true)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 1 + } + + It "should flag mandatory parameter with default value (numeric true)" { + $script = 'Function Test { Param([Parameter(Mandatory=1)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 1 + } + } + + Context "Parameter sets (multiple Parameter attributes)" { + It "should NOT flag parameter mandatory in some but not all parameter sets" { + $script = @' +Function Test { + Param( + [Parameter(Mandatory, ParameterSetName='Set1')] + [Parameter(ParameterSetName='Set2')] + $Param = 'default' + ) +} +'@ + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + + It "should flag parameter mandatory in ALL parameter sets" { + $script = @' +Function Test { + Param( + [Parameter(Mandatory, ParameterSetName='Set1')] + [Parameter(Mandatory, ParameterSetName='Set2')] + $Param = 'default' + ) +} +'@ + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } $violations.Count | Should -Be 1 } - It "has 1 provide default value for mandatory=$true parameter violation without CmdletBinding" { - $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'Function foo{ Param([Parameter(Mandatory=$true)]$Param1=''defaultValue'') }' | - Where-Object { $_.RuleName -eq $ruleName } + It "should handle mixed mandatory/non-mandatory in multiple parameter sets" { + $script = @' +Function Test { + Param( + [Parameter(Mandatory=$true, ParameterSetName='Set1')] + [Parameter(Mandatory=$false, ParameterSetName='Set2')] + [Parameter(ParameterSetName='Set3')] + $Param = 'default' + ) +} +'@ + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + } + + Context "Script-level param blocks" { + It "should flag mandatory parameters with defaults in script-level param blocks" { + $script = @' +Param( + [Parameter(Mandatory)] + $ScriptParam = 'default' +) +'@ + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } $violations.Count | Should -Be 1 } - It "returns violations when the parameter is specified as mandatory=1 and has a default value" { - $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'Function foo{ Param([Parameter(Mandatory=1)]$Param1=''defaultValue'') }' | - Where-Object { $_.RuleName -eq $ruleName } + It "should NOT flag non-mandatory parameters in script-level param blocks" { + $script = @' +Param( + [Parameter()] + $ScriptParam = 'default' +) +'@ + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + } + + Context "Non-Parameter attributes" { + It "should NOT flag non-Parameter attributes with Mandatory property" { + $script = 'Function Test { Param([MyCustomAttribute(Mandatory)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + + It "should NOT flag parameters with only validation attributes" { + $script = 'Function Test { Param([ValidateNotNull()]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + } + + Context "Valid scenarios (no violations)" { + It "should NOT flag mandatory parameters without default values" { + $script = 'Function Test { Param([Parameter(Mandatory)]$Param) }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + + It "should NOT flag non-mandatory parameters with default values" { + $script = 'Function Test { Param([Parameter(Mandatory=$false)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + + It "should NOT flag parameters without Parameter attributes" { + $script = 'Function Test { Param($Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + + It "should NOT flag mandatory=0 parameters" { + $script = 'Function Test { Param([Parameter(Mandatory=0)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 0 + } + } + + Context "Complex scenarios" { + It "should handle multiple parameters with mixed violations" { + $script = @' +Function Test { + Param( + [Parameter(Mandatory)]$BadParam = "default", + [Parameter()]$GoodParam = "default", + [Parameter(Mandatory)]$AnotherBadParam = "default" + ) +} +'@ + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 2 + } + + It "should work with CmdletBinding" { + $script = 'Function Test { [CmdletBinding()]Param([Parameter(Mandatory)]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } $violations.Count | Should -Be 1 } } - Context "When there are no violations" { - It "has 1 provide default value for mandatory parameter violation" { - $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'Function foo{ Param([Parameter(Mandatory=$false)]$Param1=''val1'', [Parameter(Mandatory)]$Param2=''val2'', $Param3=''val3'') }' | - Where-Object { $_.RuleName -eq $ruleName } + Context "Edge cases" { + It "should handle empty param blocks gracefully" { + $script = 'Function Test { Param() }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } $violations.Count | Should -Be 0 } - It "returns no violations when the parameter is specified as mandatory=0 and has a default value" { - $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'Function foo{ Param([Parameter(Mandatory=0)]$Param1=''val1'') }' | - Where-Object { $_.RuleName -eq $ruleName } + It "should handle null/empty default values" { + $script = 'Function Test { Param([Parameter(Mandatory)]$Param = $null) }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } + $violations.Count | Should -Be 1 + } + + It "should handle parameters with multiple non-Parameter attributes" { + $script = 'Function Test { Param([ValidateNotNull()][Alias("P")]$Param = "default") }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $script | Where-Object { $_.RuleName -eq $ruleName } $violations.Count | Should -Be 0 } } + } diff --git a/Tests/Rules/AvoidExclaimOperator.tests.ps1 b/Tests/Rules/AvoidExclaimOperator.tests.ps1 new file mode 100644 index 000000000..8307aef5a --- /dev/null +++ b/Tests/Rules/AvoidExclaimOperator.tests.ps1 @@ -0,0 +1,54 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +BeforeAll { + $ruleName = "PSAvoidExclaimOperator" + + $ruleSettings = @{ + Enable = $true + } + $settings = @{ + IncludeRules = @($ruleName) + Rules = @{ $ruleName = $ruleSettings } + } +} + +Describe "AvoidExclaimOperator" { + Context "When the rule is not enabled explicitly" { + It "Should not find violations" { + $def = '!$true' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def + $violations.Count | Should -Be 0 + } + } + + Context "Given a line with the exclaim operator" { + It "Should find one violation" { + $def = '!$true' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings + $violations.Count | Should -Be 1 + } + } + + Context "Given a line with the exclaim operator" { + It "Should replace the exclaim operator with the -not operator" { + $def = '!$true' + $expected = '-not $true' + Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should -Be $expected + } + } + Context "Given a line with the exlaim operator followed by a space" { + It "Should replace the exclaim operator without adding an additional space" { + $def = '! $true' + $expected = '-not $true' + Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should -Be $expected + } + } + Context "Given a line with a string containing an exclamation point" { + It "Should not replace it" { + $def = '$MyVar = "Should not replace!"' + $expected = '$MyVar = "Should not replace!"' + Invoke-Formatter -ScriptDefinition $def -Settings $settings | Should -Be $expected + } + } +} \ No newline at end of file diff --git a/Tests/Rules/AvoidReservedWordsAsFunctionNames.tests.ps1 b/Tests/Rules/AvoidReservedWordsAsFunctionNames.tests.ps1 new file mode 100644 index 000000000..260b9fe3a --- /dev/null +++ b/Tests/Rules/AvoidReservedWordsAsFunctionNames.tests.ps1 @@ -0,0 +1,147 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# Keep in sync with the rule's reserved words list in +# Rules/AvoidReservedWordsAsFunctionNames.cs +$reservedWords = @( + 'begin','break','catch','class','configuration', + 'continue','data','define','do', + 'dynamicparam','else','elseif','end', + 'enum','exit','filter','finally', + 'for','foreach','from','function', + 'if','parallel','param','process', + 'return','sequence','switch', + 'throw','trap','try','type', + 'until','using','var','while','workflow' +) + +$randomCasedReservedWords = @( + 'bEgIN','bReAk','cAtCh','CLasS','cONfiGuRaTioN', + 'cONtiNuE','dAtA','dEFInE','Do', + 'DyNaMiCpArAm','eLsE','eLsEiF','EnD', + 'EnUm','eXiT','fIlTeR','fINaLLy', + 'FoR','fOrEaCh','fROm','fUnCtIoN', + 'iF','pArAlLeL','PaRaM','pRoCeSs', + 'ReTuRn','sEqUeNcE','SwItCh', + 'tHrOw','TrAp','tRy','TyPe', + 'uNtIl','UsInG','VaR','wHiLe','wOrKfLoW' +) + +$functionScopes = @( + "global", "local", "script", "private" +) + +# Generate all combinations of reserved words and function scopes +$scopedReservedWordCases = foreach ($scope in $functionScopes) { + foreach ($word in $reservedWords) { + @{ + Scope = $scope + Name = $word + } + } +} + +# Build variants of reserved words where the reserverd word: +# appearing at the start and end of a function +# name. +$substringReservedWords = $reservedWords | ForEach-Object { + "$($_)A", "A$($_)", $_.Substring(0, $_.Length - 1) +} + +$safeFunctionNames = @( + 'Get-Something','Do-Work','Classify-Data','Begin-Process' +) + +BeforeAll { + $ruleName = 'PSAvoidReservedWordsAsFunctionNames' +} + +Describe 'AvoidReservedWordsAsFunctionNames' { + Context 'When function names are reserved words' { + It 'flags reserved word "<_>" as a violation' -TestCases $reservedWords { + + $scriptDefinition = "function $_ { 'test' }" + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + + $violations.Count | Should -Be 1 + $violations[0].Severity | Should -Be 'Warning' + $violations[0].RuleName | Should -Be $ruleName + # Message text should include the function name as used + $violations[0].Message | Should -Be "The reserved word '$_' was used as a function name. This should be avoided." + # Extent should ideally capture only the function name + $violations[0].Extent.Text | Should -Be $_ + } + + It 'flags the correct extent for a function named Function' { + + $scriptDefinition = "Function Function { 'test' }" + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + + $violations.Count | Should -Be 1 + $violations[0].Severity | Should -Be 'Warning' + $violations[0].RuleName | Should -Be $ruleName + # Message text should include the function name as used + $violations[0].Message | Should -Be "The reserved word 'Function' was used as a function name. This should be avoided." + # Extent should ideally capture only the function name + $violations[0].Extent.Text | Should -Be 'Function' + + # Make sure the extent is the correct `Function` (not the one at the + # very start) + $violations[0].Extent.StartOffset | Should -not -Be 0 + } + + # Functions can have scopes. So function global:function {} should still + # alert. + It 'flags reserved word "" with scope "" as a violation' -TestCases $scopedReservedWordCases { + param($Scope, $Name) + + $scriptDefinition = "function $($Scope):$($Name) { 'test' }" + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + + $violations.Count | Should -Be 1 + $violations[0].Severity | Should -Be 'Warning' + $violations[0].RuleName | Should -Be $ruleName + $violations[0].Message | Should -Be "The reserved word '$Name' was used as a function name. This should be avoided." + $violations[0].Extent.Text | Should -Be "$($Scope):$($Name)" + } + + + It 'detects case-insensitively for "<_>"' -TestCases $randomCasedReservedWords { + $scriptDefinition = "function $_ { }" + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + $violations.Count | Should -Be 1 + $violations[0].Message | Should -Be "The reserved word '$_' was used as a function name. This should be avoided." + } + + It 'reports one finding per offending function' { + $scriptDefinition = 'function class { };function For { };function Safe-Name { };function TRy { }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + + $violations.Count | Should -Be 3 + $violations | ForEach-Object { $_.Severity | Should -Be 'Warning' } + ($violations | Select-Object -ExpandProperty Extent | Select-Object -ExpandProperty Text) | + Sort-Object | + Should -Be @('class','For','TRy') + } + } + + Context 'When there are no violations' { + It 'does not flag safe function name "<_>"' -TestCases $safeFunctionNames { + $scriptDefinition = "function $_ { }" + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + $violations.Count | Should -Be 0 + } + + It 'does not flag when script has no functions' { + $scriptDefinition = '"hello";$x = 1..3 | ForEach-Object { $_ }' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + $violations.Count | Should -Be 0 + } + + It 'does not flag substring-like name "<_>"' -TestCases $substringReservedWords { + $scriptDefinition = "function $_ { }" + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule @($ruleName) + $violations.Count | Should -Be 0 + } + } +} diff --git a/Tests/Rules/AvoidTrailingWhitespace.tests.ps1 b/Tests/Rules/AvoidTrailingWhitespace.tests.ps1 index f130a77c5..e5a45f0d3 100644 --- a/Tests/Rules/AvoidTrailingWhitespace.tests.ps1 +++ b/Tests/Rules/AvoidTrailingWhitespace.tests.ps1 @@ -9,6 +9,9 @@ BeforeAll { $settings = @{ IncludeRules = @($ruleName) + Rules = @{ + $ruleName = @{} + } } } @@ -34,4 +37,26 @@ Describe "AvoidTrailingWhitespace" { $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings Test-CorrectionExtentFromContent $def $violations 1 $Whitespace '' } + + It 'Should be used by Invoke-Formatter, when in settings, replacing trailing ' -TestCases $testCases { + param ( + [string] $Whitespace + ) + # Test also guards against regression where single-character lines, with trailing whitespace + # would be removed entirely. See issues #1757, #1992 + $def = @" +Function Get-Example { + 'Example'$Whitespace +}$Whitespace +"@ + + $expected = @" +Function Get-Example { + 'Example' } +"@ + $formatted = Invoke-Formatter -ScriptDefinition $def -Settings $settings + $formatted | Should -Be $expected + } + +} \ No newline at end of file diff --git a/Tests/Rules/AvoidUsingAllowUnencryptedAuthentication.tests.ps1 b/Tests/Rules/AvoidUsingAllowUnencryptedAuthentication.tests.ps1 new file mode 100644 index 000000000..ca89b280c --- /dev/null +++ b/Tests/Rules/AvoidUsingAllowUnencryptedAuthentication.tests.ps1 @@ -0,0 +1,38 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +BeforeAll { + $settings = @{ + IncludeRules = @('PSAvoidUsingAllowUnencryptedAuthentication') + Rules = @{ + PSAvoidUsingAllowUnencryptedAuthentication = @{ + Enable = $true + } + } + } +} + +Describe "AvoidUsingAllowUnencryptedAuthentication" { + Context "When there are violations" { + It "detects unencrypted authentication violations" { + (Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-WebRequest foo -AllowUnencryptedAuthentication' -Settings $settings).Count | Should -Be 1 + (Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-RestMethod foo -AllowUnencryptedAuthentication' -Settings $settings).Count | Should -Be 1 + (Invoke-ScriptAnalyzer -ScriptDefinition 'iwr foo -AllowUnencryptedAuthentication' -Settings $settings).Count | Should -Be 1 + } + + It "detects arbitrary cmdlets" { + (Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-CustomWebRequest foo -AllowUnencryptedAuthentication' -Settings $settings).Count | Should -Be 1 + } + + } + + Context "When there are no violations" { + It "does not flag safe usage" { + (Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-WebRequest foo' -Settings $settings).Count | Should -Be 0 + } + + It "does not flag cases with unrelated parameters" { + (Invoke-ScriptAnalyzer -ScriptDefinition 'Invoke-WebRequest foo -Method Get' -Settings $settings).Count | Should -Be 0 + } + } +} \ No newline at end of file diff --git a/Tests/Rules/ReviewUnusedParameter.tests.ps1 b/Tests/Rules/ReviewUnusedParameter.tests.ps1 index 0249f9743..9e4202dcf 100644 --- a/Tests/Rules/ReviewUnusedParameter.tests.ps1 +++ b/Tests/Rules/ReviewUnusedParameter.tests.ps1 @@ -20,6 +20,30 @@ Describe "ReviewUnusedParameter" { $Violations.Count | Should -Be 2 } + It "has 1 violation - function with 1 parameter with ValueFromPipeline set to false and `$_ usage inside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline = $false)] $Param1) process {$_}}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 1 + } + + It "has 1 violation - function with 1 parameter with ValueFromPipeline set to false and `$PSItem usage inside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline = $false)] $Param1) process {$PSItem}}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 1 + } + + It "has 1 violation - function with 1 parameter with ValueFromPipeline set to true and `$_ usage outside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline = $true)] $Param1) $_}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 1 + } + + It "has 1 violation - function with 1 parameter with ValueFromPipeline set to true and `$PSItem usage outside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline = $true)] $Param1) $PSItem}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 1 + } + It "has 1 violation - scriptblock with 1 unused parameter" { $ScriptDefinition = '{ param ($Param1) }' $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName @@ -32,6 +56,12 @@ Describe "ReviewUnusedParameter" { $Violations.Count | Should -Be 1 } + It "doesn't traverse scriptblock scope for a random command" { + $ScriptDefinition = '{ param ($Param1) 1..3 | Invoke-Parallel { $Param1 }}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 1 + } + It "violations have correct rule and severity" { $ScriptDefinition = 'function BadFunc1 { param ($Param1, $Param2) $Param1}' $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName @@ -53,6 +83,30 @@ Describe "ReviewUnusedParameter" { $Violations.Count | Should -Be 0 } + It "has no violation - function with 1 parameter with ValueFromPipeline explictly set to true and `$_ usage inside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline = $true)] $Param1) process {$_}}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 0 + } + + It "has no violation - function with 1 parameter with ValueFromPipeline explictly set to true and `$PSItem usage inside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline = $true)] $Param1) process {$PSItem}}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 0 + } + + It "has no violation - function with 1 parameter with ValueFromPipeline implicitly set to true and `$_ usage inside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline)] $Param1) process{$_}}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 0 + } + + It "has no violation - function with 1 parameter with ValueFromPipeline implicitly set to true and `$PSItem usage inside process block" { + $ScriptDefinition = 'function BadFunc1 { param ([Parameter(ValueFromPipeline)] $Param1) process{$PSItem}}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 0 + } + It "has no violations when using PSBoundParameters" { $ScriptDefinition = 'function Bound { param ($Param1) Get-Foo @PSBoundParameters }' $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName @@ -81,6 +135,24 @@ Describe "ReviewUnusedParameter" { $ScriptDefinition = 'function foo { param ($Param1, $param2) $param1; $Param2}' $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName $Violations.Count | Should -Be 0 + } + + It "does traverse scriptblock scope for Foreach-Object" { + $ScriptDefinition = '{ param ($Param1) 1..3 | ForEach-Object { $Param1 }}' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName + $Violations.Count | Should -Be 0 + } + + It "does traverse scriptblock scope for commands added to the traversal list" { + $ScriptDefinition = '{ param ($Param1) Invoke-PSFProtectedCommand { $Param1 } }' + $Violations = Invoke-ScriptAnalyzer -ScriptDefinition $ScriptDefinition -IncludeRule $RuleName -Settings @{ + Rules = @{ + PSReviewUnusedParameter = @{ + CommandsToTraverse = @('Invoke-PSFProtectedCommand') + } + } + } + $Violations.Count | Should -Be 0 } } -} +} \ No newline at end of file diff --git a/Tests/Rules/UseCompatibleCommands.Tests.ps1 b/Tests/Rules/UseCompatibleCommands.Tests.ps1 index d2a8c5b8e..cb49f3c1f 100644 --- a/Tests/Rules/UseCompatibleCommands.Tests.ps1 +++ b/Tests/Rules/UseCompatibleCommands.Tests.ps1 @@ -401,6 +401,9 @@ Describe 'UseCompatibleCommands' { ) IgnoreCommands = @( 'Install-Module' + 'Publish-Module' + 'Register-PSRepository' + 'Unregister-PSRepository' # Some PowerShell profiles have Pester installed by default # So Pester is legitimately flagged 'Describe' diff --git a/Tests/Rules/UseConsistentIndentation.tests.ps1 b/Tests/Rules/UseConsistentIndentation.tests.ps1 index 1b556baed..0d26ff39d 100644 --- a/Tests/Rules/UseConsistentIndentation.tests.ps1 +++ b/Tests/Rules/UseConsistentIndentation.tests.ps1 @@ -11,7 +11,7 @@ Describe "UseConsistentIndentation" { function Invoke-FormatterAssertion { param( [string] $ScriptDefinition, - [string] $ExcpectedScriptDefinition, + [string] $ExpectedScriptDefinition, [int] $NumberOfExpectedWarnings, [hashtable] $Settings ) @@ -19,9 +19,9 @@ Describe "UseConsistentIndentation" { # Unit test just using this rule only $violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -Settings $settings $violations.Count | Should -Be $NumberOfExpectedWarnings -Because $ScriptDefinition - Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings $settings | Should -Be $expected -Because $ScriptDefinition + Invoke-Formatter -ScriptDefinition $scriptDefinition -Settings $settings | Should -Be $ExpectedScriptDefinition -Because $ScriptDefinition # Integration test with all default formatting rules - Invoke-Formatter -ScriptDefinition $scriptDefinition | Should -Be $expected -Because $ScriptDefinition + Invoke-Formatter -ScriptDefinition $scriptDefinition | Should -Be $ExpectedScriptDefinition -Because $ScriptDefinition } } BeforeEach { @@ -177,6 +177,18 @@ function test { '@ Invoke-Formatter -ScriptDefinition $IdempotentScriptDefinition | Should -Be $idempotentScriptDefinition } + + It 'Should find violation in script when LParen is first token on a line and is not followed by Newline' { + $ScriptDefinition = @' + (foo) + (bar) +'@ + $ExpectedScriptDefinition = @' +(foo) +(bar) +'@ + Invoke-FormatterAssertion $ScriptDefinition $ExpectedScriptDefinition 2 $settings + } } Context "When a sub-expression is provided" { diff --git a/Tests/Rules/UseConsistentWhitespace.tests.ps1 b/Tests/Rules/UseConsistentWhitespace.tests.ps1 index 31442ad77..c2013fa31 100644 --- a/Tests/Rules/UseConsistentWhitespace.tests.ps1 +++ b/Tests/Rules/UseConsistentWhitespace.tests.ps1 @@ -212,6 +212,19 @@ $ht = @{ $ruleConfiguration.CheckSeparator = $false $ruleConfiguration.IgnoreAssignmentOperatorInsideHashTable = $true } + + It "Should not find violation if assignment operator is in multi-line hash table and a using statement is present" { + $def = @' +using namespace System.IO + +$ht = @{ + variable = 3 + other = 4 +} +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + It "Should not find violation if assignment operator is in multi-line hash table" { $def = @' $ht = @{ @@ -514,6 +527,48 @@ if ($true) { Get-Item ` } } + Context "CheckSeparator" { + BeforeAll { + $ruleConfiguration.CheckInnerBrace = $false + $ruleConfiguration.CheckOpenBrace = $false + $ruleConfiguration.CheckOpenParen = $false + $ruleConfiguration.CheckOperator = $false + $ruleConfiguration.CheckPipe = $false + $ruleConfiguration.CheckSeparator = $true + } + + It "Should find a violation if there is no space after a comma" { + $def = '$Array = @(1,2)' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -HaveCount 1 + } + + It "Should not find a violation if there is a space after a comma" { + $def = '$Array = @(1, 2)' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null + } + + It "Should not find a violation if there is a new-line after a comma" { + $def = @' +$Array = @( + 1, + 2 +) +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null + } + + It "Should not find a violation if there is a comment after the separator" { + $def = @' +$Array = @( + 'foo', # Comment Line 1 + 'FizzBuzz' # Comment Line 2 +) +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + } + Context "CheckParameter" { BeforeAll { @@ -535,11 +590,17 @@ bar -h i ` Invoke-ScriptAnalyzer -ScriptDefinition "$def" -Settings $settings | Should -Be $null } - It "Should not find no violation if there is always 1 space between parameters except when using colon syntax" { + It "Should not find a violation if there is always 1 space between parameters except when using colon syntax" { $def = 'foo -bar $baz @splattedVariable -bat -parameterName:$parameterValue' Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null } + It "Should not find a violation when redirect operators, spearated by 1 space, are used and not in stream order" { + # Related to Issue #2000 + $def = 'foo 3>&1 1>$null 2>&1' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -Be $null + } + It "Should find 1 violation if there is 1 space too much before a parameter" { $def = 'foo -bar' $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings @@ -578,5 +639,176 @@ bar -h i ` Invoke-Formatter -ScriptDefinition "$def" -Settings $settings | Should -Be "$expected" } + + It "Should fix script when a parameter value is a script block spanning multiple lines" { + $def = {foo { + bar +} -baz} + + $expected = {foo { + bar +} -baz} + Invoke-Formatter -ScriptDefinition "$def" -Settings $settings | + Should -Be "$expected" + } + + It "Should fix script when a parameter value is a hashtable spanning multiple lines" { + $def = {foo @{ + a = 1 +} -baz} + + $expected = {foo @{ + a = 1 +} -baz} + Invoke-Formatter -ScriptDefinition "$def" -Settings $settings | + Should -Be "$expected" + } + + It "Should fix script when a parameter value is an array spanning multiple lines" { + $def = {foo @( + 1 +) -baz} + + $expected = {foo @( + 1 +) -baz} + Invoke-Formatter -ScriptDefinition "$def" -Settings $settings | + Should -Be "$expected" + } + + It "Should fix script when redirects are involved and whitespace is not consistent" { + # Related to Issue #2000 + $def = 'foo 3>&1 1>$null 2>&1' + $expected = 'foo 3>&1 1>$null 2>&1' + Invoke-Formatter -ScriptDefinition $def -Settings $settings | + Should -Be $expected + } + } + + Context "Braced Member Accessor Handling" { + BeforeAll { + $ruleConfiguration.CheckInnerBrace = $true + $ruleConfiguration.CheckOpenBrace = $false + $ruleConfiguration.CheckOpenParen = $false + $ruleConfiguration.CheckOperator = $false + $ruleConfiguration.CheckPipe = $false + $ruleConfiguration.CheckSeparator = $false + $ruleConfiguration.CheckParameter = $false + } + + It 'Should not find a violation for a simple braced member accessor with no whitespace' { + $def = '$variable.{Property}' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a simple braced member accessor with whitespace after dot' { + $def = '$object. {Member}' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a simple braced member accessor with newline after dot' { + $def = "`$object.`n{Member}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a simple braced member accessor with inline comment after dot' { + $def = "`$object.<#comment#>{Member}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a simple braced member accessor with inline comment before dot' { + $def = "`$object<#comment#>.{Member}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a simple braced member accessor with multiple touching inline comment before dot' { + $def = "`$object<#a#><#b#><#c#><#d#>.{Member}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for an indexed braced member accessor' { + $def = "`$object[0].{Member}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a parenthesized braced member accessor' { + $def = "(`$object).{Member}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a string literal braced member accessor' { + $def = "'StringLiteral'.{Length}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for an expandable string literal braced member accessor' { + $def = "`"StringLiteral`".{Length}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a here-string braced member accessor' { + $def = "@' +string +'@.{Length}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a doublequoted here-string braced member accessor' { + $def = "@`" +string +`"@.{Length}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for a type braced member accessor' { + $def = "[System.DayOfWeek].{Assembly}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for an braced member accessor on an identifier' { + $def = "`$Object.Property.{Prop}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for an braced member accessor with nested braces' { + $def = "`$Object.{{Prop}}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + It 'Should not find a violation for an braced member accessor with nested inner dot' { + $def = "`$Object.{`$InnerObject.{Prop}}" + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } + + # Check that dot-sourcing still causes formatting violations + It 'Should find violations for dot-sourcing a script (no space after dot)' { + $def = '.{5+5}' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings + $violations.Count | Should -Be 2 + } + + It 'Should find violations for dot-sourcing a script (space after dot)' { + $def = '. {5+5}' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings + $violations.Count | Should -Be 2 + } + + It 'Should find violations for dot-sourcing a script (Semi-Colon before dot)' { + $def = '$a = 4;. {5+5}' + $violations = Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings + $violations.Count | Should -Be 2 + } + + # PS7 Specific behaviour. QuestionDot token. + It 'Should not find a violation for a null conditional braced member accessor' { + $def = '${Object}?.{Prop}' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } -Skip:$($PSVersionTable.PSVersion.Major -lt 7) + + It 'Should not find a violation for a nested null conditional braced member accessor' { + $def = '${Object}?.{${InnerObject}?.{Prop}}' + Invoke-ScriptAnalyzer -ScriptDefinition $def -Settings $settings | Should -BeNullOrEmpty + } -Skip:$($PSVersionTable.PSVersion.Major -lt 7) + } } diff --git a/Tests/Rules/UseCorrectCasing.tests.ps1 b/Tests/Rules/UseCorrectCasing.tests.ps1 index a5280a6e3..7caafe4e0 100644 --- a/Tests/Rules/UseCorrectCasing.tests.ps1 +++ b/Tests/Rules/UseCorrectCasing.tests.ps1 @@ -3,30 +3,30 @@ Describe "UseCorrectCasing" { It "corrects case of simple cmdlet" { - Invoke-Formatter 'get-childitem' | Should -Be 'Get-ChildItem' + Invoke-Formatter 'get-childitem' | Should -BeExactly 'Get-ChildItem' } It "corrects case of fully qualified cmdlet" { - Invoke-Formatter 'Microsoft.PowerShell.management\get-childitem' | Should -Be 'Microsoft.PowerShell.Management\Get-ChildItem' + Invoke-Formatter 'Microsoft.PowerShell.management\get-childitem' | Should -BeExactly 'Microsoft.PowerShell.Management\Get-ChildItem' } It "corrects case of of cmdlet inside interpolated string" { - Invoke-Formatter '"$(get-childitem)"' | Should -Be '"$(get-childitem)"' + Invoke-Formatter '"$(get-childitem)"' | Should -BeExactly '"$(Get-ChildItem)"' } It "Corrects alias correctly" { - Invoke-Formatter 'Gci' | Should -Be 'gci' - Invoke-Formatter '?' | Should -Be '?' + Invoke-Formatter 'Gci' | Should -BeExactly 'gci' + Invoke-Formatter '?' | Should -BeExactly '?' } It "Does not corrects applications on the PATH" -Skip:($IsLinux -or $IsMacOS) { - Invoke-Formatter 'Cmd' | Should -Be 'Cmd' - Invoke-Formatter 'MORE' | Should -Be 'MORE' + Invoke-Formatter 'Git' | Should -BeExactly 'Git' + Invoke-Formatter 'SSH' | Should -BeExactly 'SSH' } It "Preserves extension of applications on Windows" -Skip:($IsLinux -or $IsMacOS) { - Invoke-Formatter 'cmd.exe' | Should -Be 'cmd.exe' - Invoke-Formatter 'more.com' | Should -Be 'more.com' + Invoke-Formatter 'cmd.exe' | Should -BeExactly 'cmd.exe' + Invoke-Formatter 'more.com' | Should -BeExactly 'more.com' } It "Preserves full application path" { @@ -36,37 +36,38 @@ Describe "UseCorrectCasing" { else { $applicationPath = "${env:WINDIR}\System32\cmd.exe" } - Invoke-Formatter ". $applicationPath" | Should -Be ". $applicationPath" + Invoke-Formatter ". $applicationPath" | Should -BeExactly ". $applicationPath" } - It "Corrects case of script function" { - function Invoke-DummyFunction { } - Invoke-Formatter 'invoke-dummyFunction' | Should -Be 'Invoke-DummyFunction' + # TODO: Can we make this work? + # There is a limitation in the Helper's CommandCache: it doesn't see commands that are (only temporarily) defined in the current scope + It "Corrects case of script function" -Skip { + function global:Invoke-DummyFunction { } + Invoke-Formatter 'invoke-dummyFunction' | Should -BeExactly 'Invoke-DummyFunction' } It "Preserves script path" { $path = Join-Path $TestDrive "$([guid]::NewGuid()).ps1" New-Item -ItemType File -Path $path $scriptDefinition = ". $path" - Invoke-Formatter $scriptDefinition | Should -Be $scriptDefinition + Invoke-Formatter $scriptDefinition | Should -BeExactly $scriptDefinition } It "Preserves UNC script path" -Skip:($IsLinux -or $IsMacOS) { $uncPath = [System.IO.Path]::Combine("\\$(HOSTNAME.EXE)\C$\", $TestDrive, "$([guid]::NewGuid()).ps1") New-Item -ItemType File -Path $uncPath $scriptDefinition = ". $uncPath" - Invoke-Formatter $scriptDefinition | Should -Be $scriptDefinition + Invoke-Formatter $scriptDefinition | Should -BeExactly $scriptDefinition } It "Corrects parameter casing" { - function Invoke-DummyFunction ($ParameterName) { } - - Invoke-Formatter 'Invoke-DummyFunction -parametername $parameterValue' | - Should -Be 'Invoke-DummyFunction -ParameterName $parameterValue' - Invoke-Formatter 'Invoke-DummyFunction -parametername:$parameterValue' | - Should -Be 'Invoke-DummyFunction -ParameterName:$parameterValue' - Invoke-Formatter 'Invoke-DummyFunction -parametername: $parameterValue' | - Should -Be 'Invoke-DummyFunction -ParameterName: $parameterValue' + # Without messing up the spacing or use of semicolons + Invoke-Formatter 'Get-ChildItem -literalpath $parameterValue' | + Should -BeExactly 'Get-ChildItem -LiteralPath $parameterValue' + Invoke-Formatter 'Get-ChildItem -literalpath:$parameterValue' | + Should -BeExactly 'Get-ChildItem -LiteralPath:$parameterValue' + Invoke-Formatter 'Get-ChildItem -literalpath: $parameterValue' | + Should -BeExactly 'Get-ChildItem -LiteralPath: $parameterValue' } It "Should not throw when using parameter name that does not exist" { @@ -75,11 +76,58 @@ Describe "UseCorrectCasing" { It "Does not throw when correcting certain cmdlets (issue 1516)" { $scriptDefinition = 'Get-Content;Test-Path;Get-ChildItem;Get-Content;Test-Path;Get-ChildItem' - $settings = @{ 'Rules' = @{ 'PSUseCorrectCasing' = @{ 'Enable' = $true } } } + $settings = @{ 'Rules' = @{ 'PSUseCorrectCasing' = @{ 'Enable' = $true; CheckCommands = $true; CheckKeywords = $true; CheckOperators = $true } } } { 1..100 | ForEach-Object { $null = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -Settings $settings -ErrorAction Stop } } | Should -Not -Throw } + + It "Corrects uppercase operators" { + Invoke-Formatter '$ENV:PATH -SPLIT ";"' | + Should -BeExactly '$ENV:PATH -split ";"' + } + + It "Corrects mixed case operators" { + Invoke-Formatter '$ENV:PATH -Split ";" -Join ":"' | + Should -BeExactly '$ENV:PATH -split ";" -join ":"' + } + + It "Corrects unary operators" { + Invoke-Formatter '-Split "Hello World"' | + Should -BeExactly '-split "Hello World"' + } + It "Does not break PlusPlus or MinusMinus" { + Invoke-Formatter '$A++; $B--' | + Should -BeExactly '$A++; $B--' + } + + It "Shows relevant diagnostic message for function/command name casing" { + $settings = @{ 'Rules' = @{ 'PSUseCorrectCasing' = @{ 'Enable' = $true; CheckCommands = $true; CheckKeywords = $true; CheckOperators = $true } } } + $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'WHERE-OBJECT Name -EQ "Value"' -Settings $settings + $violations.Count | Should -Be 1 + $violations[0].Message | Should -Be "Function/Cmdlet 'WHERE-OBJECT' does not match its exact casing 'Where-Object'." + } + + It "Shows relevant diagnostic message for parameter casing" { + $settings = @{ 'Rules' = @{ 'PSUseCorrectCasing' = @{ 'Enable' = $true; CheckCommands = $true; CheckKeywords = $true; CheckOperators = $true } } } + $violations = Invoke-ScriptAnalyzer -ScriptDefinition 'Where-Object Name -eq "Value"' -Settings $settings + $violations.Count | Should -Be 1 + $violations[0].Message | Should -Be "Parameter '-eq' of function/cmdlet 'Where-Object' does not match its exact casing 'EQ'." + } + + It "Shows relevant diagnostic message for operator casing" { + $settings = @{ 'Rules' = @{ 'PSUseCorrectCasing' = @{ 'Enable' = $true; CheckCommands = $true; CheckKeywords = $true; CheckOperators = $true } } } + $violations = Invoke-ScriptAnalyzer -ScriptDefinition '$a -EQ 1' -Settings $settings + $violations.Count | Should -Be 1 + $violations[0].Message | Should -Be "Operator '-EQ' does not match the expected case '-eq'." + } + + Context "Inconsistent Keywords" { + It "Corrects keyword case" { + Invoke-Formatter 'ForEach ($x IN $y) { $x }' | + Should -BeExactly 'foreach ($x in $y) { $x }' + } + } } diff --git a/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 b/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 index 983ac017f..113563796 100644 --- a/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 +++ b/Tests/Rules/UseDeclaredVarsMoreThanAssignments.tests.ps1 @@ -58,6 +58,54 @@ function MyFunc2() { Should -Be 0 } + It "does not flag `$PSNativeCommandArgumentPassing variable" { + Invoke-ScriptAnalyzer -ScriptDefinition '$PSNativeCommandArgumentPassing=None' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "does not flag global variable" { + Invoke-ScriptAnalyzer -ScriptDefinition '$global:x=$null' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "does not flag global variable in block" { + Invoke-ScriptAnalyzer -ScriptDefinition '$global:x=$null;{$global:x=$null}' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "does not flag env variable" { + Invoke-ScriptAnalyzer -ScriptDefinition '$env:x=$null' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "does not flag env variable in block" { + Invoke-ScriptAnalyzer -ScriptDefinition '$env:x=$null;{$env:x=$null}' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "does not flag script variable" { + Invoke-ScriptAnalyzer -ScriptDefinition '$script:x=$null' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "does not flag script variable in block" { + Invoke-ScriptAnalyzer -ScriptDefinition '$script:x=$null;{$script:x=$null}' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 0 + } + + It "flags private variable" { + Invoke-ScriptAnalyzer -ScriptDefinition '$private:x=$null' -IncludeRule $violationName | ` + Get-Count | ` + Should -Be 1 + } + It "flags a variable that is defined twice but never used" { Invoke-ScriptAnalyzer -ScriptDefinition '$myvar=1;$myvar=2' -IncludeRule $violationName | ` Get-Count | ` @@ -72,6 +120,11 @@ function MyFunc2() { } Context "When there are no violations" { + It "No warning is issued for assignment without use of preference variable ErrorView" { + $results = Invoke-ScriptAnalyzer -ScriptDefinition '$ErrorView = NormalView' + $results.Count | Should -Be 0 + } + It "returns no violations" { $noViolations.Count | Should -Be 0 } diff --git a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 index 534e6df4d..6d4724236 100644 --- a/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 +++ b/Tests/Rules/UseSingularNounsReservedVerbs.tests.ps1 @@ -32,7 +32,7 @@ Describe "UseSingularNouns" { Context "When function names have nouns from allowlist" { - It "ignores function name ending with Data" { + It "ignores function name ending with Data by default" { $nounViolationScript = @' Function Add-SomeData { @@ -44,6 +44,33 @@ Write-Output "Adding some data" -OutVariable violations $violations.Count | Should -Be 0 } + + It "ignores function name ending with Windows by default" { + $nounViolationScript = @' +Function Test-Windows +{ +Write-Output "Testing Microsoft Windows" +} +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $nounViolationScript ` + -IncludeRule "PSUseSingularNouns" ` + -OutVariable violations + $violations.Count | Should -Be 0 + } + + It "ignores function names defined in settings" { + $nounViolationScript = @' +Function Get-Bananas +{ +Write-Output "Bananas" +} +'@ + Invoke-ScriptAnalyzer -ScriptDefinition $nounViolationScript -Settings @{ + IncludeRules = @("PSUseSingularNouns") + Rules = @{ PSUseSingularNouns = @{ NounAllowList = "Bananas" } } + } | Should -BeNullOrEmpty + } + } Context "When there are no violations" { @@ -86,6 +113,18 @@ Write-Output "Adding some data" $diagnostics.SuggestedCorrections.Text | Should -BeExactly $Correction } } + Context 'Suppression' { + It 'Can be suppressed by RuleSuppressionId' { + $scriptDef = @" +function Get-Elements { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('$nounViolationName', 'Get-Elements')] + param() +} +"@ + $warnings = @(Invoke-ScriptAnalyzer -ScriptDefinition $scriptDef) + $warnings.Count | Should -Be 0 + } + } } Describe "UseApprovedVerbs" { diff --git a/Utils/ReleaseMaker.psm1 b/Utils/ReleaseMaker.psm1 deleted file mode 100644 index ac59dbb32..000000000 --- a/Utils/ReleaseMaker.psm1 +++ /dev/null @@ -1,198 +0,0 @@ -Function Get-SolutionPath -{ - Split-Path $PSScriptRoot -Parent -} - -Function Get-ChangeLogPath -{ - Join-Path (Get-SolutionPath) 'CHANGELOG.MD' -} - -Function Get-EngineProjectPath -{ - Join-Path (Get-SolutionPath) 'Engine' -} - -Function Get-ModuleManifestPath -{ - Join-Path (Get-EngineProjectPath) 'PSScriptAnalyzer.psd1' -} - -Function New-Release -{ - [CmdletBinding()] - param($newVer, $oldVer) - - $isVersionGiven = $true - if ($null -eq $newVer -or $null -eq $oldVer) - { - Write-Warning "Parameters are null. Checking changelog for version..." - $isVersionGiven = $false - } - - $solutionRoot = Get-SolutionPath - $enginePath = Get-EngineProjectPath - $versions = Get-VersionsFromChangeLog - if ($versions.Count -le 2) - { - throw "This edge condition for the number versions less that 2 is not implemented." - } - - if ($isVersionGiven) - { - Function Test-IfNotPresentInChangelog - { - param($extractedVersion, $inputVersion) - if ($extractedVersion -ne $inputVersion) - { - throw ("Version {0} does not exist in changelog. Please update changelog." -f $inputVersion) - } - } - - Test-IfNotPresentInChangelog $versions[0] $newVer - Test-IfNotPresentInChangelog $versions[1] $oldVer - } - else - { - $newVer = $versions[0] - $oldVer = $versions[1] - $caption = "Version Check" - $query = "Is version {0} the next release and version {1} the previous release ?" -f $newVer,$oldVer - [bool] $yesToAll = $false - [bool] $noToAll = $false - - if (!$PSCmdlet.ShouldContinue($query, $caption, $false, [ref] $yesToAll, [ref] $noToAll)) - { - return "Aborting..." - } - } - - # update version - Update-Version $newVer $oldVer $solutionRoot - - # copy release notes from changelog to module manifest - Update-ReleaseNotesInModuleManifest $newVer $oldVer - - # build the module - New-ReleaseBuild -} - -function Get-VersionsFromChangeLog -{ - $moduleManifestPath = Get-ModuleManifestPath - $changelogPath = Get-ChangeLogPath - $matches = [regex]::new("\[(\d+\.\d+\.\d+)\]").Matches((get-content $changelogPath -raw)) - $versions = $matches | ForEach-Object {$_.Groups[1].Value} - $versions -} - -function New-ReleaseBuild -{ - $solutionPath = Get-SolutionPath - Push-Location $solutionPath - try - { - if ( test-path out ) { remove-item out/ -recurse -force } - .\build.ps1 -All -Configuration Release - .\PSCompatibilityCollector\build.ps1 -Clean -Configuration Release - Copy-Item -Recurse .\PSCompatibilityCollector\out\* .\out\ - } - finally - { - Pop-Location - } -} - -function Update-ReleaseNotesInModuleManifest -{ - [CmdletBinding()] - param($newVer, $oldVer) - - $moduleManifestPath = Get-ModuleManifestPath - Write-Verbose ("Module manifest: {0}" -f $moduleManifestPath) - $changelogPath = Get-ChangeLogPath - Write-Verbose ("CHANGELOG: {0}" -f $changelogPath) - $changelogRegexPattern = "##\s\[{0}\].*\n((?:.*\n)+)##\s\[{1}\].*" ` - -f [regex]::Escape($newVer),[regex]::Escape($oldVer) - $changelogRegex = [regex]::new($changelogRegexPattern) - $matches = $changelogRegex.Match((get-content $changelogPath -raw)) - $changelogWithHyperlinks = $matches.Groups[1].Value.Trim() - - Write-Verbose 'CHANGELOG:' - Write-Verbose $changelogWithHyperlinks - - # Remove hyperlinks from changelog to make is suitable for publishing on powershellgallery.com - Write-Verbose "Removing hyperlinks from changelog" - $changelog = Remove-MarkdownHyperlink $changelogWithHyperlinks - Write-Verbose "CHANGELOG without hyperlinks:" - Write-Verbose $changelog - - $releaseNotesPattern = ` - "(?ReleaseNotes\s*=\s*@')(?(?:.*\n)*)(?'@)" - $replacement = "`${releaseNotesBegin}" ` - + [environment]::NewLine ` - + $changelog ` - + [environment]::NewLine ` - + "`${releaseNotesEnd}" - $r = [regex]::new($releaseNotesPattern) - $updatedManifestContent = $r.Replace([System.IO.File]::ReadAllText($moduleManifestPath), $replacement) - Set-ContentUtf8NoBom $moduleManifestPath $updatedManifestContent -} - -function Remove-MarkdownHyperlink -{ - param($markdownContent) - $markdownContent -replace "\[(.*?)\]\(.*?\)",'$1' -} - -function Combine-Path -{ - if ($args.Count -lt 2) - { - throw "give more 1 argument" - } - - $path = Join-Path $args[0] $args[1] - for ($k = 2; $k -lt $args.Count; $k++) - { - $path = Join-Path $path $args[$k] - } - - $path -} - -function Update-Version -{ - param( - [string] $newVer, - [string] $oldVer, - [string] $solutionPath - ) - - $ruleJson = Combine-Path $solutionPath 'Rules' 'Rules.csproj' - $engineJson = Combine-Path $solutionPath 'Engine' 'Engine.csproj' - $pssaManifest = Combine-Path $solutionPath 'Engine' 'PSScriptAnalyzer.psd1' - - Update-PatternInFile $ruleJson '"version": "{0}"' $oldVer $newVer - Update-PatternInFile $ruleJson '"Engine": "{0}"' $oldVer $newVer - Update-PatternInFile $engineJson '"version": "{0}"' $oldVer $newVer - Update-PatternInFile $pssaManifest "ModuleVersion = '{0}'" $oldVer $newVer -} - -function Update-PatternInFile -{ - param ($path, $unformattedPattern, $oldVal, $newVal) - - $content = Get-Content $path - $newcontent = $content -replace ($unformattedPattern -f $oldVal),($unformattedPattern -f $newVal) - Set-ContentUtf8NoBom $path $newcontent -} - -function Set-ContentUtf8NoBom { - param($path, $content) - $utfNoBom = [System.Text.UTF8Encoding]::new($false) - [System.IO.File]::WriteAllLines($path, $content, $utfNoBom) -} - -Export-ModuleMember -Function New-Release -Export-ModuleMember -Function New-ReleaseBuild diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index e311eaa3a..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,55 +0,0 @@ -environment: - PSVersion: 5 - BuildConfiguration: Release - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # For faster CI builds - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: WMF 4 - PowerShellEdition: WindowsPowerShell - PSVersion: 4 - ## Only the tests for WMF4 remain active in AppVeyor due to Azure DevOps not offering such images ## - # - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - # PowerShellEdition: PowerShellCore - # - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - # PowerShellEdition: WindowsPowerShell - # - APPVEYOR_BUILD_WORKER_IMAGE: Ubuntu - # PowerShellEdition: PowerShellCore - -# cache Nuget packages and dotnet CLI cache -cache: - - '%USERPROFILE%\.nuget\packages -> appveyor.yml' - -install: - - ps: if ($env:PowerShellEdition -eq 'WindowsPowerShell') { Import-Module .\tools\appveyor.psm1; Invoke-AppveyorInstall } - - pwsh: if ($env:PowerShellEdition -eq 'PowerShellCore') { Import-Module .\tools\appveyor.psm1; Invoke-AppveyorInstall } - -build_script: - - ps: | - if ( $env:PowerShellEdition -eq 'WindowsPowerShell' ) { - Set-Location $env:APPVEYOR_BUILD_FOLDER - if ( $env:PSVersion -eq "4" ) { # On WMF4: Also build for v3 to check it builds at least since we do not have a WMF3 image - ./build.ps1 -Configuration "$env:BuildConfiguration" -PSVersion 3 - } - ./build.ps1 -Configuration "$env:BuildConfiguration" -PSVersion "$env:PSVersion" - ./PSCompatibilityCollector/build.ps1 -Configuration "$env:BuildConfiguration" -Framework 'net462' - } - - pwsh: | - if ($env:PowerShellEdition -eq 'PowerShellCore') { - Set-Location $env:APPVEYOR_BUILD_FOLDER - ./build.ps1 -Configuration "$env:BuildConfiguration" -PSVersion 7 - ./PSCompatibilityCollector/build.ps1 -Configuration "$env:BuildConfiguration" -Framework 'netcoreapp3.1' - } - -test_script: - - ps: | - if ($env:PowerShellEdition -eq 'WindowsPowerShell') { - Invoke-AppveyorTest -CheckoutPath $env:APPVEYOR_BUILD_FOLDER - } - - pwsh: | - if ($env:PowerShellEdition -eq 'PowerShellCore') { - Import-Module .\tools\appveyor.psm1 # Appveyor does not persist pwsh sessions like it does for ps - Invoke-AppveyorTest -CheckoutPath $env:APPVEYOR_BUILD_FOLDER - } - -# Upload the project along with test results as a zip archive -on_finish: - - ps: Import-Module "${env:APPVEYOR_BUILD_FOLDER}\tools\appveyor.psm1"; Invoke-AppveyorFinish diff --git a/build.ps1 b/build.ps1 index a0e27e798..5dade48fe 100644 --- a/build.ps1 +++ b/build.ps1 @@ -7,7 +7,7 @@ param( [switch]$All, [Parameter(ParameterSetName="BuildOne")] - [ValidateSet(3, 4, 5, 7)] + [ValidateSet(5, 7)] [int]$PSVersion = $PSVersionTable.PSVersion.Major, [Parameter(ParameterSetName="BuildOne")] @@ -32,21 +32,13 @@ param( [Parameter(ParameterSetName='Test')] [switch] $InProcess, - - [Parameter(ParameterSetName='Bootstrap')] - [switch] $Bootstrap, + [string] $WithPowerShell, [Parameter(ParameterSetName='BuildAll')] [switch] $Catalog, [Parameter(ParameterSetName='Package')] - [switch] $BuildNupkg, - - [Parameter(ParameterSetName='Package')] - [switch] $CopyManifest, - - [Parameter(ParameterSetName='Package')] - [switch] $Signed + [switch] $BuildNupkg ) @@ -90,18 +82,16 @@ END { } Start-ScriptAnalyzerBuild @buildArgs } - "Bootstrap" { - Install-DotNet - return - } "Package" { - if($CopyManifest) { - Copy-Manifest -signed:$Signed - } - Start-CreatePackage -signed:$Signed + Start-CreatePackage } "Test" { - Test-ScriptAnalyzer -InProcess:$InProcess + $testArgs = @{ + InProcess = $InProcess + WithPowerShell = $WithPowerShell + Verbose = $verboseWanted + } + Test-ScriptAnalyzer @testArgs return } default { diff --git a/build.psm1 b/build.psm1 index 9f27d99a8..5daba36ba 100644 --- a/build.psm1 +++ b/build.psm1 @@ -7,9 +7,8 @@ $analyzerName = "PSScriptAnalyzer" function Get-AnalyzerVersion { - $csprojPath = [io.path]::Combine($projectRoot,"Engine","Engine.csproj") - $xml = [xml](Get-Content "${csprojPath}") - $xml.SelectSingleNode(".//VersionPrefix")."#text" + [xml]$xml = Get-Content $([io.path]::Combine($projectRoot, "Directory.Build.props")) + $xml.Project.PropertyGroup.ModuleVersion } $analyzerVersion = Get-AnalyzerVersion @@ -29,61 +28,6 @@ function Publish-File } } -# attempt to get the users module directory -function Get-UserModulePath -{ - if ( $IsCoreCLR -and -not $IsWindows ) - { - $platformType = "System.Management.Automation.Platform" -as [Type] - if ( $platformType ) { - ${platformType}::SelectProductNameForDirectory("USER_MODULES") - } - else { - throw "Could not determine users module path" - } - } - else { - "${HOME}/Documents/WindowsPowerShell/Modules" - } -} - - -function Uninstall-ScriptAnalyzer -{ - [CmdletBinding(SupportsShouldProcess)] - param ( $ModulePath = $(Join-Path -Path (Get-UserModulePath) -ChildPath ${analyzerName}) ) - END { - if ( $PSCmdlet.ShouldProcess("$modulePath") ) { - Remove-Item -Recurse -Path "$ModulePath" -Force - } - } -} - -# install script analyzer, by default into the users module path -function Install-ScriptAnalyzer -{ - [CmdletBinding(SupportsShouldProcess)] - param ( $ModulePath = $(Join-Path -Path (Get-UserModulePath) -ChildPath ${analyzerName}) ) - END { - if ( $PSCmdlet.ShouldProcess("$modulePath") ) { - Copy-Item -Recurse -Path "$script:destinationDir" -Destination "$ModulePath\." -Force - } - } -} - -# if script analyzer is installed, remove it -function Uninstall-ScriptAnalyzer -{ - [CmdletBinding(SupportsShouldProcess)] - param ( $ModulePath = $(Join-Path -Path (Get-UserModulePath) -ChildPath ${analyzerName}) ) - END { - if ((Test-Path $ModulePath) -and (Get-Item $ModulePath).PSIsContainer ) - { - Remove-Item -Force -Recurse $ModulePath - } - } -} - # Clean up the build location function Remove-Build { @@ -144,7 +88,7 @@ function Start-ScriptAnalyzerBuild param ( [switch]$All, - [ValidateSet(3, 4, 5, 7)] + [ValidateSet(5, 7)] [int]$PSVersion = $PSVersionTable.PSVersion.Major, [ValidateSet("Debug", "Release")] @@ -157,9 +101,6 @@ function Start-ScriptAnalyzerBuild BEGIN { # don't allow the build to be started unless we have the proper Cli version - # this will not actually install dotnet if it's already present, but it will - # install the proper version - Install-Dotnet if ( -not (Test-SuitableDotnet) ) { $requiredVersion = $script:wantedVersion $foundVersion = Get-InstalledCLIVersion @@ -183,7 +124,7 @@ function Start-ScriptAnalyzerBuild if ( $All ) { # Build all the versions of the analyzer - foreach ($psVersion in 3, 4, 5, 7) { + foreach ($psVersion in 5, 7) { Write-Verbose -Verbose -Message "Configuration: $Configuration PSVersion: $psVersion" Start-ScriptAnalyzerBuild -Configuration $Configuration -PSVersion $psVersion -Verbose:$verboseWanted } @@ -203,13 +144,7 @@ function Start-ScriptAnalyzerBuild $framework = 'net462' if ($PSVersion -eq 7) { - $framework = 'netcoreapp3.1' - } - - # build the appropriate assembly - if ($PSVersion -match "[34]" -and $Framework -ne "net462") - { - throw ("ScriptAnalyzer for PS version '{0}' is not applicable to {1} framework" -f $PSVersion,$Framework) + $framework = 'net8' } Push-Location -Path $projectRoot @@ -218,21 +153,23 @@ function Start-ScriptAnalyzerBuild throw "Not in solution root" } + # "Copy" the module file with the version placeholder replaced + $manifestContent = Get-Content -LiteralPath "$projectRoot\Engine\PSScriptAnalyzer.psd1" -Raw + $newManifestContent = $manifestContent -replace '{{ModuleVersion}}', $analyzerVersion + Set-Content -LiteralPath "$script:destinationDir\PSScriptAnalyzer.psd1" -Encoding utf8 -Value $newManifestContent + $itemsToCopyCommon = @( - "$projectRoot\Engine\PSScriptAnalyzer.psd1", "$projectRoot\Engine\PSScriptAnalyzer.psm1", - "$projectRoot\Engine\ScriptAnalyzer.format.ps1xml", "$projectRoot\Engine\ScriptAnalyzer.types.ps1xml" + "$projectRoot\LICENSE", + "$projectRoot\README.md", + "$projectRoot\SECURITY.md", + "$projectRoot\ThirdPartyNotices.txt", + "$projectRoot\Engine\PSScriptAnalyzer.psm1", + "$projectRoot\Engine\ScriptAnalyzer.format.ps1xml", + "$projectRoot\Engine\ScriptAnalyzer.types.ps1xml" ) switch ($PSVersion) { - 3 - { - $destinationDirBinaries = "$script:destinationDir\PSv3" - } - 4 - { - $destinationDirBinaries = "$script:destinationDir\PSv4" - } 5 { $destinationDirBinaries = "$script:destinationDir" @@ -248,7 +185,7 @@ function Start-ScriptAnalyzerBuild } $buildConfiguration = $Configuration - if ((3, 4, 7) -contains $PSVersion) { + if ($PSVersion -eq 7) { $buildConfiguration = "PSV${PSVersion}${Configuration}" } @@ -357,7 +294,10 @@ function New-Catalog function Test-ScriptAnalyzer { [CmdletBinding()] - param ( [switch] $InProcess ) + param ( + [switch] $InProcess, + [string] $WithPowerShell + ) END { # versions 3 and 4 don't understand versioned module paths, so we need to rename the directory of the version to @@ -386,7 +326,6 @@ function Test-ScriptAnalyzer else { $testModulePath = Join-Path "${projectRoot}" -ChildPath out } - $testResultsFile = "'$(Join-Path ${projectRoot} -childPath TestResults.xml)'" $testScripts = "'${projectRoot}\Tests\Build','${projectRoot}\Tests\Engine','${projectRoot}\Tests\Rules','${projectRoot}\Tests\Documentation'" try { if ( $major -lt 5 ) { @@ -395,13 +334,21 @@ function Test-ScriptAnalyzer $savedModulePath = $env:PSModulePath $env:PSModulePath = "${testModulePath}{0}${env:PSModulePath}" -f [System.IO.Path]::PathSeparator $analyzerPsd1Path = Join-Path -Path $script:destinationDir -ChildPath "$analyzerName.psd1" - $scriptBlock = [scriptblock]::Create("Import-Module '$analyzerPsd1Path'; Invoke-Pester -Path $testScripts") + $scriptBlock = [scriptblock]::Create("Import-Module '$analyzerPsd1Path'; Invoke-Pester -Path $testScripts -CI") if ( $InProcess ) { + Write-Verbose "Testing with PowerShell $($PSVersionTable.PSVersion)" & $scriptBlock } + elseif ( $WithPowerShell ) { + $pwshVersion = & $WithPowerShell --version + Write-Verbose "Testing with $pwshVersion" + & $WithPowerShell -Command $scriptBlock + } else { $powershell = (Get-Process -id $PID).MainModule.FileName - & ${powershell} -Command $scriptBlock + $pwshVersion = & $powershell --version + Write-Verbose "Testing with $pwshVersion" + & $powershell -NoProfile -Command $scriptBlock } } finally { @@ -432,49 +379,6 @@ function Get-TestFailures $results.SelectNodes(".//test-case[@result='Failure']") } -# BOOTSTRAPPING CODE FOR INSTALLING DOTNET -# install dotnet cli tools based on the version mentioned in global.json -function Install-Dotnet -{ - [CmdletBinding(SupportsShouldProcess=$true)] - param ( - [Parameter()][Switch]$Force, - [Parameter()]$version = $( Get-GlobalJsonSdkVersion -Raw ) - ) - - if ( Test-DotnetInstallation -requestedversion $version ) { - if ( $Force ) { - Write-Verbose -Verbose "Installing again" - } - else { - return - } - } - - try { - Push-Location $PSScriptRoot - $installScriptPath = Receive-DotnetInstallScript - $installScriptName = [System.IO.Path]::GetFileName($installScriptPath) - If ( $PSCmdlet.ShouldProcess("$installScriptName for $version")) { - & "${installScriptPath}" -c release -version $version -SkipNonVersionedFiles - } - # this may be the first time that dotnet has been installed, - # set up the executable variable - if ( -not $script:DotnetExe ) { - $script:DotnetExe = Get-DotnetExe - } - } - catch { - throw $_ - } - finally { - if ( Test-Path $installScriptPath ) { - Remove-Item $installScriptPath - } - Pop-Location - } -} - function Get-GlobalJsonSdkVersion { param ( [switch]$Raw ) $json = Get-Content -raw (Join-Path $PSScriptRoot global.json) | ConvertFrom-Json @@ -613,68 +517,6 @@ function Get-InstalledCLIVersion { return (ConvertTo-PortableVersion $installedVersions) } -function Test-DotnetInstallation -{ - param ( - $requestedVersion = $script:wantedVersion, - $installedVersions = $( Get-InstalledCLIVersion ) - ) - return (Test-SuitableDotnet -availableVersions $installedVersions -requiredVersion $requestedVersion ) -} - -function Receive-File { - param ( [Parameter(Mandatory,Position=0)]$uri ) - - # enable Tls12 for the request - # -SslProtocol parameter for Invoke-WebRequest wasn't in PSv3 - $securityProtocol = [System.Net.ServicePointManager]::SecurityProtocol - $tls12 = [System.Net.SecurityProtocolType]::Tls12 - try { - if ( ([System.Net.ServicePointManager]::SecurityProtocol -band $tls12) -eq 0 ) { - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor $tls12 - } - $null = Invoke-WebRequest -Uri ${uri} -OutFile "${installScriptName}" - } - finally { - [System.Net.ServicePointManager]::SecurityProtocol = $securityProtocol - } - if ( (Test-Path Variable:IsWindows) -and -not $IsWindows ) { - chmod +x $installScriptName - } - $installScript = Get-Item $installScriptName -ErrorAction Stop - if ( -not $installScript ) { - throw "Download failure of ${uri}" - } - return $installScript -} - -function Receive-DotnetInstallScript -{ - # param '$platform' is a hook to enable forcing download of a specific - # install script, generally it should not be used except in testing. - param ( $platform = "" ) - - # if $platform has been set, it has priority - # if it's not set to Windows or NonWindows, it will be ignored - if ( $platform -eq "Windows" ) { - $installScriptName = "dotnet-install.ps1" - } - elseif ( $platform -eq "NonWindows" ) { - $installScriptName = "dotnet-install.sh" - } - elseif ( ((Test-Path Variable:IsWindows) -and -not $IsWindows) ) { - # if the variable IsWindows exists and it is set to false - $installScriptName = "dotnet-install.sh" - } - else { # the default case - we're running on a Windows system - $installScriptName = "dotnet-install.ps1" - } - $uri = "https://dot.net/v1/${installScriptName}" - - $installScript = Receive-File -Uri $uri - return $installScript.FullName -} - function Get-DotnetExe { param ( $version = $script:wantedVersion ) @@ -710,6 +552,13 @@ function Get-DotnetExe $script:DotnetExe = $dotnetHuntPath return $dotnetHuntPath } + + $dotnetHuntPath = "C:\Program Files\dotnet\dotnet.exe" + Write-Verbose -Verbose "checking Windows $dotnetHuntPath" + if ( test-path $dotnetHuntPath ) { + $script:DotnetExe = $dotnetHuntPath + return $dotnetHuntPath + } } else { $dotnetHuntPath = "$HOME/.dotnet/dotnet" @@ -718,6 +567,13 @@ function Get-DotnetExe $script:DotnetExe = $dotnetHuntPath return $dotnetHuntPath } + + $dotnetHuntPath = "/usr/share/dotnet/dotnet" + Write-Verbose -Verbose "checking non-Windows $dotnetHuntPath" + if ( test-path $dotnetHuntPath ) { + $script:DotnetExe = $dotnetHuntPath + return $dotnetHuntPath + } } Write-Warning "Could not find dotnet executable" @@ -731,7 +587,7 @@ try { $script:DotnetExe = Get-DotnetExe } catch { - Write-Warning "Could not find dotnet executable" + Write-Warning "The dotnet CLI was not found, please install it: https://aka.ms/dotnet-cli" } # Copies the built PSCompatibilityCollector module to the output destination for PSSA @@ -781,44 +637,11 @@ function Copy-CrossCompatibilityModule } } -# copy the manifest into the module if is present -function Copy-Manifest -{ - param ( [switch]$signed ) - if ( $signed ) { - $buildRoot = "signed" - } - else { - $buildRoot = "out" - } - $analyzerVersion = Get-AnalyzerVersion - # location where analyzer goes - # debugging - (Get-ChildItem -File -Recurse)|ForEach-Object {Write-Verbose -Verbose -Message $_} - $modBaseDir = [io.path]::Combine($projectRoot,${buildRoot},"${analyzerName}", $analyzerVersion) - # copy the manifest files - Push-Location $buildRoot - if ( Test-Path _manifest ) { - Copy-Item -Recurse -Path _manifest -Destination $modBaseDir -Verbose - } - else { - Write-Warning -Message "_manifest not found in $PWD" - } - Pop-Location -} - # creates the nuget package which can be used for publishing to the gallery function Start-CreatePackage { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', '')] - param ( [switch]$signed ) try { - if ( $signed ) { - $buildRoot = "signed" - } - else { - $buildRoot = "out" - } + $buildRoot = "out" $repoName = [guid]::NewGuid().ToString() $nupkgDir = Join-Path $PSScriptRoot $buildRoot $null = Register-PSRepository -Name $repoName -InstallationPolicy Trusted -SourceLocation $nupkgDir diff --git a/docs/Cmdlets/Get-ScriptAnalyzerRule.md b/docs/Cmdlets/Get-ScriptAnalyzerRule.md index 64bd7307f..3d815b2c3 100644 --- a/docs/Cmdlets/Get-ScriptAnalyzerRule.md +++ b/docs/Cmdlets/Get-ScriptAnalyzerRule.md @@ -1,8 +1,7 @@ --- external help file: Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml Module Name: PSScriptAnalyzer -ms.custom: PSSA v1.21.0 -ms.date: 10/07/2021 +ms.date: 12/12/2024 online version: https://learn.microsoft.com/powershell/module/psscriptanalyzer/get-scriptanalyzerrule?view=ps-modules&wt.mc_id=ps-gethelp schema: 2.0.0 --- @@ -93,7 +92,7 @@ one value, but wildcards are supported. To get rules in subdirectories of the pa **RecurseCustomRulePath** parameter. You can create custom rules using a .NET assembly or a PowerShell module, such as the -[Community Analyzer Rules](https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Tests/Engine/CommunityAnalyzerRules/CommunityAnalyzerRules.psm1) +[Community Analyzer Rules](https://github.com/PowerShell/PSScriptAnalyzer/tree/main/Tests/Engine/CommunityAnalyzerRules) in the GitHub repository. ```yaml diff --git a/docs/Cmdlets/Invoke-Formatter.md b/docs/Cmdlets/Invoke-Formatter.md index 91dc68add..1ffb766ce 100644 --- a/docs/Cmdlets/Invoke-Formatter.md +++ b/docs/Cmdlets/Invoke-Formatter.md @@ -1,7 +1,6 @@ --- external help file: Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml Module Name: PSScriptAnalyzer -ms.custom: PSSA v1.21.0 ms.date: 10/07/2021 online version: https://learn.microsoft.com/powershell/module/psscriptanalyzer/invoke-formatter?view=ps-modules&wt.mc_id=ps-gethelp schema: 2.0.0 diff --git a/docs/Cmdlets/Invoke-ScriptAnalyzer.md b/docs/Cmdlets/Invoke-ScriptAnalyzer.md index 4605d1d89..b3e72a337 100644 --- a/docs/Cmdlets/Invoke-ScriptAnalyzer.md +++ b/docs/Cmdlets/Invoke-ScriptAnalyzer.md @@ -1,7 +1,6 @@ --- external help file: Microsoft.Windows.PowerShell.ScriptAnalyzer.dll-Help.xml Module Name: PSScriptAnalyzer -ms.custom: PSSA v1.21.0 ms.date: 10/07/2021 online version: https://learn.microsoft.com/powershell/module/psscriptanalyzer/invoke-scriptanalyzer?view=ps-modules&wt.mc_id=ps-gethelp schema: 2.0.0 @@ -193,7 +192,7 @@ value of the **Profile** parameter is the path to the Script Analyzer profile. ExcludeRules = '*WriteHost' } -Invoke-ScriptAnalyzer -Path $pshome\Modules\BitLocker -Profile .\ScriptAnalyzerProfile.txt +Invoke-ScriptAnalyzer -Path $pshome\Modules\BitLocker -Settings .\ScriptAnalyzerProfile.txt ``` If you include a conflicting parameter in the `Invoke-ScriptAnalyzer` command, such as diff --git a/docs/Cmdlets/PSScriptAnalyzer.md b/docs/Cmdlets/PSScriptAnalyzer.md index 1db78a547..0af4bf2b5 100644 --- a/docs/Cmdlets/PSScriptAnalyzer.md +++ b/docs/Cmdlets/PSScriptAnalyzer.md @@ -1,10 +1,9 @@ --- Download Help Link: https://aka.ms/ps-modules-help -Help Version: 1.21.0 +Help Version: 1.24.0 Locale: en-US Module Guid: d6245802-193d-4068-a631-8863a4342a18 Module Name: PSScriptAnalyzer -ms.custom: PSSA v1.21.0 ms.date: 10/07/2021 --- @@ -22,10 +21,13 @@ checks the quality of PowerShell code by running a set of rules. ## PSScriptAnalyzer Cmdlets ### [Get-ScriptAnalyzerRule](Get-ScriptAnalyzerRule.md) + Gets the script analyzer rules on the local computer. ### [Invoke-Formatter](Invoke-Formatter.md) + Formats a script text based on the input settings or default settings. ### [Invoke-ScriptAnalyzer](Invoke-ScriptAnalyzer.md) + Evaluates a script or module based on selected best practice rules diff --git a/docs/Rules/AlignAssignmentStatement.md b/docs/Rules/AlignAssignmentStatement.md index 9265e5389..c2709ac1a 100644 --- a/docs/Rules/AlignAssignmentStatement.md +++ b/docs/Rules/AlignAssignmentStatement.md @@ -1,7 +1,6 @@ --- description: Align assignment statement -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AlignAssignmentStatement --- @@ -19,8 +18,8 @@ are aligned or not. Consider the following example in which the key value pairs ```powershell $hashtable = @{ - property1 = "value" - anotherProperty = "another value" + property1 = 'value' + anotherProperty = 'another value' } ``` @@ -28,8 +27,8 @@ Alignment in this case would look like the following. ```powershell $hashtable = @{ - property1 = "value" - anotherProperty = "another value" + property1 = 'value' + anotherProperty = 'another value' } ``` diff --git a/docs/Rules/AvoidAssignmentToAutomaticVariable.md b/docs/Rules/AvoidAssignmentToAutomaticVariable.md index 6e5179525..60d520d07 100644 --- a/docs/Rules/AvoidAssignmentToAutomaticVariable.md +++ b/docs/Rules/AvoidAssignmentToAutomaticVariable.md @@ -1,7 +1,6 @@ --- description: Changing automatic variables might have undesired side effects -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidAssignmentToAutomaticVariable --- @@ -17,6 +16,11 @@ only be assigned in certain special cases to achieve a certain effect as a speci To understand more about automatic variables, see `Get-Help about_Automatic_Variables`. + + ## How Use variable names in functions or their parameters that do not conflict with automatic variables. diff --git a/docs/Rules/AvoidDefaultValueForMandatoryParameter.md b/docs/Rules/AvoidDefaultValueForMandatoryParameter.md index 8ad74a1a8..19c9aa732 100644 --- a/docs/Rules/AvoidDefaultValueForMandatoryParameter.md +++ b/docs/Rules/AvoidDefaultValueForMandatoryParameter.md @@ -1,7 +1,6 @@ --- description: Avoid Default Value For Mandatory Parameter -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidDefaultValueForMandatoryParameter --- diff --git a/docs/Rules/AvoidDefaultValueSwitchParameter.md b/docs/Rules/AvoidDefaultValueSwitchParameter.md index 8a797629d..9cc1ba855 100644 --- a/docs/Rules/AvoidDefaultValueSwitchParameter.md +++ b/docs/Rules/AvoidDefaultValueSwitchParameter.md @@ -1,7 +1,6 @@ --- description: Switch Parameters Should Not Default To True -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/05/2024 ms.topic: reference title: AvoidDefaultValueSwitchParameter --- @@ -11,11 +10,19 @@ title: AvoidDefaultValueSwitchParameter ## Description -Switch parameters for commands should default to false. +If your parameter takes only `true` and `false`, define the parameter as type `[Switch]`. PowerShell +treats a switch parameter as `true` when it's used with a command. If the parameter isn't included +with the command, PowerShell considers the parameter to be false. Don't define `[Boolean]` +parameters. + +You shouldn't define a switch parameter with a default value of `$true` because this isn't the +expected behavior of a switch parameter. ## How -Change the default value of the switch parameter to be false. +Change the default value of the switch parameter to be `$false` or don't provide a default value. +Write the logic of the script to assume that the switch parameter default value is `$false` or not +provided. ## Example @@ -49,8 +56,22 @@ function Test-Script $Param1, [switch] - $Switch=$False + $Switch ) + + begin { + # Ensure that the $Switch is set to false if not provided + if (-not $PSBoundParameters.ContainsKey('Switch')) { + $Switch = $false + } + } ... } ``` + +## More information + +- [Strongly Encouraged Development Guidelines][01] + + +[01]: https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines#parameters-that-take-true-and-false diff --git a/docs/Rules/AvoidExclaimOperator.md b/docs/Rules/AvoidExclaimOperator.md new file mode 100644 index 000000000..11d078d6b --- /dev/null +++ b/docs/Rules/AvoidExclaimOperator.md @@ -0,0 +1,48 @@ +--- +description: Avoid exclaim operator +ms.date: 03/26/2024 +ms.topic: reference +title: AvoidExclaimOperator +--- +# AvoidExclaimOperator + +**Severity Level: Warning** + +## Description + +Avoid using the negation operator (`!`). Use `-not` for improved readability. + +> [!NOTE] +> This rule is not enabled by default. The user needs to enable it through settings. + +## How to Fix + +## Example + +### Wrong + +```powershell +$MyVar = !$true +``` + +### Correct + +```powershell +$MyVar = -not $true +``` + +## Configuration + +```powershell +Rules = @{ + PSAvoidExclaimOperator = @{ + Enable = $true + } +} +``` + +### Parameters + +- `Enable`: **bool** (Default value is `$false`) + + Enable or disable the rule during ScriptAnalyzer invocation. diff --git a/docs/Rules/AvoidGlobalAliases.md b/docs/Rules/AvoidGlobalAliases.md index 84158157c..5157ec6f1 100644 --- a/docs/Rules/AvoidGlobalAliases.md +++ b/docs/Rules/AvoidGlobalAliases.md @@ -1,7 +1,6 @@ --- description: Avoid global aliases. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidGlobalAliases --- @@ -28,7 +27,7 @@ Use other scope modifiers for new aliases. ### Wrong ```powershell -New-Alias -Name Name -Value Value -Scope "Global" +New-Alias -Name Name -Value Value -Scope Global ``` ### Correct diff --git a/docs/Rules/AvoidGlobalFunctions.md b/docs/Rules/AvoidGlobalFunctions.md index 86870b31b..929466cb6 100644 --- a/docs/Rules/AvoidGlobalFunctions.md +++ b/docs/Rules/AvoidGlobalFunctions.md @@ -1,7 +1,6 @@ --- description: Avoid global functions and aliases -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidGlobalFunctions --- @@ -14,7 +13,6 @@ title: AvoidGlobalFunctions Globally scoped functions override existing functions within the sessions with matching names. This name collision can cause difficult to debug issues for consumers of modules. - To understand more about scoping, see `Get-Help about_Scopes`. ## How diff --git a/docs/Rules/AvoidGlobalVars.md b/docs/Rules/AvoidGlobalVars.md index 0927c2b2f..7fa0f6d2f 100644 --- a/docs/Rules/AvoidGlobalVars.md +++ b/docs/Rules/AvoidGlobalVars.md @@ -1,7 +1,6 @@ --- description: No Global Variables -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidGlobalVars --- diff --git a/docs/Rules/AvoidInvokingEmptyMembers.md b/docs/Rules/AvoidInvokingEmptyMembers.md index 45ad72942..6049e869b 100644 --- a/docs/Rules/AvoidInvokingEmptyMembers.md +++ b/docs/Rules/AvoidInvokingEmptyMembers.md @@ -1,7 +1,6 @@ --- description: Avoid Invoking Empty Members -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidInvokingEmptyMembers --- @@ -23,13 +22,13 @@ Provide the requested members for a given type or class. ### Wrong ```powershell -$MyString = "abc" +$MyString = 'abc' $MyString.('len'+'gth') ``` ### Correct ```powershell -$MyString = "abc" +$MyString = 'abc' $MyString.('length') ``` diff --git a/docs/Rules/AvoidLongLines.md b/docs/Rules/AvoidLongLines.md index edc5e30e4..c36daaa86 100644 --- a/docs/Rules/AvoidLongLines.md +++ b/docs/Rules/AvoidLongLines.md @@ -1,7 +1,6 @@ --- description: Avoid long lines -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 04/29/2025 ms.topic: reference title: AvoidLongLines --- @@ -11,10 +10,11 @@ title: AvoidLongLines ## Description -Lines should be no longer than a configured number of characters (default: 120), including leading -whitespace (indentation). +The length of lines, including leading spaces (indentation), should be less than the configured number +of characters. The default length is 120 characters. -**Note**: This rule is not enabled by default. The user needs to enable it through settings. +> [!NOTE] +> This rule isn't enabled by default. The user needs to enable it through settings. ## Configuration @@ -27,12 +27,12 @@ Rules = @{ } ``` -### Parameters +## Parameters -#### Enable: bool (Default value is `$false`) +### `Enable`: bool (Default value is `$false`) Enable or disable the rule during ScriptAnalyzer invocation. -#### MaximumLineLength: int (Default value is 120) +### `MaximumLineLength`: int (Default value is 120) Optional parameter to override the default maximum line length. diff --git a/docs/Rules/AvoidMultipleTypeAttributes.md b/docs/Rules/AvoidMultipleTypeAttributes.md index 03b6af820..9ccd478c5 100644 --- a/docs/Rules/AvoidMultipleTypeAttributes.md +++ b/docs/Rules/AvoidMultipleTypeAttributes.md @@ -1,7 +1,6 @@ --- description: Avoid multiple type specifiers on parameters. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidMultipleTypeAttributes --- diff --git a/docs/Rules/AvoidNullOrEmptyHelpMessageAttribute.md b/docs/Rules/AvoidNullOrEmptyHelpMessageAttribute.md index fa9459275..386c96050 100644 --- a/docs/Rules/AvoidNullOrEmptyHelpMessageAttribute.md +++ b/docs/Rules/AvoidNullOrEmptyHelpMessageAttribute.md @@ -1,7 +1,6 @@ --- description: Avoid using null or empty HelpMessage parameter attribute. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidNullOrEmptyHelpMessageAttribute --- diff --git a/docs/Rules/AvoidOverwritingBuiltInCmdlets.md b/docs/Rules/AvoidOverwritingBuiltInCmdlets.md index e25b3b579..10e1ad30a 100644 --- a/docs/Rules/AvoidOverwritingBuiltInCmdlets.md +++ b/docs/Rules/AvoidOverwritingBuiltInCmdlets.md @@ -1,7 +1,6 @@ --- description: Avoid overwriting built in cmdlets -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/12/2024 ms.topic: reference title: AvoidOverwritingBuiltInCmdlets --- @@ -15,7 +14,7 @@ This rule flags cmdlets that are available in a given edition/version of PowerSh operating system which are overwritten by a function declaration. It works by comparing function declarations against a set of allowlists that ship with PSScriptAnalyzer. These allowlist files are used by other PSScriptAnalyzer rules. More information can be found in the documentation for the -[UseCompatibleCmdlets](./UseCompatibleCmdlets.md) rule. +[UseCompatibleCmdlets][01] rule. ## Configuration @@ -26,7 +25,7 @@ following your settings file. @{ 'Rules' = @{ 'PSAvoidOverwritingBuiltInCmdlets' = @{ - 'PowerShellVersion' = @("core-6.1.0-windows") + 'PowerShellVersion' = @('core-6.1.0-windows') } } } @@ -38,14 +37,17 @@ following your settings file. The parameter `PowerShellVersion` is a list of allowlists that ship with PSScriptAnalyzer. -**Note**: The default value for `PowerShellVersion` is `"core-6.1.0-windows"` if PowerShell 6 or -later is installed, and `"desktop-5.1.14393.206-windows"` if it is not. +> [!NOTE] +> The default value for `PowerShellVersion` is `core-6.1.0-windows` if PowerShell 6 or +> later is installed, and `desktop-5.1.14393.206-windows` if it's not. Usually, patched versions of PowerShell have the same cmdlet data, therefore only settings of major and minor versions of PowerShell are supplied. One can also create a custom settings file as well -with the -[New-CommandDataFile.ps1](https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Utils/New-CommandDataFile.ps1) -script and use it by placing the created `JSON` into the `Settings` folder of the `PSScriptAnalyzer` -module installation folder, then the `PowerShellVersion` parameter is just its file name (that can -also be changed if desired). Note that the `core-6.0.2-*` files were removed in PSScriptAnalyzer -1.18 since PowerShell 6.0 reached end of life. +with the [New-CommandDataFile.ps1][02] script and use it by placing the created `JSON` into the +`Settings` folder of the `PSScriptAnalyzer` module installation folder, then the `PowerShellVersion` +parameter is just its filename (that can also be changed if desired). Note that the `core-6.0.2-*` +files were removed in PSScriptAnalyzer 1.18 since PowerShell 6.0 reached end of life. + + +[01]: ./UseCompatibleCmdlets.md +[02]: https://github.com/PowerShell/PSScriptAnalyzer/blob/main/Utils/New-CommandDataFile.ps1 diff --git a/docs/Rules/AvoidReservedWordsAsFunctionNames.md b/docs/Rules/AvoidReservedWordsAsFunctionNames.md new file mode 100644 index 000000000..769cbb85f --- /dev/null +++ b/docs/Rules/AvoidReservedWordsAsFunctionNames.md @@ -0,0 +1,44 @@ +--- +description: Avoid reserved words as function names +ms.date: 08/31/2025 +ms.topic: reference +title: AvoidReservedWordsAsFunctionNames +--- +# AvoidReservedWordsAsFunctionNames + +**Severity Level: Warning** + +## Description + +Avoid using reserved words as function names. Using reserved words as function names can cause +errors or unexpected behavior in scripts. + +## How to Fix + +Avoid using any of the reserved words as function names. Choose a different name that's not a +reserved word. + +See [about_Reserved_Words][01] for a list of reserved words in PowerShell. + +## Example + +### Wrong + +```powershell +# Function is a reserved word +function function { + Write-Host "Hello, World!" +} +``` + +### Correct + +```powershell +# myFunction is not a reserved word +function myFunction { + Write-Host "Hello, World!" +} +``` + + +[01]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_reserved_words diff --git a/docs/Rules/AvoidSemicolonsAsLineTerminators.md b/docs/Rules/AvoidSemicolonsAsLineTerminators.md index 65fa0d940..4716238ce 100644 --- a/docs/Rules/AvoidSemicolonsAsLineTerminators.md +++ b/docs/Rules/AvoidSemicolonsAsLineTerminators.md @@ -1,7 +1,6 @@ --- description: Avoid semicolons as line terminators -ms.custom: PSSA v1.21.0 -ms.date: 06/15/2022 +ms.date: 06/28/2023 ms.topic: reference title: AvoidSemicolonsAsLineTerminators --- diff --git a/docs/Rules/AvoidShouldContinueWithoutForce.md b/docs/Rules/AvoidShouldContinueWithoutForce.md index e19b370de..189989a89 100644 --- a/docs/Rules/AvoidShouldContinueWithoutForce.md +++ b/docs/Rules/AvoidShouldContinueWithoutForce.md @@ -1,7 +1,6 @@ --- description: Avoid Using ShouldContinue Without Boolean Force Parameter -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidShouldContinueWithoutForce --- @@ -33,7 +32,7 @@ Function Test-ShouldContinue $MyString = 'blah' ) - if ($PsCmdlet.ShouldContinue("ShouldContinue Query", "ShouldContinue Caption")) + if ($PsCmdlet.ShouldContinue('ShouldContinue Query', 'ShouldContinue Caption')) { ... } @@ -52,7 +51,7 @@ Function Test-ShouldContinue [Switch]$Force ) - if ($Force -or $PsCmdlet.ShouldContinue("ShouldContinue Query", "ShouldContinue Caption")) + if ($Force -or $PsCmdlet.ShouldContinue('ShouldContinue Query', 'ShouldContinue Caption')) { ... } diff --git a/docs/Rules/AvoidTrailingWhitespace.md b/docs/Rules/AvoidTrailingWhitespace.md index 1decf9a57..416948c3b 100644 --- a/docs/Rules/AvoidTrailingWhitespace.md +++ b/docs/Rules/AvoidTrailingWhitespace.md @@ -1,7 +1,6 @@ --- description: Avoid trailing whitespace -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidTrailingWhitespace --- diff --git a/docs/Rules/AvoidUsingAllowUnencryptedAuthentication.md b/docs/Rules/AvoidUsingAllowUnencryptedAuthentication.md new file mode 100644 index 000000000..20451e66f --- /dev/null +++ b/docs/Rules/AvoidUsingAllowUnencryptedAuthentication.md @@ -0,0 +1,35 @@ +--- +description: Avoid sending credentials and secrets over unencrypted connections +ms.date: 02/28/2024 +ms.topic: reference +title: AvoidUsingAllowUnencryptedAuthentication +--- +# AvoidUsingAllowUnencryptedAuthentication + +**Severity Level: Warning** + +## Description + +Avoid using the **AllowUnencryptedAuthentication** parameter of `Invoke-WebRequest` and +`Invoke-RestMethod`. When using this parameter, the cmdlets send credentials and secrets over +unencrypted connections. This should be avoided except for compatibility with legacy systems. + +For more details, see [Invoke-RestMethod](xref:Microsoft.PowerShell.Utility.Invoke-RestMethod). + +## How + +Avoid using the **AllowUnencryptedAuthentication** parameter. + +## Example 1 + +### Wrong + +```powershell +Invoke-WebRequest foo -AllowUnencryptedAuthentication +``` + +### Correct + +```powershell +Invoke-WebRequest foo +``` diff --git a/docs/Rules/AvoidUsingBrokenHashAlgorithms.md b/docs/Rules/AvoidUsingBrokenHashAlgorithms.md index 83b75929c..32bb464af 100644 --- a/docs/Rules/AvoidUsingBrokenHashAlgorithms.md +++ b/docs/Rules/AvoidUsingBrokenHashAlgorithms.md @@ -1,7 +1,6 @@ --- description: Avoid using broken hash algorithms -ms.custom: PSSA v1.21.0 -ms.date: 05/31/2022 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingBrokenHashAlgorithms --- diff --git a/docs/Rules/AvoidUsingCmdletAliases.md b/docs/Rules/AvoidUsingCmdletAliases.md index a2c2b010d..9a33149ad 100644 --- a/docs/Rules/AvoidUsingCmdletAliases.md +++ b/docs/Rules/AvoidUsingCmdletAliases.md @@ -1,7 +1,6 @@ --- description: Avoid Using Cmdlet Aliases or omitting the 'Get-' prefix. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingCmdletAliases --- @@ -21,7 +20,7 @@ There are also implicit aliases. When PowerShell cannot find the cmdlet name, it Every PowerShell author learns the actual command names, but different authors learn and use different aliases. Aliases can make code difficult to read, understand and impact availability. -Using the full command name makes it eaiser to maintain your scripts in the the future. +Using the full command name makes it easier to maintain your scripts in the the future. Using the full command names also allows for syntax highlighting in sites and applications like GitHub and Visual Studio Code. diff --git a/docs/Rules/AvoidUsingComputerNameHardcoded.md b/docs/Rules/AvoidUsingComputerNameHardcoded.md index 7cf3c6d8c..83a60c950 100644 --- a/docs/Rules/AvoidUsingComputerNameHardcoded.md +++ b/docs/Rules/AvoidUsingComputerNameHardcoded.md @@ -1,7 +1,6 @@ --- description: Avoid Using ComputerName Hardcoded -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingComputerNameHardcoded --- @@ -25,7 +24,7 @@ Remove hard coded computer names. ```powershell Function Invoke-MyRemoteCommand () { - Invoke-Command -Port 343 -ComputerName "hardcoderemotehostname" + Invoke-Command -Port 343 -ComputerName hardcoderemotehostname } ``` @@ -45,7 +44,7 @@ Function Invoke-MyCommand ($ComputerName) ```powershell Function Invoke-MyLocalCommand () { - Invoke-Command -Port 343 -ComputerName "hardcodelocalhostname" + Invoke-Command -Port 343 -ComputerName hardcodelocalhostname } ``` diff --git a/docs/Rules/AvoidUsingConvertToSecureStringWithPlainText.md b/docs/Rules/AvoidUsingConvertToSecureStringWithPlainText.md index 02e2033de..d25fce124 100644 --- a/docs/Rules/AvoidUsingConvertToSecureStringWithPlainText.md +++ b/docs/Rules/AvoidUsingConvertToSecureStringWithPlainText.md @@ -1,7 +1,6 @@ --- description: Avoid Using SecureString With Plain Text -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 01/28/2025 ms.topic: reference title: AvoidUsingConvertToSecureStringWithPlainText --- @@ -30,14 +29,12 @@ module from the PowerShell Gallery. ### Wrong ```powershell -$UserInput = Read-Host "Please enter your secure code" +$UserInput = Read-Host 'Please enter your secure code' $EncryptedInput = ConvertTo-SecureString -String $UserInput -AsPlainText -Force ``` ### Correct ```powershell -$SecureUserInput = Read-Host "Please enter your secure code" -AsSecureString -$EncryptedInput = ConvertFrom-SecureString -String $SecureUserInput -$SecureString = ConvertTo-SecureString -String $EncryptedInput +$SecureUserInput = Read-Host 'Please enter your secure code' -AsSecureString ``` diff --git a/docs/Rules/AvoidUsingDeprecatedManifestFields.md b/docs/Rules/AvoidUsingDeprecatedManifestFields.md index 9c64d6a0c..802304dfc 100644 --- a/docs/Rules/AvoidUsingDeprecatedManifestFields.md +++ b/docs/Rules/AvoidUsingDeprecatedManifestFields.md @@ -1,7 +1,6 @@ --- description: Avoid Using Deprecated Manifest Fields -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingDeprecatedManifestFields --- diff --git a/docs/Rules/AvoidUsingDoubleQuotesForConstantString.md b/docs/Rules/AvoidUsingDoubleQuotesForConstantString.md index 464f1c0a1..19c83fad7 100644 --- a/docs/Rules/AvoidUsingDoubleQuotesForConstantString.md +++ b/docs/Rules/AvoidUsingDoubleQuotesForConstantString.md @@ -1,7 +1,6 @@ --- description: Avoid using double quotes if the string is constant. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingDoubleQuotesForConstantString --- diff --git a/docs/Rules/AvoidUsingEmptyCatchBlock.md b/docs/Rules/AvoidUsingEmptyCatchBlock.md index 9d675e7ef..198341758 100644 --- a/docs/Rules/AvoidUsingEmptyCatchBlock.md +++ b/docs/Rules/AvoidUsingEmptyCatchBlock.md @@ -1,7 +1,6 @@ --- description: Avoid Using Empty Catch Block -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingEmptyCatchBlock --- @@ -41,7 +40,7 @@ try } catch [DivideByZeroException] { - Write-Error "DivideByZeroException" + Write-Error 'DivideByZeroException' } try @@ -50,6 +49,6 @@ try } catch [DivideByZeroException] { - throw "DivideByZeroException" + throw 'DivideByZeroException' } ``` diff --git a/docs/Rules/AvoidUsingInvokeExpression.md b/docs/Rules/AvoidUsingInvokeExpression.md index ba37de8c7..7779008ec 100644 --- a/docs/Rules/AvoidUsingInvokeExpression.md +++ b/docs/Rules/AvoidUsingInvokeExpression.md @@ -1,7 +1,6 @@ --- description: Avoid Using Invoke-Expression -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingInvokeExpression --- @@ -26,7 +25,7 @@ Remove the use of `Invoke-Expression`. ### Wrong ```powershell -Invoke-Expression "Get-Process" +Invoke-Expression 'Get-Process' ``` ### Correct diff --git a/docs/Rules/AvoidUsingPlainTextForPassword.md b/docs/Rules/AvoidUsingPlainTextForPassword.md index 6f6ba0492..c25123ec1 100644 --- a/docs/Rules/AvoidUsingPlainTextForPassword.md +++ b/docs/Rules/AvoidUsingPlainTextForPassword.md @@ -1,7 +1,6 @@ --- description: Avoid Using Plain Text For Password Parameter -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingPlainTextForPassword --- diff --git a/docs/Rules/AvoidUsingPositionalParameters.md b/docs/Rules/AvoidUsingPositionalParameters.md index 4cecb2965..1a1b77d01 100644 --- a/docs/Rules/AvoidUsingPositionalParameters.md +++ b/docs/Rules/AvoidUsingPositionalParameters.md @@ -1,7 +1,6 @@ --- description: Avoid Using Positional Parameters -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 02/13/2024 ms.topic: reference title: AvoidUsingPositionalParameters --- @@ -24,8 +23,8 @@ supplied. A simple example where the risk of using positional parameters is negl ```powershell Rules = @{ - AvoidUsingPositionalParameters = @{ - CommandAllowList = 'az', 'Join-Path' + PSAvoidUsingPositionalParameters = @{ + CommandAllowList = 'Join-Path', 'MyCmdletOrScript' Enable = $true } } @@ -33,9 +32,9 @@ Rules = @{ ### Parameters -#### AvoidUsingPositionalParameters: string[] (Default value is 'az') +#### CommandAllowList: string[] (Default value is @()') -Commands to be excluded from this rule. `az` is excluded by default because starting with version 2.40.0 the entrypoint of the AZ CLI became an `az.ps1` script but this script does not have any named parameters and just passes them on using `$args` as is to the Python process that it starts, therefore it is still a CLI and not a PowerShell command. +Commands or scripts to be excluded from this rule. #### Enable: bool (Default value is `$true`) diff --git a/docs/Rules/AvoidUsingUsernameAndPasswordParams.md b/docs/Rules/AvoidUsingUsernameAndPasswordParams.md index cb8a6cf5b..32fbac3c7 100644 --- a/docs/Rules/AvoidUsingUsernameAndPasswordParams.md +++ b/docs/Rules/AvoidUsingUsernameAndPasswordParams.md @@ -1,7 +1,6 @@ --- description: Avoid Using Username and Password Parameters -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingUsernameAndPasswordParams --- diff --git a/docs/Rules/AvoidUsingWMICmdlet.md b/docs/Rules/AvoidUsingWMICmdlet.md index 3adb521d4..96e716718 100644 --- a/docs/Rules/AvoidUsingWMICmdlet.md +++ b/docs/Rules/AvoidUsingWMICmdlet.md @@ -1,7 +1,6 @@ --- description: Avoid Using Get-WMIObject, Remove-WMIObject, Invoke-WmiMethod, Register-WmiEvent, Set-WmiInstance -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: AvoidUsingWMICmdlet --- @@ -17,7 +16,7 @@ The following cmdlets should not be used: - `Get-WmiObject` - `Remove-WmiObject` -- `Invoke-WmiObject` +- `Invoke-WmiMethod` - `Register-WmiEvent` - `Set-WmiInstance` @@ -38,7 +37,7 @@ Change to the equivalent CIM based cmdlet. - `Get-WmiObject` -> `Get-CimInstance` - `Remove-WmiObject` -> `Remove-CimInstance` -- `Invoke-WmiObject` -> `Invoke-CimMethod` +- `Invoke-WmiMethod` -> `Invoke-CimMethod` - `Register-WmiEvent` -> `Register-CimIndicationEvent` - `Set-WmiInstance` -> `Set-CimInstance` @@ -48,12 +47,12 @@ Change to the equivalent CIM based cmdlet. ```powershell Get-WmiObject -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-WmiObject -Invoke-WmiMethod -Class Win32_Process -Name "Create" -ArgumentList @{ CommandLine = "notepad.exe" } +Invoke-WmiMethod -Class Win32_Process -Name 'Create' -ArgumentList @{ CommandLine = 'notepad.exe' } ``` ### Correct ```powershell Get-CimInstance -Query 'Select * from Win32_Process where name LIKE "myprocess%"' | Remove-CIMInstance -Invoke-CimMethod -ClassName Win32_Process -MethodName "Create" -Arguments @{ CommandLine = "notepad.exe" } +Invoke-CimMethod -ClassName Win32_Process -MethodName 'Create' -Arguments @{ CommandLine = 'notepad.exe' } ``` diff --git a/docs/Rules/AvoidUsingWriteHost.md b/docs/Rules/AvoidUsingWriteHost.md index 44dc220f7..a02571c79 100644 --- a/docs/Rules/AvoidUsingWriteHost.md +++ b/docs/Rules/AvoidUsingWriteHost.md @@ -1,7 +1,6 @@ --- description: Avoid Using Write-Host -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/05/2024 ms.topic: reference title: AvoidUsingWriteHost --- @@ -11,10 +10,15 @@ title: AvoidUsingWriteHost ## Description -The use of `Write-Host` is greatly discouraged unless in the use of commands with the `Show` verb. -The `Show` verb explicitly means "show on the screen, with no other possibilities". +The primary purpose of the `Write-Host` cmdlet is to produce display-only output in the host. For +example: printing colored text or prompting the user for input when combined with `Read-Host`. +`Write-Host` uses the `ToString()` method to write the output. The particular result depends on the +program that's hosting PowerShell. The output from `Write-Host` isn't sent to the pipeline. To +output data to the pipeline, use `Write-Output` or implicit output. -Commands with the `Show` verb do not have this check applied. +The use of `Write-Host` in a function is discouraged unless the function uses the `Show` verb. The +`Show` verb explicitly means _display information to the user_. This rule doesn't apply to functions +with the `Show` verb. ## How @@ -28,27 +32,31 @@ logging or returning one or more objects. ```powershell function Get-MeaningOfLife { - ... - Write-Host "Computing the answer to the ultimate question of life, the universe and everything" - ... + Write-Host 'Computing the answer to the ultimate question of life, the universe and everything' Write-Host 42 } ``` ### Correct +Use `Write-Verbose` for informational messages. The user can decide whether to see the message by +providing the **Verbose** parameter. + ```powershell function Get-MeaningOfLife { - [CmdletBinding()]Param() # to make it possible to set the VerbosePreference when calling the function - ... - Write-Verbose "Computing the answer to the ultimate question of life, the universe and everything" - ... + [CmdletBinding()]Param() # makes it possible to support Verbose output + + Write-Verbose 'Computing the answer to the ultimate question of life, the universe and everything' Write-Output 42 } function Show-Something { - Write-Host "show something on screen"; + Write-Host 'show something on screen' } ``` + +## More information + +[Write-Host](xref:Microsoft.PowerShell.Utility.Write-Host) diff --git a/docs/Rules/DSCDscExamplesPresent.md b/docs/Rules/DSCDscExamplesPresent.md index 084eb556a..b2b1cf608 100644 --- a/docs/Rules/DSCDscExamplesPresent.md +++ b/docs/Rules/DSCDscExamplesPresent.md @@ -1,7 +1,6 @@ --- description: DSC examples are present -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCDscExamplesPresent --- diff --git a/docs/Rules/DSCDscTestsPresent.md b/docs/Rules/DSCDscTestsPresent.md index 583a8050b..f8fe31983 100644 --- a/docs/Rules/DSCDscTestsPresent.md +++ b/docs/Rules/DSCDscTestsPresent.md @@ -1,7 +1,6 @@ --- description: Dsc tests are present -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCDscTestsPresent --- diff --git a/docs/Rules/DSCReturnCorrectTypesForDSCFunctions.md b/docs/Rules/DSCReturnCorrectTypesForDSCFunctions.md index 2b92d9873..168185280 100644 --- a/docs/Rules/DSCReturnCorrectTypesForDSCFunctions.md +++ b/docs/Rules/DSCReturnCorrectTypesForDSCFunctions.md @@ -1,7 +1,6 @@ --- description: Return Correct Types For DSC Functions -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCReturnCorrectTypesForDSCFunctions --- diff --git a/docs/Rules/DSCStandardDSCFunctionsInResource.md b/docs/Rules/DSCStandardDSCFunctionsInResource.md index 843f2d6c5..f455d7437 100644 --- a/docs/Rules/DSCStandardDSCFunctionsInResource.md +++ b/docs/Rules/DSCStandardDSCFunctionsInResource.md @@ -1,7 +1,6 @@ --- description: Use Standard Get/Set/Test TargetResource functions in DSC Resource -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCStandardDSCFunctionsInResource --- diff --git a/docs/Rules/DSCUseIdenticalMandatoryParametersForDSC.md b/docs/Rules/DSCUseIdenticalMandatoryParametersForDSC.md index 5f6f021ab..dc8e9e918 100644 --- a/docs/Rules/DSCUseIdenticalMandatoryParametersForDSC.md +++ b/docs/Rules/DSCUseIdenticalMandatoryParametersForDSC.md @@ -1,7 +1,6 @@ --- description: Use identical mandatory parameters for DSC Get/Test/Set TargetResource functions -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCUseIdenticalMandatoryParametersForDSC --- diff --git a/docs/Rules/DSCUseIdenticalParametersForDSC.md b/docs/Rules/DSCUseIdenticalParametersForDSC.md index 35d3bb459..8f8e98e55 100644 --- a/docs/Rules/DSCUseIdenticalParametersForDSC.md +++ b/docs/Rules/DSCUseIdenticalParametersForDSC.md @@ -1,7 +1,6 @@ --- description: Use Identical Parameters For DSC Test and Set Functions -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCUseIdenticalParametersForDSC --- diff --git a/docs/Rules/DSCUseVerboseMessageInDSCResource.md b/docs/Rules/DSCUseVerboseMessageInDSCResource.md index cf58a30b0..bb5abacef 100644 --- a/docs/Rules/DSCUseVerboseMessageInDSCResource.md +++ b/docs/Rules/DSCUseVerboseMessageInDSCResource.md @@ -1,7 +1,6 @@ --- description: Use verbose message in DSC resource -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: DSCUseVerboseMessageInDSCResource --- @@ -38,7 +37,7 @@ Function Test-Function { [CmdletBinding()] Param() - Write-Verbose "Verbose output" + Write-Verbose 'Verbose output' ... } ``` diff --git a/docs/Rules/MisleadingBacktick.md b/docs/Rules/MisleadingBacktick.md index 3f8950376..e140b8e9d 100644 --- a/docs/Rules/MisleadingBacktick.md +++ b/docs/Rules/MisleadingBacktick.md @@ -1,7 +1,6 @@ --- description: Misleading Backtick -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: MisleadingBacktick --- diff --git a/docs/Rules/MissingModuleManifestField.md b/docs/Rules/MissingModuleManifestField.md index d72f09e4b..c19d53454 100644 --- a/docs/Rules/MissingModuleManifestField.md +++ b/docs/Rules/MissingModuleManifestField.md @@ -1,7 +1,6 @@ --- description: Module Manifest Fields -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: MissingModuleManifestField --- diff --git a/docs/Rules/PlaceCloseBrace.md b/docs/Rules/PlaceCloseBrace.md index 4a55e3ad2..6e14acc74 100644 --- a/docs/Rules/PlaceCloseBrace.md +++ b/docs/Rules/PlaceCloseBrace.md @@ -1,7 +1,6 @@ --- description: Place close braces -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: PlaceCloseBrace --- @@ -42,7 +41,7 @@ Create violation if there is an empty line before a close brace. #### IgnoreOneLineBlock: bool (Default value is `$true`) Indicates if closed brace pairs in a one line block should be ignored or not. For example, -`$x = if ($true) { "blah" } else { "blah blah" }`, if the property is set to true then the rule +`$x = if ($true) { 'blah' } else { 'blah blah' }`, if the property is set to true then the rule doesn't fire a violation. #### NewLineAfter: bool (Default value is `$true`) diff --git a/docs/Rules/PlaceOpenBrace.md b/docs/Rules/PlaceOpenBrace.md index 0c873e6a5..a523ec4e8 100644 --- a/docs/Rules/PlaceOpenBrace.md +++ b/docs/Rules/PlaceOpenBrace.md @@ -1,7 +1,6 @@ --- description: Place open braces consistently -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: PlaceOpenBrace --- @@ -46,5 +45,5 @@ Enforce a new line character after an open brace. The default value is true. #### IgnoreOneLineBlock: bool (Default value is `$true`) Indicates if open braces in a one line block should be ignored or not. For example, -` $x = if ($true) { "blah" } else { "blah blah" }`, if the property is set to true then the rule +`$x = if ($true) { 'blah' } else { 'blah blah' }`, if the property is set to true then the rule doesn't fire a violation. diff --git a/docs/Rules/PossibleIncorrectComparisonWithNull.md b/docs/Rules/PossibleIncorrectComparisonWithNull.md index 671f4914f..9a28646f4 100644 --- a/docs/Rules/PossibleIncorrectComparisonWithNull.md +++ b/docs/Rules/PossibleIncorrectComparisonWithNull.md @@ -1,7 +1,6 @@ --- description: Null Comparison -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/03/2024 ms.topic: reference title: PossibleIncorrectComparisonWithNull --- @@ -19,8 +18,8 @@ There are multiple reasons why this occurs: - `$null` is a scalar value. When the value on the left side of an operator is a scalar, comparison operators return a **Boolean** value. When the value is a collection, the comparison operators return any matching values or an empty array if there are no matches in the collection. -- PowerShell performs type casting left to right, resulting in incorrect comparisons when `$null` is - cast to other scalar types. +- PowerShell performs type casting on the right-hand operand, resulting in incorrect comparisons + when `$null` is cast to other scalar types. The only way to reliably check if a value is `$null` is to place `$null` on the left side of the operator so that a scalar comparison is performed. @@ -56,17 +55,17 @@ function Test-CompareWithNull ## Try it Yourself ```powershell -# Both expressions below return 'false' because the comparison does not return an -# object and therefore the if statement always falls through: +# This example returns 'false' because the comparison does not return any objects from the array if (@() -eq $null) { 'true' } else { 'false' } -if (@() -ne $null) { 'true' } else { 'false' } +# This example returns 'true' because the array is empty +if ($null -ne @()) { 'true' } else { 'false' } ``` This is how the comparison operator works by-design. But, as demonstrated, this can lead to non-intuitive behavior, especially when the intent is simple test for null. The following example demonstrates the designed behavior of the comparison operator when the -left-hand side is a collection. Each element in the collection is compared the right-hand side +left-hand side is a collection. Each element in the collection is compared to the right-hand side value. When true, that element of the collection is returned. ```powershell diff --git a/docs/Rules/PossibleIncorrectUsageOfAssignmentOperator.md b/docs/Rules/PossibleIncorrectUsageOfAssignmentOperator.md index 5737b53f7..11c5d23f1 100644 --- a/docs/Rules/PossibleIncorrectUsageOfAssignmentOperator.md +++ b/docs/Rules/PossibleIncorrectUsageOfAssignmentOperator.md @@ -1,7 +1,6 @@ --- description: Equal sign is not an assignment operator. Did you mean the equality operator \'-eq\'? -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: PossibleIncorrectUsageOfAssignmentOperator --- @@ -53,7 +52,7 @@ if ($a = Get-Something) # Only execute action if command returns something and a } ``` -## Implicit suppresion using Clang style +## Implicit suppression using Clang style There are some rare cases where assignment of variable inside an `if` statement is by design. Instead of suppressing the rule, one can also signal that assignment was intentional by wrapping the diff --git a/docs/Rules/PossibleIncorrectUsageOfRedirectionOperator.md b/docs/Rules/PossibleIncorrectUsageOfRedirectionOperator.md index 9956b92d9..871d1340f 100644 --- a/docs/Rules/PossibleIncorrectUsageOfRedirectionOperator.md +++ b/docs/Rules/PossibleIncorrectUsageOfRedirectionOperator.md @@ -1,7 +1,6 @@ --- description: \'>\' is not a comparison operator. Use \'-gt\' (greater than) or \'-ge\' (greater or equal). -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: PossibleIncorrectUsageOfRedirectionOperator --- diff --git a/docs/Rules/ProvideCommentHelp.md b/docs/Rules/ProvideCommentHelp.md index b9305da14..19e83681a 100644 --- a/docs/Rules/ProvideCommentHelp.md +++ b/docs/Rules/ProvideCommentHelp.md @@ -1,7 +1,6 @@ --- description: Basic Comment Help -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: ProvideCommentHelp --- @@ -17,9 +16,9 @@ presence of comment based help and not on the validity or format. For assistance on comment based help, use the command `Get-Help about_comment_based_help` or the following articles: -- [Writing Comment-based Help](https://learn.microsoft.com/powershell/scripting/developer/help/writing-comment-based-help-topics) -- [Writing Help for PowerShell Cmdlets](https://learn.microsoft.com/powershell/scripting/developer/help/writing-help-for-windows-powershell-cmdlets) -- [Create XML-based help using PlatyPS](https://learn.microsoft.com/powershell/utility-modules/platyps/create-help-using-platyps) +- [Writing Comment-based Help][01] +- [Writing Help for PowerShell Cmdlets][02] +- [Create XML-based help using PlatyPS][03] ## Configuration @@ -30,41 +29,42 @@ Rules = @{ ExportedOnly = $false BlockComment = $true VSCodeSnippetCorrection = $false - Placement = "before" + Placement = 'before' } } ``` ### Parameters -#### Enable: bool (Default valus is `$true`) +- `Enable`: **bool** (Default value is `$true`) -Enable or disable the rule during ScriptAnalyzer invocation. + Enable or disable the rule during ScriptAnalyzer invocation. -#### ExportedOnly: bool (Default value is `$true`) +- `ExportedOnly`: **bool** (Default value is `$true`) -If enabled, throw violation only on functions/cmdlets that are exported using the -`Export-ModuleMember` cmdlet. + If enabled, throw violation only on functions/cmdlets that are exported using the + `Export-ModuleMember` cmdlet. -#### BlockComment: bool (Default value is `$true`) +- `BlockComment`: **bool** (Default value is `$true`) -If enabled, returns comment help in block comment style, i.e., `<#...#>`. Otherwise returns comment -help in line comment style, i.e., each comment line starts with `#`. + If enabled, returns comment help in block comment style (`<#...#>`). Otherwise returns + comment help in line comment style where each comment line starts with `#`. -#### VSCodeSnippetCorrection: bool (Default value is `$false`) +- `VSCodeSnippetCorrection`: **bool** (Default value is `$false`) -If enabled, returns comment help in vscode snippet format. + If enabled, returns comment help in vscode snippet format. -#### Placement: string (Default value is `before`) +- `Placement`: **string** (Default value is `before`) -Represents the position of comment help with respect to the function definition. + Represents the position of comment help with respect to the function definition. -Possible values are: `before`, `begin` and `end`. If any invalid value is given, the property -defaults to `before`. + Possible values are: -`before` means the help is placed before the function definition. `begin` means the help is placed -at the beginning of the function definition body. `end` means the help is places the end of the -function definition body. + - `before`: means the help is placed before the function definition + - `begin` means the help is placed at the beginning of the function definition body + - `end` means the help is places the end of the function definition body + + If any invalid value is given, the property defaults to `before`. ## Example @@ -118,3 +118,7 @@ function Get-File } ``` + +[01]: https://learn.microsoft.com/powershell/scripting/developer/help/writing-comment-based-help-topics +[02]: https://learn.microsoft.com/powershell/scripting/developer/help/writing-help-for-windows-powershell-cmdlets +[03]: https://learn.microsoft.com/powershell/utility-modules/platyps/create-help-using-platyps diff --git a/docs/Rules/README.md b/docs/Rules/README.md index aaac74f66..da1058bc2 100644 --- a/docs/Rules/README.md +++ b/docs/Rules/README.md @@ -1,7 +1,6 @@ --- description: List of PSScriptAnalyzer rules -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 03/27/2024 ms.topic: reference title: List of PSScriptAnalyzer rules --- @@ -15,6 +14,7 @@ The PSScriptAnalyzer contains the following rule definitions. | [AvoidAssignmentToAutomaticVariable](./AvoidAssignmentToAutomaticVariable.md) | Warning | Yes | | | [AvoidDefaultValueForMandatoryParameter](./AvoidDefaultValueForMandatoryParameter.md) | Warning | Yes | | | [AvoidDefaultValueSwitchParameter](./AvoidDefaultValueSwitchParameter.md) | Warning | Yes | | +| [AvoidExclaimOperator](./AvoidExclaimOperator.md) | Warning | No | | | [AvoidGlobalAliases1](./AvoidGlobalAliases.md) | Warning | Yes | | | [AvoidGlobalFunctions](./AvoidGlobalFunctions.md) | Warning | Yes | | | [AvoidGlobalVars](./AvoidGlobalVars.md) | Warning | Yes | | @@ -23,15 +23,17 @@ The PSScriptAnalyzer contains the following rule definitions. | [AvoidMultipleTypeAttributes1](./AvoidMultipleTypeAttributes.md) | Warning | Yes | | | [AvoidNullOrEmptyHelpMessageAttribute](./AvoidNullOrEmptyHelpMessageAttribute.md) | Warning | Yes | | | [AvoidOverwritingBuiltInCmdlets](./AvoidOverwritingBuiltInCmdlets.md) | Warning | Yes | Yes | +| [AvoidReservedWordsAsFunctionNames](./AvoidReservedWordsAsFunctionNames.md) | Warning | Yes | | | [AvoidSemicolonsAsLineTerminators](./AvoidSemicolonsAsLineTerminators.md) | Warning | No | | | [AvoidShouldContinueWithoutForce](./AvoidShouldContinueWithoutForce.md) | Warning | Yes | | | [AvoidTrailingWhitespace](./AvoidTrailingWhitespace.md) | Warning | Yes | | +| [AvoidUsingAllowUnencryptedAuthentication](./AvoidUsingAllowUnencryptedAuthentication.md) | Warning | Yes | | | [AvoidUsingBrokenHashAlgorithms](./AvoidUsingBrokenHashAlgorithms.md) | Warning | Yes | | | [AvoidUsingCmdletAliases](./AvoidUsingCmdletAliases.md) | Warning | Yes | Yes2 | | [AvoidUsingComputerNameHardcoded](./AvoidUsingComputerNameHardcoded.md) | Error | Yes | | | [AvoidUsingConvertToSecureStringWithPlainText](./AvoidUsingConvertToSecureStringWithPlainText.md) | Error | Yes | | | [AvoidUsingDeprecatedManifestFields](./AvoidUsingDeprecatedManifestFields.md) | Warning | Yes | | -| [AvoidUsingDoubleQuotesForConstantString](./AvoidUsingDoubleQuotesForConstantString.md) | Warning | No | Yes | +| [AvoidUsingDoubleQuotesForConstantString](./AvoidUsingDoubleQuotesForConstantString.md) | Information | No | | | [AvoidUsingEmptyCatchBlock](./AvoidUsingEmptyCatchBlock.md) | Warning | Yes | | | [AvoidUsingInvokeExpression](./AvoidUsingInvokeExpression.md) | Warning | Yes | | | [AvoidUsingPlainTextForPassword](./AvoidUsingPlainTextForPassword.md) | Warning | Yes | | @@ -56,7 +58,7 @@ The PSScriptAnalyzer contains the following rule definitions. | [ProvideCommentHelp](./ProvideCommentHelp.md) | Information | Yes | Yes | | [ReservedCmdletChar](./ReservedCmdletChar.md) | Error | Yes | | | [ReservedParams](./ReservedParams.md) | Error | Yes | | -| [ReviewUnusedParameter](./ReviewUnusedParameter.md) | Warning | Yes | | +| [ReviewUnusedParameter](./ReviewUnusedParameter.md) | Warning | Yes | Yes2 | | [ShouldProcess](./ShouldProcess.md) | Warning | Yes | | | [UseApprovedVerbs](./UseApprovedVerbs.md) | Warning | Yes | | | [UseBOMForUnicodeEncodedFile](./UseBOMForUnicodeEncodedFile.md) | Warning | Yes | | @@ -74,7 +76,7 @@ The PSScriptAnalyzer contains the following rule definitions. | [UseProcessBlockForPipelineCommand](./UseProcessBlockForPipelineCommand.md) | Warning | Yes | | | [UsePSCredentialType](./UsePSCredentialType.md) | Warning | Yes | | | [UseShouldProcessForStateChangingFunctions](./UseShouldProcessForStateChangingFunctions.md) | Warning | Yes | | -| [UseSingularNouns](./UseSingularNouns.md) | Warning | Yes | | +| [UseSingularNouns](./UseSingularNouns.md) | Warning | Yes | Yes | | [UseSupportsShouldProcess](./UseSupportsShouldProcess.md) | Warning | Yes | | | [UseToExportFieldsInManifest](./UseToExportFieldsInManifest.md) | Warning | Yes | | | [UseUsingScopeModifierInNewRunspaces](./UseUsingScopeModifierInNewRunspaces.md) | Warning | Yes | | @@ -82,5 +84,5 @@ The PSScriptAnalyzer contains the following rule definitions. - 1 Rule is not available on all PowerShell versions, editions, or OS platforms. See the rule's documentation for details. -- 2 The rule a configurable property, but the rule can't be disabled like other +- 2 The rule has a configurable property, but the rule can't be disabled like other configurable rules. diff --git a/docs/Rules/ReservedCmdletChar.md b/docs/Rules/ReservedCmdletChar.md index fc9dc6496..55acfc707 100644 --- a/docs/Rules/ReservedCmdletChar.md +++ b/docs/Rules/ReservedCmdletChar.md @@ -1,7 +1,6 @@ --- description: Reserved Cmdlet Chars -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: ReservedCmdletChar --- diff --git a/docs/Rules/ReservedParams.md b/docs/Rules/ReservedParams.md index 768b660f3..0e433e0e4 100644 --- a/docs/Rules/ReservedParams.md +++ b/docs/Rules/ReservedParams.md @@ -1,7 +1,6 @@ --- description: Reserved Parameters -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 03/06/2024 ms.topic: reference title: ReservedParams --- @@ -11,7 +10,9 @@ title: ReservedParams ## Description -You cannot use reserved common parameters in an advanced function. +You can't redefine [common parameters][01] in an advanced function. Using the `CmdletBinding` or +`Parameter` attributes creates an advanced function. The common parameters are are automatically +available in advanced functions, so you can't redefine them. ## How @@ -24,7 +25,7 @@ Change the name of the parameter. ```powershell function Test { - [CmdletBinding] + [CmdletBinding()] Param ( $ErrorVariable, @@ -38,7 +39,7 @@ function Test ```powershell function Test { - [CmdletBinding] + [CmdletBinding()] Param ( $Err, @@ -46,3 +47,5 @@ function Test ) } ``` + +[01]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_commonparameters diff --git a/docs/Rules/ReviewUnusedParameter.md b/docs/Rules/ReviewUnusedParameter.md index 309bcd4d2..4732b8aba 100644 --- a/docs/Rules/ReviewUnusedParameter.md +++ b/docs/Rules/ReviewUnusedParameter.md @@ -1,7 +1,6 @@ --- description: ReviewUnusedParameter -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 03/26/2024 ms.topic: reference title: ReviewUnusedParameter --- @@ -14,6 +13,24 @@ title: ReviewUnusedParameter This rule identifies parameters declared in a script, scriptblock, or function scope that have not been used in that scope. +## Configuration settings + +By default, this rule doesn't consider child scopes other than scriptblocks provided to +`Where-Object` or `ForEach-Object`. The `CommandsToTraverse` setting is an string array allows you +to add additional commands that accept scriptblocks that this rule should examine. + +```powershell +@{ + Rules = @{ + PSReviewUnusedParameter = @{ + CommandsToTraverse = @( + 'Invoke-PSFProtectedCommand' + ) + } + } +} +``` + ## How Consider removing the unused parameter. diff --git a/docs/Rules/ShouldProcess.md b/docs/Rules/ShouldProcess.md index 2e50e2764..40dcedec4 100644 --- a/docs/Rules/ShouldProcess.md +++ b/docs/Rules/ShouldProcess.md @@ -1,7 +1,6 @@ --- description: Should Process -ms.custom: PSSA v1.21.0 -ms.date: 03/24/2022 +ms.date: 06/28/2023 ms.topic: reference title: ShouldProcess --- @@ -18,9 +17,9 @@ but makes no calls to `ShouldProcess` or it calls `ShouldProcess` but does not d For more information, see the following articles: -- [about_Functions_Advanced_Methods](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_methods) -- [about_Functions_CmdletBindingAttribute](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_Functions_CmdletBindingAttribute) -- [Everything you wanted to know about ShouldProcess](https://learn.microsoft.com/powershell/scripting/learn/deep-dives/everything-about-shouldprocess) +- [about_Functions_Advanced_Methods][01] +- [about_Functions_CmdletBindingAttribute][02] +- [Everything you wanted to know about ShouldProcess][03] ## How @@ -42,7 +41,7 @@ function Set-File [Parameter(Mandatory=$true)] $Path ) - "String" | Out-File -FilePath $Path + 'String' | Out-File -FilePath $Path } ``` @@ -73,3 +72,7 @@ function Set-File } } ``` + +[01]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced_methods +[02]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_Functions_CmdletBindingAttribute +[03]: https://learn.microsoft.com/powershell/scripting/learn/deep-dives/everything-about-shouldprocess diff --git a/docs/Rules/UseApprovedVerbs.md b/docs/Rules/UseApprovedVerbs.md index 932795f77..c1bcaa0c7 100644 --- a/docs/Rules/UseApprovedVerbs.md +++ b/docs/Rules/UseApprovedVerbs.md @@ -1,7 +1,6 @@ --- description: Cmdlet Verbs -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 03/26/2024 ms.topic: reference title: UseApprovedVerbs --- @@ -15,10 +14,9 @@ All cmdlets must used approved verbs. Approved verbs can be found by running the command `Get-Verb`. -Additional documentation on approved verbs can be found at -[Approved Verbs for PowerShell Commands](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands). -Some unapproved verbs are documented on the approved verbs page and point to approved alternatives. -Try searching for the verb you used to find its approved form. For example, searching for `Read`, +For a more information about approved verbs, see [Approved Verbs for PowerShell Commands][01]. Some +unapproved verbs are documented on the approved verbs page and point to approved alternatives. Try +searching for the verb you used to find its approved form. For example, searching for `Read`, `Open`, or `Search` leads you to `Get`. ## How @@ -44,3 +42,6 @@ function Update-Item ... } ``` + + +[01]: /powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands diff --git a/docs/Rules/UseBOMForUnicodeEncodedFile.md b/docs/Rules/UseBOMForUnicodeEncodedFile.md index 1c4c0f40a..e0a46b1e2 100644 --- a/docs/Rules/UseBOMForUnicodeEncodedFile.md +++ b/docs/Rules/UseBOMForUnicodeEncodedFile.md @@ -1,7 +1,6 @@ --- description: Use BOM encoding for non-ASCII files -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 01/07/2025 ms.topic: reference title: UseBOMForUnicodeEncodedFile --- @@ -14,6 +13,30 @@ title: UseBOMForUnicodeEncodedFile For a file encoded with a format other than ASCII, ensure Byte Order Mark (BOM) is present to ensure that any application consuming this file can interpret it correctly. +You can use this rule to test any arbitrary text file, but the intent is to ensure that PowerShell +scripts are saved with a BOM when using a Unicode encoding. + ## How -Ensure that the file is encoded with BOM present. +For PowerShell commands that write to files, ensure that you set the encoding parameter to a value +that produces a BOM. In PowerShell 7 and higher, the following values of the **Encoding** parameter +produce a BOM: + +- `bigendianunicode` +- `bigendianutf32` +- `oem` +- `unicode` +- `utf32` +- `utf8BOM` + +When you create a script file using a text editor, ensure that the editor is configured to save the +file with a BOM. Consult the documentation for your text editor for instructions on how to save +files with a BOM. + +## Further reading + +For more information, see the following articles: + +- [about_Character_Encoding](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_character_encoding) +- [Set-Content](https://learn.microsoft.com/powershell/module/microsoft.powershell.management/set-content) +- [Understanding file encoding in VS Code and PowerShell](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding) diff --git a/docs/Rules/UseCmdletCorrectly.md b/docs/Rules/UseCmdletCorrectly.md index 158dade6d..81ec42e4e 100644 --- a/docs/Rules/UseCmdletCorrectly.md +++ b/docs/Rules/UseCmdletCorrectly.md @@ -1,7 +1,6 @@ --- description: Use Cmdlet Correctly -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseCmdletCorrectly --- diff --git a/docs/Rules/UseCompatibleCmdlets.md b/docs/Rules/UseCompatibleCmdlets.md index 6a9d62d95..4cd52340e 100644 --- a/docs/Rules/UseCompatibleCmdlets.md +++ b/docs/Rules/UseCompatibleCmdlets.md @@ -1,7 +1,6 @@ --- description: Use compatible cmdlets -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/12/2024 ms.topic: reference title: UseCompatibleCmdlets --- @@ -11,8 +10,8 @@ title: UseCompatibleCmdlets ## Description -This rule flags cmdlets that are not available in a given Edition/Version of PowerShell on a given -Operating System. It works by comparing a cmdlet against a set of allowlists which ship with +This rule flags cmdlets that aren't available in a given Edition and Version of PowerShell on a +given Operating System. It works by comparing a cmdlet against a set of allowlists which ship with PSScriptAnalyzer. They can be found at `/path/to/PSScriptAnalyzerModule/Settings`. These files are of the form, `--.json` where `` can be either `Core` or `Desktop`, `` can be either `Windows`, `Linux` or `MacOS`, and `` is the PowerShell @@ -23,7 +22,7 @@ the following your settings file: @{ 'Rules' = @{ 'PSUseCompatibleCmdlets' = @{ - 'compatibility' = @("core-6.1.0-windows") + 'compatibility' = @('core-6.1.0-windows') } } } @@ -42,7 +41,10 @@ The parameter `compatibility` is a list that contain any of the following Usually, patched versions of PowerShell have the same cmdlet data, therefore only settings of major and minor versions of PowerShell are supplied. You can also create a custom settings file with the -[New-CommandDataFile.ps1](https://github.com/PowerShell/PSScriptAnalyzer/blob/development/Utils/New-CommandDataFile.ps1) -script. Place the created `.json` file in the `Settings` folder of the `PSScriptAnalyzer` module -folder. Then the `compatibility` parameter values is just the filename. Note that the `core-6.0.2-*` -files were removed in PSScriptAnalyzer 1.18 since PowerShell 6.0 reached it's end of life. +[New-CommandDataFile.ps1][01] script. Place the created `.json` file in the `Settings` folder of the +`PSScriptAnalyzer` module folder. Then the `compatibility` parameter values is just the filename. +Note that the `core-6.0.2-*` files were removed in PSScriptAnalyzer 1.18 since PowerShell 6.0 +reached it's end of life. + + +[01]: https://github.com/PowerShell/PSScriptAnalyzer/blob/main/Utils/New-CommandDataFile.ps1 diff --git a/docs/Rules/UseCompatibleCommands.md b/docs/Rules/UseCompatibleCommands.md index 3eb650322..ae74862ba 100644 --- a/docs/Rules/UseCompatibleCommands.md +++ b/docs/Rules/UseCompatibleCommands.md @@ -1,7 +1,6 @@ --- description: Use compatible commands -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/12/2024 ms.topic: reference title: UseCompatibleCommands --- @@ -47,43 +46,41 @@ your configuration. Platforms bundled by default are: -| PowerShell Version | Operating System | ID | -| ------------------ | --------------------- | --------------------------------------------------------------------- | -| 3.0 | Windows Server 2012 | `win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework` | -| 4.0 | Windows Server 2012R2 | `win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework` | -| 5.1 | Windows Server 2016 | `win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework` | -| 5.1 | Windows Server 2019 | `win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | -| 5.1 | Windows 10 1809 (RS5) | `win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | -| 6.2 | Windows Server 2016 | `win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core` | -| 6.2 | Windows Server 2019 | `win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | -| 6.2 | Windows 10 1809 (RS5) | `win-4_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | -| 6.2 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core` | -| 7.0 | Windows Server 2016 | `win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core` | -| 7.0 | Windows Server 2019 | `win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core` | -| 7.0 | Windows 10 1809 (RS5) | `win-4_x64_10.0.17763.0_6.2.4_x64_3.1.2_core` | -| 7.0 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_3.1.2_core` | - -Other profiles can be found in the -[GitHub repo](https://github.com/PowerShell/PSScriptAnalyzer/tree/development/PSCompatibilityCollector/optional_profiles). - -You can also generate your own platform profile using the -[PSCompatibilityCollector module](https://github.com/PowerShell/PSScriptAnalyzer/tree/development/PSCompatibilityCollector). +| PowerShell Version | Operating System | ID | +| :----------------: | ---------------------- | --------------------------------------------------------------------- | +| 3.0 | Windows Server 2012 | `win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework` | +| 4.0 | Windows Server 2012 R2 | `win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework` | +| 5.1 | Windows Server 2016 | `win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework` | +| 5.1 | Windows Server 2019 | `win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | +| 5.1 | Windows 10 Pro | `win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | +| 6.2 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core` | +| 6.2 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core` | +| 6.2 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | +| 6.2 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core` | +| 7.0 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_7.0.0_x64_3.1.2_core` | +| 7.0 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core` | +| 7.0 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core` | +| 7.0 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core` | + +Other profiles can be found in the [GitHub repo][02]. + +You can also generate your own platform profile using the [PSCompatibilityCollector module][01]. The compatibility profile settings takes a list of platforms to target under `TargetProfiles`. A platform can be specified as: - A platform name (like `ubuntu_x64_18.04_6.1.1_x64_4.0.30319.42000_core`), which will have `.json` added to the end and is searched for in the default profile directory. -- A filename (like `my_custom_platform.json`), which will be searched for the in the default - profile directory. +- A filename (like `my_custom_platform.json`), which will be searched for the in the default profile + directory. - An absolute path to a file (like `D:\PowerShellProfiles\TargetMachine.json`). The default profile directory is under the PSScriptAnalzyer module at `$PSScriptRoot/compatibility_profiles` (where `$PSScriptRoot` here refers to the directory containing `PSScriptAnalyzer.psd1`). -The compatibility analysis compares a command used to both a target profile and a "union" profile -(containing all commands available in *any* profile in the profile dir). If a command is not present +The compatibility analysis compares a command used to both a target profile and a 'union' profile +(containing all commands available in _any_ profile in the profile dir). If a command is not present in the union profile, it is assumed to be locally created and ignored. Otherwise, if a command is present in the union profile but not present in a target, it is deemed to be incompatible with that target. @@ -102,7 +99,7 @@ An example configuration might look like: ```powershell @{ Rules = @{ - PSUseCompatibleCommmands = @{ + PSUseCompatibleCommands = @{ Enable = $true TargetProfiles = @( 'ubuntu_x64_18.04_6.1.3_x64_4.0.30319.42000_core' @@ -126,17 +123,23 @@ Command compatibility diagnostics can be suppressed with an attribute on the `pa scriptblock as with other rules. ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCommands", "")] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', '')] ``` The rule can also be suppressed only for particular commands: ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCommands", "Start-Service")] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', + 'Start-Service')] ``` And also suppressed only for parameters: ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCommands", "Import-Module/FullyQualifiedName")] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', + 'Import-Module/FullyQualifiedName')] ``` + + +[01]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector +[02]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector/optional_profiles diff --git a/docs/Rules/UseCompatibleSyntax.md b/docs/Rules/UseCompatibleSyntax.md index 5a98ea3ec..a2adbbbb6 100644 --- a/docs/Rules/UseCompatibleSyntax.md +++ b/docs/Rules/UseCompatibleSyntax.md @@ -1,7 +1,6 @@ --- description: Use compatible syntax -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseCompatibleSyntax --- @@ -22,9 +21,9 @@ PowerShell versions because they aren't able to parse the incompatible syntaxes. PSUseCompatibleSyntax = @{ Enable = $true TargetVersions = @( - "6.0", - "5.1", - "4.0" + '6.0', + '5.1', + '4.0' ) } } diff --git a/docs/Rules/UseCompatibleTypes.md b/docs/Rules/UseCompatibleTypes.md index cd9da007d..9bff5fa76 100644 --- a/docs/Rules/UseCompatibleTypes.md +++ b/docs/Rules/UseCompatibleTypes.md @@ -1,7 +1,6 @@ --- description: Use compatible types -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/12/2024 ms.topic: reference title: UseCompatibleTypes --- @@ -48,27 +47,25 @@ your configuration. Platforms bundled by default are: -| PowerShell Version | Operating System | ID | -| ------------------ | --------------------- | --------------------------------------------------------------------- | -| 3.0 | Windows Server 2012 | `win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework` | -| 4.0 | Windows Server 2012R2 | `win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework` | -| 5.1 | Windows Server 2016 | `win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework` | -| 5.1 | Windows Server 2019 | `win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | -| 5.1 | Windows 10 1809 (RS5) | `win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | -| 6.2 | Windows Server 2016 | `win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core` | -| 6.2 | Windows Server 2019 | `win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | -| 6.2 | Windows 10 1809 (RS5) | `win-4_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | -| 6.2 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core` | -| 7.0 | Windows Server 2016 | `win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core` | -| 7.0 | Windows Server 2019 | `win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core` | -| 7.0 | Windows 10 1809 (RS5) | `win-4_x64_10.0.17763.0_6.2.4_x64_3.1.2_core` | -| 7.0 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_3.1.2_core` | - -Other profiles can be found in the -[GitHub repo](https://github.com/PowerShell/PSScriptAnalyzer/tree/development/PSCompatibilityCollector/optional_profiles). - -You can also generate your own platform profile using the -[PSCompatibilityCollector module](https://github.com/PowerShell/PSScriptAnalyzer/tree/development/PSCompatibilityCollector). +| PowerShell Version | Operating System | ID | +| :----------------: | ---------------------- | --------------------------------------------------------------------- | +| 3.0 | Windows Server 2012 | `win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework` | +| 4.0 | Windows Server 2012 R2 | `win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework` | +| 5.1 | Windows Server 2016 | `win-8_x64_10.0.14393.0_5.1.14393.2791_x64_4.0.30319.42000_framework` | +| 5.1 | Windows Server 2019 | `win-8_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | +| 5.1 | Windows 10 Pro | `win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework` | +| 6.2 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_6.2.4_x64_4.0.30319.42000_core` | +| 6.2 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_6.2.4_x64_4.0.30319.42000_core` | +| 6.2 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_6.2.4_x64_4.0.30319.42000_core` | +| 6.2 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_6.2.4_x64_4.0.30319.42000_core` | +| 7.0 | Ubuntu 18.04 LTS | `ubuntu_x64_18.04_7.0.0_x64_3.1.2_core` | +| 7.0 | Windows 10.0.14393 | `win-8_x64_10.0.14393.0_7.0.0_x64_3.1.2_core` | +| 7.0 | Windows 10.0.17763 | `win-8_x64_10.0.17763.0_7.0.0_x64_3.1.2_core` | +| 7.0 | Windows 10.0.18362 | `win-4_x64_10.0.18362.0_7.0.0_x64_3.1.2_core` | + +Other profiles can be found in the [GitHub repo][02]. + +You can also generate your own platform profile using the [PSCompatibilityCollector module][01]. The compatibility profile settings takes a list of platforms to target under `TargetProfiles`. A platform can be specified as: @@ -83,8 +80,8 @@ The default profile directory is under the PSScriptAnalzyer module at `$PSScriptRoot/PSCompatibilityCollector/profiles` (where `$PSScriptRoot` here refers to the directory containing `PSScriptAnalyzer.psd1`). -The compatibility analysis compares a type used to both a target profile and a "union" profile -(containing all types available in *any* profile in the profile dir). If a type is not present in +The compatibility analysis compares a type used to both a target profile and a 'union' profile +(containing all types available in _any_ profile in the profile dir). If a type is not present in the union profile, it is assumed to be locally created and ignored. Otherwise, if a type is present in the union profile but not present in a target, it is deemed to be incompatible with that target. @@ -127,11 +124,11 @@ PS> $settings = @{ Rules = @{ PSUseCompatibleTypes = @{ Enable = $true - TargetProfiles = @("win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework") + TargetProfiles = @('win-48_x64_10.0.17763.0_5.1.17763.316_x64_4.0.30319.42000_framework') } } } -PS> Invoke-ScriptAnalyzer -Settings $settings -ScriptDefinition '[System.Management.Automation.SemanticVersion]"1.18.0-rc1"' +PS> Invoke-ScriptAnalyzer -Settings $settings -ScriptDefinition "[System.Management.Automation.SemanticVersion]'1.18.0-rc1'" RuleName Severity ScriptName Line Message -------- -------- ---------- ---- ------- @@ -146,17 +143,23 @@ Command compatibility diagnostics can be suppressed with an attribute on the `pa scriptblock as with other rules. ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleTypes", "")] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleTypes', '')] ``` The rule can also be suppressed only for particular types: ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleTypes", "System.Management.Automation.Security.SystemPolicy")] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleTypes', + 'System.Management.Automation.Security.SystemPolicy')] ``` And also suppressed only for type members: ```powershell -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseCompatibleCommands", "System.Management.Automation.LanguagePrimitives/ConvertTypeNameToPSTypeName")] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseCompatibleCommands', + 'System.Management.Automation.LanguagePrimitives/ConvertTypeNameToPSTypeName')] ``` + + +[01]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector +[02]: https://github.com/PowerShell/PSScriptAnalyzer/tree/main/PSCompatibilityCollector/optional_profiles diff --git a/docs/Rules/UseConsistentIndentation.md b/docs/Rules/UseConsistentIndentation.md index de5979ff7..f084d5ac4 100644 --- a/docs/Rules/UseConsistentIndentation.md +++ b/docs/Rules/UseConsistentIndentation.md @@ -1,7 +1,6 @@ --- description: Use consistent indentation -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseConsistentIndentation --- diff --git a/docs/Rules/UseConsistentWhitespace.md b/docs/Rules/UseConsistentWhitespace.md index 3438c60ff..e36c42783 100644 --- a/docs/Rules/UseConsistentWhitespace.md +++ b/docs/Rules/UseConsistentWhitespace.md @@ -1,7 +1,6 @@ --- description: Use whitespaces -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseConsistentWhitespace --- diff --git a/docs/Rules/UseCorrectCasing.md b/docs/Rules/UseCorrectCasing.md index 8d8dd2895..b73df6415 100644 --- a/docs/Rules/UseCorrectCasing.md +++ b/docs/Rules/UseCorrectCasing.md @@ -1,7 +1,6 @@ --- description: Use exact casing of cmdlet/function/parameter name. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 03/19/2025 ms.topic: reference title: UseCorrectCasing --- @@ -11,26 +10,66 @@ title: UseCorrectCasing ## Description -This is a style/formatting rule. PowerShell is case insensitive where applicable. The casing of -cmdlet names or parameters does not matter but this rule ensures that the casing matches for -consistency and also because most cmdlets/parameters start with an upper case and using that -improves readability to the human eye. +This is a style/formatting rule. PowerShell is case insensitive wherever possible, so the casing of +cmdlet names, parameters, keywords and operators doesn't matter. This rule nonetheless ensures +consistent casing for clarity and readability. Using lowercase keywords helps distinguish them from +commands. Using lowercase operators helps distinguish them from parameters. ## How -Use exact casing of the cmdlet and its parameters, e.g. -`Invoke-Command { 'foo' } -RunAsAdministrator`. +- Use exact casing for type names. +- Use exact casing of the cmdlet and its parameters. +- Use lowercase for language keywords and operators. -## Example +## Configuration -### Wrong +```powershell +Rules = @{ + PSUseCorrectCasing = @{ + Enable = $true + CheckCommands = $true + CheckKeyword = $true + CheckOperator = $true + } +} +``` + +## Parameters + +### Enable: bool (Default value is `$false`) + +Enable or disable the rule during ScriptAnalyzer invocation. + +### CheckCommands: bool (Default value is `$true`) + +If true, require the case of all command and parameter names to match their canonical casing. + +### CheckKeyword: bool (Default value is `$true`) + +If true, require the case of all keywords to be lowercase. + +### CheckOperator: bool (Default value is `$true`) + +If true, require the case of all operators to be lowercase. For example: `-eq`, `-ne`, `-gt` + +## Examples + +### Wrong way ```powershell +ForEach ($file in Get-childitem -Recurse) { + $file.Extension -EQ '.txt' +} + invoke-command { 'foo' } -runasadministrator ``` -### Correct +### Correct way ```powershell +foreach ($file in Get-ChildItem -Recurse) { + $file.Extension -eq '.txt' +} + Invoke-Command { 'foo' } -RunAsAdministrator ``` diff --git a/docs/Rules/UseDeclaredVarsMoreThanAssignments.md b/docs/Rules/UseDeclaredVarsMoreThanAssignments.md index 3f66d7b03..e316dbf85 100644 --- a/docs/Rules/UseDeclaredVarsMoreThanAssignments.md +++ b/docs/Rules/UseDeclaredVarsMoreThanAssignments.md @@ -1,7 +1,6 @@ --- description: Extra Variables -ms.custom: PSSA v1.21.0 -ms.date: 06/30/2022 +ms.date: 03/06/2024 ms.topic: reference title: UseDeclaredVarsMoreThanAssignments --- @@ -15,7 +14,7 @@ Variables that are assigned but not used are not needed. > [!NOTE] > For this rule, the variable must be used within the same scriptblock that it was declared or it -> won't be considered to be "used". +> won't be considered to be 'used'. ## How @@ -28,8 +27,8 @@ Remove the variables that are declared but not used. ```powershell function Test { - $declaredVar = "Declared just for fun" - $declaredVar2 = "Not used" + $declaredVar = 'Declared just for fun' + $declaredVar2 = 'Not used' Write-Output $declaredVar } ``` @@ -39,15 +38,18 @@ function Test ```powershell function Test { - $declaredVar = "Declared just for fun" + $declaredVar = 'Declared just for fun' Write-Output $declaredVar } ``` -### Special case +### Special cases -The following example triggers the **PSUseDeclaredVarsMoreThanAssignments** warning because `$bar` -is not used within the scriptblock where it was defined. +The following examples trigger the **PSUseDeclaredVarsMoreThanAssignments** warning. This behavior +is a limitation of the rule. There is no way to avoid these false positive warnings. + +In this case, the warning is triggered because `$bar` is not used within the scriptblock where it +was defined. ```powershell $foo | ForEach-Object { @@ -60,3 +62,11 @@ if($bar){ Write-Host 'Collection contained a false case.' } ``` + +In the next example, the warning is triggered because `$errResult` isn't recognized as being used in +the `Write-Host` command. + +```powershell +$errResult = $null +Write-Host 'Ugh:' -ErrorVariable errResult +``` diff --git a/docs/Rules/UseLiteralInitializerForHashtable.md b/docs/Rules/UseLiteralInitializerForHashtable.md index d7ff68f80..16f9a2f9b 100644 --- a/docs/Rules/UseLiteralInitializerForHashtable.md +++ b/docs/Rules/UseLiteralInitializerForHashtable.md @@ -1,7 +1,6 @@ --- description: Create hashtables with literal initializers -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseLiteralInitializerForHashtable --- diff --git a/docs/Rules/UseOutputTypeCorrectly.md b/docs/Rules/UseOutputTypeCorrectly.md index ee531d1b7..733425fdd 100644 --- a/docs/Rules/UseOutputTypeCorrectly.md +++ b/docs/Rules/UseOutputTypeCorrectly.md @@ -1,7 +1,6 @@ --- description: Use OutputType Correctly -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseOutputTypeCorrectly --- @@ -45,6 +44,6 @@ function Get-Foo Param( ) - return "four" + return 'four' } ``` diff --git a/docs/Rules/UsePSCredentialType.md b/docs/Rules/UsePSCredentialType.md index 85e7ed2d8..c962762ed 100644 --- a/docs/Rules/UsePSCredentialType.md +++ b/docs/Rules/UsePSCredentialType.md @@ -1,7 +1,6 @@ --- description: Use PSCredential type. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UsePSCredentialType --- diff --git a/docs/Rules/UseProcessBlockForPipelineCommand.md b/docs/Rules/UseProcessBlockForPipelineCommand.md index 3bb5d2f4c..2e5630880 100644 --- a/docs/Rules/UseProcessBlockForPipelineCommand.md +++ b/docs/Rules/UseProcessBlockForPipelineCommand.md @@ -1,7 +1,6 @@ --- description: Use process block for command that accepts input from pipeline. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseProcessBlockForPipelineCommand --- diff --git a/docs/Rules/UseShouldProcessForStateChangingFunctions.md b/docs/Rules/UseShouldProcessForStateChangingFunctions.md index 84f79bbbf..97bb97767 100644 --- a/docs/Rules/UseShouldProcessForStateChangingFunctions.md +++ b/docs/Rules/UseShouldProcessForStateChangingFunctions.md @@ -1,7 +1,6 @@ --- description: Use ShouldProcess For State Changing Functions -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 12/05/2024 ms.topic: reference title: UseShouldProcessForStateChangingFunctions --- @@ -11,7 +10,12 @@ title: UseShouldProcessForStateChangingFunctions ## Description -Functions whose verbs change system state should support `ShouldProcess`. +Functions whose verbs change system state should support `ShouldProcess`. To enable the +`ShouldProcess` feature, set the `SupportsShouldProcess` argument in the `CmdletBinding` attribute. +The `SupportsShouldProcess` argument adds **Confirm** and **WhatIf** parameters to the function. The +**Confirm** parameter prompts the user before it runs the command on each object in the pipeline. +The **WhatIf** parameter lists the changes that the command would make, instead of running the +command. Verbs that should support `ShouldProcess`: @@ -59,3 +63,16 @@ function Set-ServiceObject ... } ``` + +## More information + +- [about_Functions_CmdletBindingAttribute][01] +- [Everything you wanted to know about ShouldProcess][04] +- [Required Development Guidelines][03] +- [Requesting Confirmation from Cmdlets][02] + + +[01]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_cmdletbindingattribute +[02]: https://learn.microsoft.com/powershell/scripting/developer/cmdlet/requesting-confirmation-from-cmdlets +[03]: https://learn.microsoft.com/powershell/scripting/developer/cmdlet/required-development-guidelines#support-confirmation-requests-rd04 +[04]: https://learn.microsoft.com/powershell/scripting/learn/deep-dives/everything-about-shouldprocess diff --git a/docs/Rules/UseSingularNouns.md b/docs/Rules/UseSingularNouns.md index 9dc24a017..3997c069a 100644 --- a/docs/Rules/UseSingularNouns.md +++ b/docs/Rules/UseSingularNouns.md @@ -1,7 +1,6 @@ --- description: Cmdlet Singular Noun -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 03/27/2024 ms.topic: reference title: UseSingularNouns --- @@ -11,7 +10,37 @@ title: UseSingularNouns ## Description -PowerShell team best practices state cmdlets should use singular nouns and not plurals. +PowerShell team best practices state cmdlets should use singular nouns and not plurals. Suppression +allows you to suppress the rule for specific function names. For example: + +``` +function Get-Elements { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', 'Get-Elements')] + Param() +} +``` + +## Configuration + +```powershell +Rules = @{ + PSUseSingularNouns = @{ + Enable = $true + NounAllowList = 'Data', 'Windows', 'Foos' + } +} +``` + +### Parameters + +- `Enable`: `bool` (Default value is `$true`) + + Enable or disable the rule during ScriptAnalyzer invocation. + +- `NounAllowList`: `string[]` (Default value is `{'Data', 'Windows'}`) + + Commands to be excluded from this rule. `Data` and `Windows` are common false positives and are + excluded by default. ## How diff --git a/docs/Rules/UseSupportsShouldProcess.md b/docs/Rules/UseSupportsShouldProcess.md index 20ec0b2ce..904ad0773 100644 --- a/docs/Rules/UseSupportsShouldProcess.md +++ b/docs/Rules/UseSupportsShouldProcess.md @@ -1,7 +1,6 @@ --- description: Use SupportsShouldProcess -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseSupportsShouldProcess --- @@ -13,13 +12,13 @@ title: UseSupportsShouldProcess This rule discourages manual declaration of `WhatIf` and `Confirm` parameters in a function/cmdlet. These parameters are, however, provided automatically when a function declares a `CmdletBinding` -attribute with `SupportsShouldProcess` as its named argument. Using `SupportsShouldProcess` no only +attribute with `SupportsShouldProcess` as its named argument. Using `SupportsShouldProcess` not only provides these parameters but also some generic functionality that allows the function/cmdlet authors to provide the desired interactive experience while using the cmdlet. ## Example -### Wrong: +### Wrong ```powershell function foo { @@ -31,7 +30,7 @@ function foo { } ``` -### Correct: +### Correct ```powershell function foo { diff --git a/docs/Rules/UseToExportFieldsInManifest.md b/docs/Rules/UseToExportFieldsInManifest.md index 9b3bbaaef..5faaf9d1b 100644 --- a/docs/Rules/UseToExportFieldsInManifest.md +++ b/docs/Rules/UseToExportFieldsInManifest.md @@ -1,7 +1,6 @@ --- description: Use the *ToExport module manifest fields. -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseToExportFieldsInManifest --- diff --git a/docs/Rules/UseUTF8EncodingForHelpFile.md b/docs/Rules/UseUTF8EncodingForHelpFile.md index 9452d2b9b..6d8e0f3c2 100644 --- a/docs/Rules/UseUTF8EncodingForHelpFile.md +++ b/docs/Rules/UseUTF8EncodingForHelpFile.md @@ -1,7 +1,6 @@ --- description: Use UTF8 Encoding For Help File -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 01/07/2025 ms.topic: reference title: UseUTF8EncodingForHelpFile --- @@ -11,4 +10,24 @@ title: UseUTF8EncodingForHelpFile ## Description -Check if help file uses UTF-8 encoding. +Check that an `about_` help file uses UTF-8 encoding. The filename must start with `about_` and end +with `.help.txt`. The rule uses the **CurrentEncoding** property of the **StreamReader** class to +determine the encoding of the file. + +## How + +For PowerShell commands that write to files, ensure that you set the encoding parameter to `utf8`, +`utf8BOM`, or `utf8NoBOM`. + +When you create a help file using a text editor, ensure that the editor is configured to save the +file in a UTF8 format. Consult the documentation for your text editor for instructions on how to +save files with a specific encoding. + +## Further reading + +For more information, see the following articles: + +- [System.IO.StreamReader](https://learn.microsoft.com/dotnet/api/system.io.streamreader.currentencoding) +- [about_Character_Encoding](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_character_encoding) +- [Set-Content](https://learn.microsoft.com/powershell/module/microsoft.powershell.management/set-content) +- [Understanding file encoding in VS Code and PowerShell](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/vscode/understanding-file-encoding) diff --git a/docs/Rules/UseUsingScopeModifierInNewRunspaces.md b/docs/Rules/UseUsingScopeModifierInNewRunspaces.md index b17a471b5..bde0f667d 100644 --- a/docs/Rules/UseUsingScopeModifierInNewRunspaces.md +++ b/docs/Rules/UseUsingScopeModifierInNewRunspaces.md @@ -1,7 +1,6 @@ --- description: Use 'Using:' scope modifier in RunSpace ScriptBlocks -ms.custom: PSSA v1.21.0 -ms.date: 10/18/2021 +ms.date: 06/28/2023 ms.topic: reference title: UseUsingScopeModifierInNewRunspaces --- @@ -32,40 +31,40 @@ Within the ScriptBlock, instead of just using a variable from the parent scope, ### Wrong ```powershell -$var = "foo" +$var = 'foo' 1..2 | ForEach-Object -Parallel { $var } ``` ### Correct ```powershell -$var = "foo" +$var = 'foo' 1..2 | ForEach-Object -Parallel { $using:var } ``` ## More correct examples ```powershell -$bar = "bar" -Invoke-Command -ComputerName "foo" -ScriptBlock { $using:bar } +$bar = 'bar' +Invoke-Command -ComputerName 'foo' -ScriptBlock { $using:bar } ``` ```powershell -$bar = "bar" -$s = New-PSSession -ComputerName "foo" +$bar = 'bar' +$s = New-PSSession -ComputerName 'foo' Invoke-Command -Session $s -ScriptBlock { $using:bar } ``` ```powershell # Remark: Workflow is supported on Windows PowerShell only Workflow { - $foo = "foo" + $foo = 'foo' InlineScript { $using:foo } } ``` ```powershell -$foo = "foo" +$foo = 'foo' Start-ThreadJob -ScriptBlock { $using:foo } Start-Job -ScriptBlock {$using:foo } ``` diff --git a/global.json b/global.json index e758de528..875b92095 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "3.1.424" + "version": "8.0.416", + "rollForward": "latestFeature" } } diff --git a/tools/appveyor.psm1 b/tools/appveyor.psm1 deleted file mode 100644 index 294e8d096..000000000 --- a/tools/appveyor.psm1 +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -$ErrorActionPreference = 'Stop' - -function Install-Pester { - $requiredPesterVersion = '5.3' - $pester = Get-Module Pester -ListAvailable | Where-Object { $_.Version -ge $requiredPesterVersion } - if ($null -eq $pester) { - if ($null -eq (Get-Module -ListAvailable PowershellGet)) { - # WMF 4 image build - Write-Verbose -Verbose "Installing Pester via nuget" - nuget install Pester -Version $requiredPesterVersion -source https://www.powershellgallery.com/api/v2 -outputDirectory "$env:ProgramFiles\WindowsPowerShell\Modules\." -ExcludeVersion - } - else { - # Visual Studio 2017 build (has already Pester v3, therefore a different installation mechanism is needed to make it also use the new version 4) - Write-Verbose -Verbose "Installing Pester via Install-Module" - $installedPester = Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser -Repository PSGallery -Verbose -PassThru - } - - $pesterVersion = if ($installedPester) { $installedPester.Version } else { $requiredPesterVersion } - - Write-Verbose -Verbose "Installed Pester version $pesterVersion" - } -} - -# Implements the AppVeyor 'install' step and installs the required versions of Pester, platyPS and the .Net Core SDK if needed. -function Invoke-AppVeyorInstall { - param( - # For the multi-stage build in Azure DevOps, Pester is not needed for bootstrapping the build environment - [switch] $SkipPesterInstallation - ) - - $installPowerShellModulesjobs = @() - if (-not $SkipPesterInstallation.IsPresent) { $installPowerShellModulesjobs += Start-Job ${Function:Install-Pester} } - - $installPowerShellModulesjobs += Start-Job { - if ($null -eq (Get-Module -ListAvailable PowershellGet)) { - # WMF 4 image build - Write-Verbose -Verbose "Installing platyPS via nuget" - nuget install platyPS -source https://www.powershellgallery.com/api/v2 -outputDirectory "$Env:ProgramFiles\WindowsPowerShell\Modules\." -ExcludeVersion - } - else { - Write-Verbose -Verbose "Installing platyPS via Install-Module" - Install-Module -Name platyPS -Force -Scope CurrentUser -Repository PSGallery - } - Write-Verbose -Verbose 'Installed platyPS' - } - - # Do not use 'build.ps1 -bootstrap' option for bootstraping the .Net SDK as it does not work well in CI with the AppVeyor Ubuntu image - Write-Verbose -Verbose "Installing required .Net CORE SDK" - # the legacy WMF4 image only has the old preview SDKs of dotnet - $globalDotJson = Get-Content (Join-Path $PSScriptRoot '..\global.json') -Raw | ConvertFrom-Json - $requiredDotNetCoreSDKVersion = $globalDotJson.sdk.version - if ($PSVersionTable.PSVersion.Major -gt 4) { - $requiredDotNetCoreSDKVersionPresent = (dotnet --list-sdks) -match $requiredDotNetCoreSDKVersion - } - else { - # WMF 4 image has old SDK that does not have --list-sdks parameter - $requiredDotNetCoreSDKVersionPresent = (dotnet --version).StartsWith($requiredDotNetCoreSDKVersion) - } - if (-not $requiredDotNetCoreSDKVersionPresent) { - Write-Verbose -Verbose "Installing required .Net CORE SDK $requiredDotNetCoreSDKVersion" - $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol - try { - [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 - if ($IsLinux -or $isMacOS) { - Invoke-WebRequest 'https://dot.net/v1/dotnet-install.sh' -OutFile dotnet-install.sh - bash dotnet-install.sh --version $requiredDotNetCoreSDKVersion - [System.Environment]::SetEnvironmentVariable('PATH', "/home/appveyor/.dotnet$([System.IO.Path]::PathSeparator)$PATH") - } - else { - Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile dotnet-install.ps1 - .\dotnet-install.ps1 -Version $requiredDotNetCoreSDKVersion - } - } - finally { - [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol - Remove-Item .\dotnet-install.* - } - Write-Verbose -Verbose 'Installed required .Net CORE SDK' - } - - Wait-Job $installPowerShellModulesjobs | Receive-Job - $installPowerShellModulesjobs | ForEach-Object { - if ($_.State -eq 'Failed') { - throw 'Installing PowerShell modules failed, see job logs above' - } - } -} - -# Implements AppVeyor 'test_script' step -function Invoke-AppveyorTest { - Param( - [Parameter(Mandatory)] - [ValidateScript( {Test-Path $_})] - $CheckoutPath - ) - - Install-Pester - - # enforce the language to utf-8 to avoid issues - $env:LANG = "en_US.UTF-8" - Write-Verbose -Verbose ("Running tests on PowerShell version " + $PSVersionTable.PSVersion) - Write-Verbose -Verbose "Language set to '${env:LANG}'" - - # set up env:PSModulePath to the build location, don't copy it to the "normal place" - $analyzerVersion = ([xml](Get-Content "${CheckoutPath}\Engine\Engine.csproj")).SelectSingleNode(".//VersionPrefix")."#text".Trim() - $majorVersion = ([System.Version]$analyzerVersion).Major - $psMajorVersion = $PSVersionTable.PSVersion.Major - - if ( $psMajorVersion -lt 5 ) { - $versionModuleDir = "${CheckoutPath}\out\PSScriptAnalyzer\${analyzerVersion}" - $renameTarget = "${CheckoutPath}\out\PSScriptAnalyzer\PSScriptAnalyzer" - Rename-Item "${versionModuleDir}" "${renameTarget}" - $moduleDir = "${CheckoutPath}\out\PSScriptAnalyzer" - } - else{ - $moduleDir = "${CheckoutPath}\out" - } - - $env:PSModulePath = "${moduleDir}","${env:PSModulePath}" -join [System.IO.Path]::PathSeparator - Write-Verbose -Verbose "module path: ${env:PSModulePath}" - - # Set up testing assets - [string[]] $testScripts = @( - Join-Path $CheckoutPath 'Tests\Engine' - Join-Path $CheckoutPath 'Tests\Rules' - Join-Path $CheckoutPath 'Tests\Documentation' - Join-Path $CheckoutPath 'PSCompatibilityCollector\Tests' - ) - - # Change culture to Turkish to test that PSSA works well with different locales - [System.Threading.Thread]::CurrentThread.CurrentCulture = [cultureinfo]::CreateSpecificCulture('tr-TR') - [System.Threading.Thread]::CurrentThread.CurrentUICulture = [cultureinfo]::CreateSpecificCulture('tr-TR') - - # Run all tests - Import-Module PSScriptAnalyzer - Import-Module Pester - - Write-Verbose -Verbose "Module versions:" - Get-Module PSScriptAnalyzer,Pester,PowershellGet -ErrorAction SilentlyContinue | - ForEach-Object { - Write-Verbose -Verbose "$($_.Name): $($_.Version) [$($_.Path)]" - } - - $configuration = [PesterConfiguration]::Default - $configuration.CodeCoverage.Enabled = $false - $configuration.Output.Verbosity = 'Normal' - $configuration.Run.Exit = $true - $configuration.Run.PassThru = $true - $configuration.Run.Path = $testScripts - $configuration.TestResult.Enabled = $true - Invoke-Pester -Configuration $configuration -} - -# Implements AppVeyor 'on_finish' step -function Invoke-AppveyorFinish { - $uploadUrl = "https://ci.appveyor.com/api/testresults/nunit/${env:APPVEYOR_JOB_ID}" - $testResultsPath = Join-Path $pwd TestResults.xml # default when using the -CI switch in Invoke-Pester - Write-Verbose -Verbose "Uploading test results '$testResultsPath' to '${uploadUrl}'" - $null = (New-Object 'System.Net.WebClient').UploadFile("$uploadUrl" , $testResultsPath) - - $stagingDirectory = (Resolve-Path ..).Path - $zipFile = Join-Path $stagingDirectory "$(Split-Path $pwd -Leaf).zip" - Add-Type -AssemblyName 'System.IO.Compression.FileSystem' - [System.IO.Compression.ZipFile]::CreateFromDirectory((Join-Path $pwd 'out'), $zipFile) - @( - # add test results as an artifact - (Get-ChildItem testResults.xml) - # You can add other artifacts here - (Get-ChildItem $zipFile) - ) | ForEach-Object { Push-AppveyorArtifact $_.FullName } -} diff --git a/tools/docker/ubuntu/Dockerfile b/tools/docker/ubuntu/Dockerfile deleted file mode 100644 index b3ea385fd..000000000 --- a/tools/docker/ubuntu/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM mcr.microsoft.com/powershell:ubuntu-20.04 - -ENV __InContainer 1 - -RUN apt update -qq && apt install -q -y wget git apt-transport-https -RUN wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb && dpkg -i packages-microsoft-prod.deb - -RUN apt update -qq && \ - cd / && \ - git clone https://github.com/PowerShell/PSScriptAnalyzer - -RUN pwsh -c 'save-module -name platyps,pester -path $PSHOME/Modules' diff --git a/tools/installPSResources.ps1 b/tools/installPSResources.ps1 new file mode 100644 index 000000000..48ab81bdd --- /dev/null +++ b/tools/installPSResources.ps1 @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +param( + [ValidateSet("PSGallery", "CFS")] + [string]$PSRepository = "PSGallery" +) + +if ($PSRepository -eq "CFS" -and -not (Get-PSResourceRepository -Name CFS -ErrorAction SilentlyContinue)) { + Register-PSResourceRepository -Name CFS -Uri "https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/PowerShellGalleryMirror/nuget/v3/index.json" +} + +# NOTE: Due to a bug in Install-PSResource with upstream feeds, we have to +# request an exact version. Otherwise, if a newer version is available in the +# upstream feed, it will fail to install any version at all. +Install-PSResource -Verbose -TrustRepository -RequiredResource @{ + platyPS = @{ + version = "0.14.2" + repository = $PSRepository + } + Pester = @{ + version = "5.7.1" + repository = $PSRepository + } +} diff --git a/tools/releaseBuild/AssemblySignConfig.xml b/tools/releaseBuild/AssemblySignConfig.xml deleted file mode 100644 index a3f2e3f1c..000000000 --- a/tools/releaseBuild/AssemblySignConfig.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/releaseBuild/CatalogSignConfig.xml b/tools/releaseBuild/CatalogSignConfig.xml deleted file mode 100644 index a0f7da038..000000000 --- a/tools/releaseBuild/CatalogSignConfig.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tools/releaseBuild/CredScan.Suppressions.json b/tools/releaseBuild/CredScan.Suppressions.json deleted file mode 100644 index 0a0e5db11..000000000 --- a/tools/releaseBuild/CredScan.Suppressions.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "tool": "Credential Scanner", - "suppressions": [ - { "file": "\\README.md", - "_justification": "The file refers to passwords but has no actual passwords" }, - { "file": "\\Engine\\Settings\\desktop-4.0-windows.json", - "_justification": "The file contains the list of all parameters of a cmdlet but no passwords are actually present." }, - { "file": "\\Engine\\Settings\\desktop-3.0-windows.json", - "_justification": "The file contains the list of all parameters of a cmdlet but no passwords are actually present." }, - { "file": "\\Engine\\Settings\\desktop-5.1.14393.206-windows.json", - "_justification": "The file contains the list of all parameters of a cmdlet but no passwords are actually present." }, - { "file": "\\Tests\\Engine\\RuleSuppression.tests.ps1", - "_justification": "The parameter password is used in function declaration for test but is not called and no password is present." } - ] -} - - diff --git a/tools/releaseBuild/FileCatalogSigning.xml b/tools/releaseBuild/FileCatalogSigning.xml deleted file mode 100644 index e2e2a6323..000000000 --- a/tools/releaseBuild/FileCatalogSigning.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/tools/releaseBuild/Image/DockerFile b/tools/releaseBuild/Image/DockerFile deleted file mode 100644 index 682f7248e..000000000 --- a/tools/releaseBuild/Image/DockerFile +++ /dev/null @@ -1,28 +0,0 @@ -# escape=` -#0.3.6 (no powershell 6) -# FROM microsoft/windowsservercore -FROM mcr.microsoft.com/dotnet/framework/sdk:4.8 -LABEL maintainer='PowerShell Team ' -LABEL description="This Dockerfile for Windows Server Core with git installed via chocolatey." - -SHELL ["C:\\windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-command"] -# Install Git, and platyPS -# Git installs to C:\Program Files\Git -# nuget installs to C:\ProgramData\chocolatey\bin\NuGet.exe -COPY dockerInstall.psm1 containerFiles/dockerInstall.psm1 - -RUN Import-Module PackageManagement; ` - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; ` - Import-Module ./containerFiles/dockerInstall.psm1; ` - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12; ` - Install-ChocolateyPackage -PackageName git -Executable git.exe; ` - Install-ChocolateyPackage -PackageName nuget.commandline -Executable nuget.exe -Cleanup; ` - Install-Module -Force -Name platyPS -Repository PSGallery; ` - Invoke-WebRequest -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile C:/dotnet-install.ps1; ` - C:/dotnet-install.ps1 -Version 3.1.419; ` - Add-Path C:/Users/ContainerAdministrator/AppData/Local/Microsoft/dotnet; - -COPY buildPSSA.ps1 containerFiles/buildPSSA.ps1 - -ENTRYPOINT ["C:\\windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", "-command"] - diff --git a/tools/releaseBuild/Image/buildPSSA.ps1 b/tools/releaseBuild/Image/buildPSSA.ps1 deleted file mode 100644 index bb60eb2b5..000000000 --- a/tools/releaseBuild/Image/buildPSSA.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -push-location C:/PSScriptAnalyzer -import-module C:/PSScriptAnalyzer/Utils/ReleaseMaker.psm1 -New-ReleaseBuild -Copy-Item -Recurse C:/PSScriptAnalyzer/out C:/ diff --git a/tools/releaseBuild/Image/dockerInstall.psm1 b/tools/releaseBuild/Image/dockerInstall.psm1 deleted file mode 100644 index 24d1235d6..000000000 --- a/tools/releaseBuild/Image/dockerInstall.psm1 +++ /dev/null @@ -1,114 +0,0 @@ -function Install-ChocolateyPackage -{ - param( - [Parameter(Mandatory=$true)] - [string] - $PackageName, - - [Parameter(Mandatory=$false)] - [string] - $Executable, - - [string[]] - $ArgumentList, - - [switch] - $Cleanup, - - [int] - $ExecutionTimeout = 2700, - - [string] - $Version - ) - - if(-not(Get-Command -name Choco -ErrorAction SilentlyContinue)) - { - Write-Verbose "Installing Chocolatey provider..." -Verbose - Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression - } - - Write-Verbose "Installing $PackageName..." -Verbose - $extraCommand = @() - if($Version) - { - $extraCommand += '--version', $version - } - choco install -y $PackageName --no-progress --execution-timeout=$ExecutionTimeout $ArgumentList $extraCommands - - if($executable) - { - Write-Verbose "Verifing $Executable is in path..." -Verbose - $exeSource = $null - $exeSource = Get-ChildItem -path "$env:ProgramFiles\$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - if(!$exeSource) - { - Write-Verbose "Falling back to x86 program files..." -Verbose - $exeSource = Get-ChildItem -path "${env:ProgramFiles(x86)}\$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - } - - # Don't search the chocolatey program data until more official locations have been searched - if(!$exeSource) - { - Write-Verbose "Falling back to chocolatey..." -Verbose - $exeSource = Get-ChildItem -path "$env:ProgramData\chocolatey\$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - } - - # all obvious locations are exhausted, use brute force and search from the root of the filesystem - if(!$exeSource) - { - Write-Verbose "Falling back to the root of the drive..." -Verbose - $exeSource = Get-ChildItem -path "/$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - } - - if(!$exeSource) - { - throw "$Executable not found" - } - - $exePath = Split-Path -Path $exeSource - Add-Path -path $exePath - } - - if($Cleanup.IsPresent) - { - Remove-Folder -Folder "$env:temp\chocolatey" - } -} - -function Add-Path -{ - param - ( - $path - ) - $machinePathString = [System.Environment]::GetEnvironmentVariable('path',[System.EnvironmentVariableTarget]::Machine) - $machinePath = $machinePathString -split ';' - - if($machinePath -inotcontains $path) - { - $newPath = "$machinePathString;$path" - Write-Verbose "Adding $path to path..." -Verbose - [System.Environment]::SetEnvironmentVariable('path',$newPath,[System.EnvironmentVariableTarget]::Machine) - Write-Verbose "Added $path to path." -Verbose - $env:Path += ";$newPath" - } - else - { - Write-Verbose "$path already in path." -Verbose - } -} - -function Remove-Folder -{ - param( - [string] - $Folder - ) - - Write-Verbose "Cleaning up $Folder..." -Verbose - $filter = Join-Path -Path $Folder -ChildPath * - [int]$measuredCleanupMB = (Get-ChildItem $filter -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB - Remove-Item -recurse -force $filter -ErrorAction SilentlyContinue - Write-Verbose "Cleaned up $measuredCleanupMB MB from $Folder" -Verbose -} diff --git a/tools/releaseBuild/build.json b/tools/releaseBuild/build.json deleted file mode 100644 index 8d46f2e33..000000000 --- a/tools/releaseBuild/build.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Windows": { - "Name": "win7-x64", - "RepoDestinationPath": "C:\\PSScriptAnalyzer", - "BuildCommand": "C:\\containerFiles\\buildPSSA.ps1", - "DockerFile": ".\\tools\\releaseBuild\\Image\\DockerFile", - "DockerImageName": "pssa", - "BinaryBucket": "release", - "PublishAsFolder": true, - "AdditionalContextFiles" : [ - ".\\tools\\releaseBuild\\Image\\buildPSSA.ps1", - ".\\tools\\releaseBuild\\Image\\dockerInstall.psm1" - ] - } -} diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml deleted file mode 100644 index f2fc1c3ee..000000000 --- a/tools/releaseBuild/signing.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/releaseBuild/updateSigning.ps1 b/tools/releaseBuild/updateSigning.ps1 deleted file mode 100644 index d3973fb44..000000000 --- a/tools/releaseBuild/updateSigning.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. -param( - [string] $SigningXmlPath = (Join-Path -Path $PSScriptRoot -ChildPath 'signing.xml') -) -# Script for use in VSTS to update signing.xml - -$ErrorActionPreference = 'Stop' - -# Parse the signing xml -$signingXml = [xml](Get-Content $signingXmlPath) - -# Get any variables to updating 'signType' in the XML -# Define a varabile named `SignType' in VSTS to updating that signing type -# Example: $env:AuthenticodeSignType='newvalue' -# will cause all files with the 'Authenticode' signtype to be updated with the 'newvalue' signtype -$signTypes = @{} -Get-ChildItem -Path env:/*SignType | ForEach-Object -Process { - $signType = $_.Name.ToUpperInvariant().Replace('SIGNTYPE','') - Write-Host "Found SigningType $signType with value $($_.value)" - $signTypes[$signType] = $_.Value -} - -# examine each job in the xml -$signingXml.SignConfigXML.job | ForEach-Object -Process { - # examine each file in the job - $_.file | ForEach-Object -Process { - # if the sign type is one of the variables we found, update it to the new value - $signType = $_.SignType.ToUpperInvariant() - if($signTypes.ContainsKey($signType)) - { - $newSignType = $signTypes[$signType] - Write-Host "Updating $($_.src) to $newSignType" - $_.signType = $newSignType - } - } -} - -$signingXml.Save($signingXmlPath) diff --git a/tools/releaseBuild/vstsbuild.ps1 b/tools/releaseBuild/vstsbuild.ps1 deleted file mode 100644 index 9faeedfa0..000000000 --- a/tools/releaseBuild/vstsbuild.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -[cmdletbinding()] -param ( ) - -Begin -{ - $ErrorActionPreference = 'Stop' - - $gitBinFullPath = (Get-Command -Name git -CommandType Application).Path | Select-Object -First 1 - if ( ! $gitBinFullPath ) - { - throw "Git is missing! Install from 'https://git-scm.com/download/win'" - } - - # clone the release tools - $releaseToolsDirName = "PSRelease" - $releaseToolsLocation = Join-Path -Path $PSScriptRoot -ChildPath PSRelease - if ( Test-Path $releaseToolsLocation ) - { - Remove-Item -Force -Recurse -Path $releaseToolsLocation - } - & $gitBinFullPath clone -b master --quiet https://github.com/PowerShell/${releaseToolsDirName}.git $releaseToolsLocation - Import-Module "$releaseToolsLocation/vstsBuild" -Force - Import-Module "$releaseToolsLocation/dockerBasedBuild" -Force -} - -End { - - $AdditionalFiles = .{ - Join-Path $PSScriptRoot -child "Image/buildPSSA.ps1" - Join-Path $PSScriptRoot -child "Image/dockerInstall.psm1" - } - $buildPackageName = $null - - # defined if building in VSTS - if($env:BUILD_STAGINGDIRECTORY) - { - # Use artifact staging if running in VSTS - $destFolder = $env:BUILD_STAGINGDIRECTORY - } - else - { - # Use temp as destination if not running in VSTS - $destFolder = $env:temp - } - - $resolvedRepoRoot = (Resolve-Path (Join-Path -Path $PSScriptRoot -ChildPath "../../")).Path - - try - { - Write-Verbose "Starting build at $resolvedRepoRoot ..." -Verbose - Clear-VstsTaskState - - $buildArgs = @{ - RepoPath = $resolvedRepoRoot - BuildJsonPath = './tools/releaseBuild/build.json' - Parameters = @{ ReleaseTag = "unused" } # not needed for PSSA - AdditionalFiles = $AdditionalFiles - Name = "win7-x64" - } - Invoke-Build @buildArgs - } - catch - { - Write-VstsError -Error $_ - } - finally{ - Write-VstsTaskState - exit 0 - } -} diff --git a/tools/updateVersion.ps1 b/tools/updateVersion.ps1 new file mode 100644 index 000000000..7e9d1a47d --- /dev/null +++ b/tools/updateVersion.ps1 @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +param( + [Parameter(Mandatory)] + [semver]$Version, + + [Parameter(Mandatory)] + [string]$Changes +) + +git diff --staged --quiet --exit-code +if ($LASTEXITCODE -ne 0) { + throw "There are staged changes in the repository. Please commit or reset them before running this script." +} + +$Path = "Directory.Build.props" +$f = Get-Content -Path $Path +$f = $f -replace '^(?\s+)(.+)(?)$', "`${prefix}${Version}`${suffix}" +$f | Set-Content -Path $Path +git add $Path + +$Path = "docs/Cmdlets/PSScriptAnalyzer.md" +$f = Get-Content -Path $Path +$f = $f -replace '^(?Help Version: )(.+)$', "`${prefix}${Version}" +$f | Set-Content -Path $Path +git add $Path + +git commit --edit --message "v${Version}: $Changes"