What is the recommended coding style for PowerShell?

前端 未结 3 455
萌比男神i
萌比男神i 2020-12-22 14:51

Is there any recommended coding style how to write PowerShell scripts?

It\'s not about how to structure the code (how many functions, if to use modu

相关标签:
3条回答
  • 2020-12-22 15:34

    I believe the most comprehensive coding style resource for PowerShell still is The PowerShell Best Practices and Style Guide.

    From their introduction:

    Like English spelling and grammar rules, PowerShell programming best practices and style rules nearly always have exceptions, but we are documenting a baseline for code structure, command design, programming, formatting, and even style which will help you to avoid common problems, and help you write more reusable, readable code -- because reusable code doesn't have to be rewritten, and readable code can be maintained.

    They also made these GitBook links available:

    • Online content.
    • PDF/ePub download.
    0 讨论(0)
  • 2020-12-22 15:35

    I recently came across an excellent point about indent style in PowerShell. As the linked comment states, observe the difference between these same syntaxes:

    1..10 | Sort-Object
    {
        -$_
    }
    

    and

    1..10 | Sort-Object {
        -$_
    }
    

    While my inclination is to "do as the Romans do" and use the standard C# indentation style (Allman, more or less), I take issue with this exception and others similar to it.

    This inclines me personally to use my favored 1TBS, but I could be convinced otherwise. How did you settle, out of curiosity?

    0 讨论(0)
  • 2020-12-22 15:55

    After spending a couple of years diving pretty deep into PowerShell v2.0, here's what I've settled on:

    <#
    .SYNOPSIS
    Cmdlet help is awesome.  Autogenerate via a template so I never forget.
    
    .DESCRIPTION
    .PARAMETER
    .PARAMETER
    .INPUTS
    .OUTPUTS
    .EXAMPLE
    .EXAMPLE
    .LINK
    #>
    function Get-Widget
    {
        [CmdletBinding()]
        param (
            # Think about which parameters users might loop over.  If there is a clear
            # favorite (80/20 rule), make it ValueFromPipeline and name it InputObject.
            [parameter(ValueFromPipeline=$True)]
            [alias("Server")]
            [string]$InputObject,
    
            # All other loop candidates are marked pipeline-able by property name.  Use Aliases to ensure the most 
            # common objects users want to feed in will "just work".
            [parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$True)]
            [alias("FullName")]
            [alias("Path")]
            [string[]]$Name,
    
            # Provide and document defaults for optional parameters whenever possible.
            [parameter(Position=1)]
            [int]$Minimum = 0,
    
            [parameter(Position=2)]
            [int]$ComputerName = "localhost",
    
            # Stick to standardized parameter names when possible.  *Especially* with switches.  Use Aliases to support 
            # domain-specific terminology and/or when you want to expose the parameter name of the .Net API you're wrapping.
            [parameter()]
            [Alias("IncludeFlibbles")]
            [switch]$All,
        )
    
        # The three main function blocks use this format if & only if they are short one-liners    
        begin { $buf = new-list string }
    
        # Otherwise they use spacing comparable to a C# method
        process    
        {
            # Likewise, control flow statements have a special style for one-liners
            try
            {
                # Side Note: internal variables (which may be inherited from a parent scope)  
                # are lowerCamelCase.  Direct parameters are UpperCamelCase.
                if ($All)
                    { $flibbles = $Name | Get-Flibble }   
                elseif ($Minimum -eq 0)          
                    { $flibbles = @() }
                else
                    { return }                       
    
                $path = $Name |
                    ? { $_.Length -gt $Minimum } |
                    % { $InputObject.InvokeGetAPI($_, $flibbles) } |
                    ConvertTo-FullPath
            }
            finally { Cleanup }
    
            # In general, though, control flow statements also stick to the C# style guidelines
            while($true)
            {
                Do-Something
                if ($true)
                {
                    try
                    {
                        Do-Something
                        Do-Something
                        $buf.Add("abc")
                    }
                    catch
                    {
                        Do-Something
                        Do-Something
                    }
                }            
            }    
        }    
    }
    
    <# 
    Pipelines are a form of control flow, of course, and in my opinion the most important.  Let's go 
    into more detail.
    
    I find my code looks more consistent when I use the pipeline to nudge all of PowerShell's supported 
    language constructs (within reason) toward an "infix" style, regardless of their legacy origin.  At the 
    same time, I get really strict about avoiding complexity within each line.  My style encourages a long,
    consistent "flow" of command-to-command-to-command, so we can ensure ample whitespace while remaining
    quite compact for a .NET language. 
    
    Note - from here on out I use aliases for the most common pipeline-aware cmdlets in my stable of 
    tools.  Quick extract from my "meta-script" module definition:
    sal ?? Invoke-Coalescing
    sal ?: Invoke-Ternary
    sal im Invoke-Method
    sal gpv Get-PropertyValue
    sal spv Set-PropertyValue
    sal tp Test-Path2
    sal so Select-Object2        
    sal eo Expand-Object        
    
    % and ? are your familiar friends.
    Anything else that begins with a ? is a pseudo-infix operator autogenerated from the Posh syntax reference.
    #>        
    function PipelineExamples
    {
        # Only the very simplest pipes get to be one-liners:
        $profileInfo = dir $profile | so @{Path="fullname"; KBs={$_.length/1kb}}
        $notNull = $someString | ?? ""        
        $type = $InputObject -is [Type] | ?: $InputObject $InputObject.GetType()        
        $ComObject | spv Enabled $true
        $foo | im PrivateAPI($param1, $param2)
        if ($path | tp -Unc)
            { Do-Something }
    
        # Any time the LHS is a collection (i.e. we're going to loop), the pipe character ends the line, even 
        # when the expression looks simple.
        $verySlowConcat = ""            
        $buf |
            % { $verySlowConcat += $_ }
        # Always put a comment on pipelines that have uncaptured output [destined for the caller's pipeline]
        $buf |
            ? { $_ -like "*a*" }
    
    
        # Multi-line blocks inside a pipeline:
        $orders |
            ? { 
                $_.SaleDate -gt $thisQuarter -and
                ($_ | Get-Customer | Test-Profitable) -and
                $_.TastesGreat -and
                $_.LessFilling
            } |
            so Widgets |        
            % {                
                if ($ReviewCompetition)
                {
                    $otherFirms |
                        Get-Factory |
                        Get-ManufactureHistory -Filter $_ |
                        so HistoryEntry.Items.Widgets                     
                }
                else
                {
                    $_
                }
            } |            
            Publish-WidgetReport -Format HTML
    
    
        # Mix COM, reflection, native commands, etc. seamlessly
        $flibble = Get-WmiObject SomethingReallyOpaque |
            spv AuthFlags 0xf -PassThru |
            im Put() -PassThru |
            gpv Flibbles |
            select -first 1
    
        # The coalescing operator is particularly well suited to this sort of thing
        $initializeMe = $OptionalParam |
            ?? $MandatoryParam.PropertyThatMightBeNullOrEmpty |
            ?? { pwd | Get-Something -Mode Expensive } |
            ?? { throw "Unable to determine your blahblah" }           
        $uncFolderPath = $someInput |
            Convert-Path -ea 0 |
            ?? $fallback { tp -Unc -Folder }
    
        # String manipulation        
        $myName = "First{0}   Last{1}   " |
            ?+ "Suffix{2}" |
            ?replace "{", ": {" |
            ?f {eo richard berg jr | im ToUpper}            
    
        # Math algorithms written in this style start to approach the elegance of functional languages
        $weightedAvg = $values |
            Linq-Zip $weights {$args[0] * $args[1]} |
            Linq-Sum |
            ?/ ($weights | Linq-Sum)
    }
    
    # Don't be afraid to define helper functions.  Thanks to the script:Name syntax, you don't have to cram them into 
    # the begin{} block or anything like that.  Name, parameters, etc don't always need to follow the cmdlet guidelines.
    # Note that variables from outer scopes are automatically available.  (even if we're in another file!)
    function script:Cleanup { $buf.Clear() }
    
    # In these small helpers where the logic is straightforward and the correct behavior well known, I occasionally 
    # condense the indentation to something in between the "one liner" and "Microsoft C# guideline" styles
    filter script:FixComputerName
    {
        if ($ComputerName -and $_) {            
            # Handle UNC paths 
            if ($_[1] -eq "\") {   
                $uncHost = ($_ -split "\\")[2]
                $_.Replace($uncHost, $ComputerName)
            } else {
                $drive = $_[0]
                $pathUnderDrive = $_.Remove(0,3)            
                "\\$ComputerName\$drive`$\$pathUnderDrive"
            }
        } else {
            $_
        }
    }
    

    Stack Overflow's syntax highlighter is giving up on me completely. Paste it into the ISE.

    0 讨论(0)
提交回复
热议问题