en-us/Writing_A_Scriptcop_Rule.walkthru.help.txt

# ScriptCop rules are PowerShell scripts or functions that follow a particular pattern. The errors generated by these functions will show up as a failure in ScriptCop.
# There are 8 different kinds of rules:
# * CommandInfo rules - These rules work off of the core CommandInfo, which includes parameter information
# * FunctionInfo rules - These rules work off of FunctionInfo, which includes parameter information and the function definition
# * CmdletInfo rules - These rules apply to compiled cmdlets
# * ApplicationInfo rules - These rules apply to an exe or other external command, such as a document
# * ModuleInfo rules - These rules apply to module metadata
# * AliasInfo rules - These rules apply to aliases
# * ScriptToken based rules - These rules apply to the PowerShell tokens in a function
# * Help based rules - These rules apply to PowerShell help
# This is an example of a fairly simple CommandInfo rule, Test-CommandNamingConvention. FunctionInfo, AliasInfo, CmdletInfo, and ApplicationInfo rules would have a similar structure
function Test-CommandNamingConvention
{
    param(
    [Parameter(ParameterSetName='TestCommandInfo',Mandatory=$true,ValueFromPipeline=$true)]
    [Management.Automation.CommandInfo]
    $CommandInfo
    )
     
    begin {
         
        $standardVerbs = @{}
        Get-Verb |
            ForEach-Object { $standardVerbs."$($_.Verb)" = $_ }
    }
     
    process {
        $commandName = $commandInfo.Name
        $verb, $noun, $rest = $commandName -split '-'
        if (-not $noun) {
            Write-Error "$CommandInfo does not follow the verb-noun naming convention.
PowerShell commands should be named like:
 
StandardVerb-CustomNoun
 
To see all of the standard verbs, run 'Get-Verb'
"
            return
        }
         
        if ($rest) {
            Write-Error "$CommandInfo name contains an additional -, please rename the command"
            return
        }
         
        if (-not $standardVerbs.$verb) {
            Write-Error "$CommandInfo uses a non-standard verb, $Verb. Please change it. Use Get-Verb to see all verbs"
        }
         
        if ($commandName.IndexOfAny("#,(){}[]&/\`$^;:`"'<>|?@``*%+=~ ".ToCharArray()) -ne -1)
        {
            Write-Error "$commandInfo name contains invalid characters"
        }
         
    }
}
 
# To register a rule, you can either put the rule definition in the ScriptCop\Rules directory, or you can use Register-ScriptCopRule
Get-Command Test-CommandNamingConvention | Register-ScriptCopRule
 
 
# Here's an example of a rule that validates help:
function Test-Help
{
    <#
    .Synopsis
        Tests the quality of a commands's help
    .Description
        Tests the quality of a command's help. Checks for the following things:
         
        - Presence of a Help Topic
        - The same content in the synopsis and description
        - The presence of examples
        - The presence of links
    .Notes
        Version History
            10/30/2011 - ScriptCop 1.5
                - Length of Synopsis should be less than 100 characters
            7/1/2011 - ScriptCop 1.2
                - Links are present
            5/1/2011 - Initial Draft
                - Help Topic Exists
                - Synopsis -ne Description
                - Examples are present
             
             
                     
    #>
    param(
    [Parameter(ParameterSetName='TestHelpContent',ValueFromPipelineByPropertyName=$true)]
    $HelpContent,
     
    [Parameter(ParameterSetName='TestHelpContent',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [Management.Automation.CommandInfo]
    $HelpCommand
    )
     
    process {
        if (-not $HelpContent) {
            Write-Error "$HelpCommand does not have a help topic"
            return
        }
         
        $description = ($helpContent.Description | Out-String -Width 5kb).Trim()
        if ($HelpContent.Synopsis -eq $description) {
            Write-Error "$HelpCommand has the same synopsis and description"
        }
         
         
        if ($HelpContent.Synopsis.Length -gt 100) {
            Write-Error "I've heard that brevity is the soul of wit. $HelpCommand has a synopsis that is over 100 characters long."
        }
         
        if (-not $helpContent.Examples) {
            Write-Error "$HelpCommand does not have examples"
        } else {
         
            $c = 0
            foreach ($example in $helpContent.Examples.example) {
                $c++
                if (($example | Out-String) -notmatch "$HelpCommand") {
                    Write-Error "$HelpCommand example $c does not mention $helpCommand"
                }
            }
        }
         
        $parameterNames = ([Management.Automation.CommandMetaData]$helpCommand).Parameters.Keys
        $parametersWithoutHelp = @()
        foreach ($parameter in $parameterNames) {
            $parameterHasHelp = $helpContent.parameters.parameter |
                ? { $_.Name -eq $parameter } |
                Select-Object -ExpandProperty Description |
                Select-Object -ExpandProperty Text
            if (-not $parameterHasHelp) {
                $parametersWithoutHelp += $parameter
            }
        }
         
        if ($parametersWithoutHelp) {
            Write-Error "Not all parameters in $helpCommand have help. Parameters without help: $parametersWithoutHelp"
        }
         
        $relatedLinks = @(($helpContent.relatedLinks | Out-String).Trim())
        if (-not $relatedLinks) {
            Write-Error "No command is an island. Please add at least one .LINK ."
        }
 
         
         
    }
}
 
 
 
# Here's an example of a fairly simple tokenization based rule. This checks for the use of the commands Read-Host or Write-Host, or the use of the Console API. Both of these behaviors create functions that cannot be easily included in other functions, or used in other hosts like the PowerShell Integrated Scripting Environment
function Test-ForUnusableFunction
{
    #region ScriptTokenValidation Parameter Statement
    param(
    <#
    This parameter will contain the tokens in the script, and will be automatically
    provided when this command is run within ScriptCop.
     
    This parameter should not be used directly, except for testing purposes.
    #>
    [Parameter(ParameterSetName='TestScriptToken',
        Mandatory=$true,
        ValueFromPipelineByPropertyName=$true)]
    [Management.Automation.PSToken[]]
    $ScriptToken,
     
    <#
    This parameter will contain the command that was tokenized, and will be automatically
    provided when this command is run within ScriptCop.
     
    This parameter should not be used directly, except for testing purposes.
    #>
    [Parameter(ParameterSetName='TestScriptToken',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [Management.Automation.CommandInfo]
    $ScriptTokenCommand,
     
    <#
    This parameter contains the raw text of the script, and will be automatically
    provided when this command is run within ScriptCop
     
    This parameter should not be used directly, except for testing purposes.
    #>
    [Parameter(ParameterSetName='TestScriptToken',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
    [string]
    $ScriptText
    )
    #endregion ScriptTokenValidation Parameter Statement
     
     
    process {
        $hasWriteHost = $ScriptToken |
            Where-Object { $_.Type -eq "Command" -and $_.Content -eq "Write-Host" }
         
        if ($hasWriteHost) {
            Write-Error "$ScriptTokenCommand uses Write-Host. Write-Host makes your scripts unsuable inside of other scripts. If you need to add tracing, consider Write-Verbose and Write-Debug"
        }
         
        $hasReadHost = $ScriptToken |
            Where-Object { $_.Type -eq "Command" -and $_.Content -eq "Read-Host" }
         
        if ($hasReadHost) {
            Write-Error "$ScriptTokenCommand uses Read-Host. Read-Host makes your scripts unsuable inside of other scripts, because it means part of your script cannot be controlled by parameters. If you need to prompt for a value, you should create a mandatory parameter."
        }
         
         
        $hasConsoleAPI = $ScriptToken |
            Where-Object { $_.Type -eq "Type" -and
                ($_.Content -eq "Console" -or
                $_.Content -eq "System.Console")
                }
 
        if ($hasConsoleAPI) {
            Write-Error "$ScriptTokenCommand uses the console API. Using the console API ensures your script will only work in PowerShell.exe."
        }
 
    }
}
 
 
# The other type of rule you might create is based off of a module info. This rule will apply to the whole module. This particular rule checks that the module has an about topic (.help.txt)
function Test-ModuleHasAnAboutTopic
{
    param(
    [Parameter(ParameterSetName='TestModuleInfo',Mandatory=$true,ValueFromPipeline=$true)]
    [Management.Automation.PSModuleInfo]
    $ModuleInfo
    )
     
    process {
        $aboutTopics =
            $ModuleInfo |
                Split-Path |
                Get-ChildItem -Filter "$(Get-Culture)" |
                Get-ChildItem -Filter *.help.txt
         
        if (-not $aboutTopics) {
            Write-Error "$ModuleInfo does not have an about topic"
        }
    }
}