Create Your Own PowerShell Rules for ScriptCop

Create Your Own PowerShell Rules for ScriptCop

  • Comments 1
  • Likes

Summary: James Brundage teaches how to create your own Windows PowerShell rules for ScriptCop.

 

Microsoft Scripting Guy Ed Wilson here. Today, we have part two (including the conclusion) to the article begun by James Brundage yesterday. Take it away, James!

 

Writing your own ScriptCop rules

Yesterday, I introduced you to ScriptCop, the tool to help make sure your scripts are following the rules.  Today, I’ll help you fine-tune the rules ScriptCop follows. 

Every organization does things its own way. Some people like tabs. Some like spaces. Some like aliases. Others like fully qualified commands. Some domains won’t let an unsigned script within 50 feet, and some lay out the welcome mat of “Bypass.” ScriptCop lets you write simple rules to enforce your organization’s best practices. 

To do this with ScriptCop, you’ll need to download it instead of running it online. You can download it from the Start-Automating ScriptCop site. After you unblock the file, you can unzip it to Documents\WindowsPowerShell\Modules. The scripts themselves are not signed. 

ScriptCop rules live in the rules directory. ScriptCop rules are special functions or scripts that follow one rule of their own: Test-ScriptCopRule. This makes sure the rule has the right parameters so that it can get summarized information about your command without running your command. Remember, these are “static” analysis tools: we look, but don’t touch; therefore your script is not modified or harmed in any way. 

There are a few different things we analyze, and you can write a rule that uses any of them:

  • The CommandMetaData of the command.
  • The PSModuleInfo of the module.
  • The list of PSScriptTokens and text in each command.
  • The MamlCommandHelpInfo for a command. 

The items in bold are typenames. Use them or the good old Get-Member cmdlet to find out which information is available as you write each rule. 

In today’s blog post, I will help you to write four very quick rules:

  • A rule to make sure that any parameter named name is a string.
  • A rule to make sure the module is at least version 1.0.
  • A rule to check that the command doesn’t use the Write-Host cmdlet.
  • A rule to check that the command has author information. 

A ScriptCop rule is really simplicity itself. It looks at a piece of information and writes out an error. 

The first rule, to make sure that any parameter named name is a string, is simple: We have a copy/paste header in the rule that sets up the parameters. 

The $CommandInfo variable will have the metadata about the command, including a dictionary for each parameter. We look in that dictionary for name, and then check if its parameter type isn’t a string. If it’s not, we just use the Write-Error cmdlet. When you use the Write-Error cmdlet within a ScriptCop rule, it marks the rule as having failed. The resulting function is shown here.

function Test-ParametersNamedNameAreStrings

{

    param(

    [Parameter(ParameterSetName='TestCommandInfo',Mandatory=$true,ValueFromPipeline=$true)]

    [Management.Automation.CommandInfo]

    $CommandInfo

    )

                  

    process {       

        if ($commandInfo.parameters['Name'] -and

            $commandInfo.parameters['Name'].ParameterType -ne [string]) {

            Write-Error -Message "$commandInfo -Name is not a string"           

        }

    }

}

 

The second rule, the one to determine if the module is at least version 1.0, is just as easy to create. The key here is knowing that the $moduleInfo variable has a property named version. Other than that, it is pretty straightforward. Here is the complete function: 

function Test-ModuleVersionIsAtLeastOne

{

    param(

    [Parameter(ParameterSetName='TestModuleInfo',Mandatory=$true,ValueFromPipeline=$true)]

    [Management.Automation.PSModuleInfo]

    $ModuleInfo

    )

   

    process {

        if ($ModuleInfo.Version -lt '1.0' ) {

            Write-Error "$ModuleInfo Version must be 1.0 or greater.  It was $($moduleInfo.Version)."

        }

    }

}

 

The check for the Write-Host cmdlet is similarly straightforward. Unfortunately, the parameters that this example uses are a little longer. The style of the parameters for a ScriptCop rule is what let’s it work. The styles are very picky, so always simply copy and paste the parameters from another ScriptCop rule. To learn more or see all of the possible signatures, run: 

Get-Help about_scriptcop_rules

 

Here’s the Write-Host. Don’t be daunted by all the parameter code; it was copied and pasted. The meat of the rule is just these few lines: 

$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 other scripts."

            return

        }

 

Here’s the meat and potatoes: 

Test-DoesNotUseWriteHost

{

    #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 other scripts."

            return

        }

    }

}

 

The last rule is the most complex, and by that, I mean it’s not really that hard at all. This one line looks at $helpContent (which comes preloaded with all of the Help) and tells us if it has an author: 

$hasAuthorInNotes = $helpContent.alertSet | Out-string -Width 1024 | Where-Object { $_ -ilike "author:*" } 

 

function Test-AuthorInNotes

{

    param(

    [Parameter(ParameterSetName='TestHelpContent',ValueFromPipelineByPropertyName=$true)]  

    $HelpContent,

   

    [Parameter(ParameterSetName='TestHelpContent',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]

    [Management.Automation.CommandInfo]

    $HelpCommand

    )

   

    process {

        if (-not $HelpContent) {           

            return

        }

       

        $hasAuthorInNotes = $helpContent.alertSet | Out-string -Width 1024 | Where-Object { $_ -ilike "author:*" }

       

        if (-not $hasAuthorInNotes) {

            Write-Error "$HelpCommand does not include an author in the help notes."

        }

    }

}

 

To use each of these rules, simply create a new file for each rule in the Rules folder inside the ScriptCop module directory. Then, reload ScriptCop by typing Import-Module ScriptCop –Force.

 

To run ScriptCop locally on or more commands, use: 

Get-Command $NameOfCommand  | Test-Command 

To run ScriptCop on or more modules, use: 

Get-Module $NameOfModule | Test-Command

 To run just one or two rules (like your new rules), run: 

Test-Command –Rule $NameOfRule           

To exclude rules, use: 

Test-Command -ExcludedRule $NameOfRule

By using the rules ScriptCop gives you and writing your own, you can ensure that every script in your company’s repository is exactly the way you want it to be. To find out more about the types of rules you can create, type Get-Help about_scriptcop_rules.  To download or use the latest version of the tool go, to http://scriptcop.start-automating.com/

ScriptCop is a powerful tool to help your scripts follow the rules. Now you have the power to pick and choose which rules to follow. With great power comes great responsibility—use it wisely. 

 

James, thank you for creating ScriptCop, and for taking the time to share with us some examples for its use. 

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson, Microsoft Scripting Guy 

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hi James,

    GREAT!

    A very reasonalbe thing (tool) to have in our scripting bag!

    It remindes me of the FxCop utility to check .Net code.

    Creating own rules might not be something to bother but still it is fine to have it!

    Klaus