PowerShell / PSScriptAnalyzer

Download ScriptAnalyzer from PowerShellGallery
https://www.powershellgallery.com/packages/PSScriptAnalyzer/
MIT License
1.85k stars 373 forks source link

Better way to implement rules in PS script #1551

Open rjmholt opened 4 years ago

rjmholt commented 4 years ago

PSScriptAnalyzer today supports external implementation of rules in PowerShell script.

Alongside the proposal to allow custom binary rules (https://github.com/PowerShell/PSScriptAnalyzer/issues/1543), PSScriptAnalyzer should also look at how rules written in PowerShell are implemented to ensure that PS script rules:

To address these goals, some proposals are:

Standard assumed arguments

PS script functions today have a very loose parameter contract that expects parameters that either end with the Ast suffix or the Token suffix, this means:

Instead script rules should be required to accept a clear and concrete set of parameters, similar to the way TabExpansion2 does:

param(
    [ValidateNotNullOrEmpty()]
    [System.Management.Automation.Language.Ast]
    $Ast,

    [ValidateNotNullOrEmpty()]
    [System.Management.Automation.Language.Token[]]
    $Tokens,

    [string]
    $ScriptFilePath
)

Additional points:

Attribute based rule definition

Today, PS script rules are:

To be consistent with https://github.com/PowerShell/PSScriptAnalyzer/issues/1543 and addressing https://github.com/PowerShell/PSScriptAnalyzer/issues/1545, rules instead should be declared with attributes:

[Microsoft.PowerShell.ScriptAnalyzer.Rule("AvoidAliases", "Avoid the use of aliases in PowerShell scripts")]

We could also provide a type accelerator for this attribute to reduce it to [Rule(...)].

This attribute would provide the possibility of namespace override: [Rule(..., Namespace = "MyRules")].

Write-Diagnostic instead of needing to construct diagnostic objects

Today script rules must construct and emit diagnostic objects themselves. This means:

Instead, a PowerShell-native solution would be for PSSA to introduce a new cmdlet that hooks back into the hosting script analyzer:

Write-Diagnostic -Message <string> -Extent <IScriptExtent> [-Severity <DiagnosticSeverity>]

This command can:

Constrain and document the assumptions script rules can make about their executing context

Finally, today it's not clear what assumptions a script rule should make about the context in which it's run, in particular whether it shares context with the environment running Invoke-ScriptAnalyzer.

So that PSSA can be as efficient as possible, the only assumptions script rules should make about their running environment are:

In particular, some assumptions we don't want are:

Example

So an example of a rule defined with the new proposed API would be something like this:

function Test-UsesApprovedVerbs
{
    [Microsoft.PowerShell.ScriptAnalyzer.Rule("UseApprovedVerbs", "Ensure functions use approved PowerShell verbs", Namespace = "MyRules", Severity = "Information")]
    param(
        [System.Management.Automation.Language.Ast]
        $Ast,

        [System.Management.Automation.Language.Token[]]
        $Tokens,

        [string]
        $ScriptFilePath
    )

    $commandAsts = $Ast.FindAll({ <# logic to find non-compliant ASTs #> }, $true)
    foreach ($commandAst in $commandAsts)
    {
        # Extra parameter sets or transformations could be added here to make this nicer
        Write-Diagnostic -Extent $commandAst.Extent -Message "Does not comply" -Severity Information
    }
}

This rule can be referenced with MyRules/UseApprovedVerbs in settings.

Further considerations

jegannathanmaniganadan commented 4 years ago

I am really looking forward on this,

Few doubts,

[SuppressMessageAttribute('MyRules\FooBar', 'Justification')] ?

Would it be nice if there is a way to register all (the new design allows me to specify more than one namespace) my custom rule namespace in PSSA, so that it will internally handle all the rules from that space ? Or something like below ?

Invoke-ScriptAnalyzer -CustomRulePath some.psm1 -CustomRuleSpace MyRule

Feel free to ignore if this sounds bad

rjmholt commented 4 years ago

What would be the default if not overridden ? script name ?

Currently you can't construct the attribute without a name, so you're forced to provide it. However perhaps it makes sense to use a default like:

Assuming that I override the custom rule namespace, how would the suppression look like ?

Yeah, suppression would look pretty much as you lay out.

Would it be nice if there is a way to register all (the new design allows me to specify more than one namespace) my custom rule namespace in PSSA, so that it will internally handle all the rules from that space ?

That's actually a really good consideration. The current settings proposal includes specifying a rule collection by path and then adding settings for each rule. You would then need to specify each rule you want to enable from that collection. Thinking on that now, that's cumbersome (if nicely explicit), but the problem is that any rules that require configuration settings must have those settings provided to be able to run. This makes me think that:

iRon7 commented 1 year ago

From the start that I wrote my own custom rule a few years ago, I am still wondering how to install a rule in the environment. Meaning in the same scope as where PSScriptAnalyzer is installed (everywhere where I have access to the Invoke-ScriptAnalyzer command). It looks like an too obvious question, so I am wondering if I might just completely missed something...

The document Creating custom rules, states:

PSScriptAnalyzer uses the Managed Extensibility Framework (MEF) to import all rules defined in the assembly. It can also consume rules written in PowerShell scripts.

but how do I install/add my custom PowerShell rule into the complete MEF framework?

When calling Invoke-ScriptAnalyzer, users can specify custom rules using the CustomizedRulePath parameter.

but I would like my PowerShell rule to be included automatically together with the standard rules

(See also the section Here’s how to get this on your system: in the PowerShell Injection Hunter document, are that the minimum steps to install custom rules?)

@jegannathanmaniganadan,

Would it be nice if there is a way to register all (the new design allows me to specify more than one namespace) my custom rule namespace in PSSA, so that it will internally handle all the rules from that space ?

Exactly. In fact I would even expect PSScriptAnalyzer to automatically pickup any (PowerShell) rule that I installed in my environment (through a repository as the PowerShell Gallery) or manually, e.g.:

Import-Module .\AvoidDynamicVariables.psm1

IMO, it should be possible that the command Invoke-AnalyzeScript filters the installed (PowerShell) custom rules from general commands e.g. like:

Get-Command -Verb Measure | Where-Object { $_.OutputType.Name -eq [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord] }

(or additional filters, e.g. sources that start or end with ".Rule", e.g.: .\AvoidDynamicVariables.Rule.psm1.) And simply includes them when invoked.