MAIN MENU
Blog de Devolutions

Anuncios, actualizaciones y análisis de Devolutions.

PowerShell Universal custom PSScriptAnalyzer rules illustration for the Devolutions blog.

Crear reglas PSScriptAnalyzer personalizadas para PowerShell Universal

Recorra las reglas predeterminadas de PSScriptAnalyzer, los archivos de configuración y una regla personalizada de PowerShell Universal que señala asignaciones a variables integradas con ScriptBlockAst y DiagnosticRecord.

Como parte de la versión 5 de PowerShell Universal, estamos añadiendo algunas reglas PSScriptAnalyzer personalizadas a la plataforma para detectar más fácilmente problemas al crear scripts. En esta entrada veremos cómo crear reglas PSScriptAnalyzer personalizadas.

PSScriptAnalyzer

PSScriptAnalyzer es una herramienta de análisis estático de código para scripts de PowerShell. Comprueba la calidad del código según un conjunto de reglas. Las reglas integradas cubren todo tipo de aspectos, como el uso de alias, los espacios al final de las líneas, las comprobaciones $null y más. En editores como VS Code, PSScriptAnalyzer se ejecuta en segundo plano para ofrecer comentarios en tiempo real sobre sus scripts.

Internamente, la extensión de PowerShell ejecuta Invoke-ScriptAnalyzer para ofrecer esos comentarios. Puede ejecutar este comando usted mismo para ver los resultados del análisis. Ejecutarlo solo con la ruta aplica todas las reglas predeterminadas.

Invoke-ScriptAnalyzer -Path .\MyScript.ps1

También puede excluir reglas con parámetros del cmdlet.

Invoke-ScriptAnalyzer -Path .\MyScript.ps1 -ExcludeRule PSAvoidUsingCmdletAliases

Para personalizar el comportamiento de PSScriptAnalyzer sin modificar la línea de comandos, también puede usar un archivo de configuración. Este archivo cubre las mismas opciones que los distintos parámetros de Invoke-ScriptAnalyzer.

# PSScriptAnalyzerSettings.psd1
@{
    Severity=@('Error','Warning')
    ExcludeRules=@('PSAvoidUsingCmdletAliases', 'PSAvoidUsingWriteHost')
}

Puede usar archivos de configuración del analizador de scripts pasando el parámetro -Settings a Invoke-ScriptAnalyzer.

Invoke-ScriptAnalyzer -Path MyScript.ps1 -Settings PSScriptAnalyzerSettings.psd1

También puede usar este archivo de configuración con VS Code estableciendo PowerShell > Script Analysis: Settings Path en PSScriptAnalyzerSettings.psd1.

VS Code PowerShell Script Analysis Settings Path pointing at PSScriptAnalyzerSettings.psd1.
Ruta de configuración de PSScriptAnalyzer en VS Code

Reglas personalizadas

Las reglas personalizadas se definen con módulos de PowerShell. Pueden aceptar una lista de tokens o un árbol de sintaxis abstracta (AST) y devolver registros de diagnóstico. Por ejemplo, en PowerShell Universal, tenemos una regla que comprueba el uso de variables integradas como $PSUEnvironment o $UniversalClient. Asignar esas variables puede provocar un comportamiento indefinido, así que queremos que los usuarios lo sepan de inmediato cuando intenten sobrescribirlas.

El primer paso es crear un nuevo archivo de módulo .psm1 y definir una función que analice el script. En este ejemplo, hemos elegido Universal.Rules.psm1 como nombre de archivo y hemos definido la función Measure-PSUBuiltInVariables. La ayuda basada en comentarios es obligatoria, y hay algunos detalles específicos de esta función que conviene señalar.

Primero, debe incluir el atributo OutputType para indicar el tipo de objeto que devolverá la función. En este caso, devolverá una matriz de objetos Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord. También debe aceptar un objeto ScriptBlockAst como parámetro. Este objeto representa el bloque de script del script que se está analizando.

<#
.SYNOPSIS
    Locates built-in PowerShell Universal variables
.DESCRIPTION
    Checks to make sure that built-in PowerShell Universal variables are not being used in the script.
.EXAMPLE
    Measure-PSUBuiltInVariables -ScriptBlockAst $ScriptBlockAst
.INPUTS
    [System.Management.Automation.Language.ScriptBlockAst]
.OUTPUTS
    [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
.NOTES
    None
#>
function Measure-PSUBuiltInVariables {
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    Param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ScriptBlockAst]
        $ScriptBlockAst
    )

El cuerpo de esta función realiza el trabajo principal de analizar el bloque de script. En nuestro ejemplo, recorremos el AST para encontrar instrucciones de asignación. Si encontramos instrucciones de asignación, comprobamos a continuación si el lado izquierdo de la asignación es una variable. Si es así, usamos entonces un método estático para verificar si es una de las variables integradas de PowerShell Universal.

Si se cumplen las condiciones, creamos nuevos objetos DiagnosticRecord y rellenamos la información necesaria. $_.Extent es importante porque indica a los editores, como VS Code o Monaco, qué parte del script resaltar con la advertencia o el error.

    Process {
        $results = @()

        try {
            #region Define predicates to find ASTs.
            [ScriptBlock]$predicate1 = {
                param ([System.Management.Automation.Language.Ast]$Ast)

                if ($Ast -is [System.Management.Automation.Language.AssignmentStatementAst]) {
                    if ($Ast.Left -is [System.Management.Automation.Language.VariableExpressionAst]) {
                        return [PowerShellUniversal.BuiltInVariables]::IsBuiltInVariable($Ast.Left.VariablePath.UserPath)
                    }
                }

                return $false
            }

            #endregion

            #region Finds ASTs that match the predicate.

            [System.Management.Automation.Language.Ast[]]$methodAst = $ScriptBlockAst.FindAll($predicate1, $true)

            $methodAst | ForEach-Object {
                $result = New-Object `
                    -Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
                    -ArgumentList "Overriding built in PowerShell Universal variables can cause undefined behavior.", $_.Extent, $PSCmdlet.MyInvocation.InvocationName, Warning, $null
                $results += $result
            }

            return $results

            #endregion
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }

Por último, nos aseguramos de exportar la función desde nuestro módulo.

Export-ModuleMember -Function Measure-PSUBuiltInVariables

Dentro de PowerShell Universal, usamos Invoke-ScriptAnalyzer para ejecutar el análisis de código. Mientras escribe en el editor, se llama a esta función para ofrecer comentarios en tiempo real. Para usar nuestra regla personalizada, pasamos el parámetro -CustomRulePath a Invoke-ScriptAnalyzer.

Invoke-ScriptAnalyzer -ScriptDefinition $code -CustomRulePath $InstallDir\Modules\Universal.Rules\Universal.Rules.psm1 -IncludeDefaultRules

A continuación procesamos la salida de PSScriptAnalyzer para ofrecer comentarios al usuario.

PowerShell Universal editor warning that overriding built-in variables causes undefined behavior.
Regla PSScriptAnalyzer personalizada en el editor de PowerShell Universal

¿Tiene ideas de reglas personalizadas para PowerShell Universal? Cuéntenoslo.

¿Listo para crear? Descargue PowerShell Universal.