Condividi tramite


Capitolo 9 - Funzioni

One-liner di PowerShell e script che devono essere modificati spesso sono buoni candidati per essere trasformati in funzioni riutilizzabili.

Scrivere funzioni quando possibile perché sono più orientate agli strumenti. È possibile aggiungere le funzioni a un modulo script, inserire tale modulo in una posizione definita nel $env:PSModulePathe chiamare le funzioni senza dover individuare dove sono state salvate le funzioni. Usando il modulo PowerShellGet, è facile condividere i moduli di PowerShell in un repository NuGet. PowerShellGet viene fornito con la versione 5.0 e successive di PowerShell. È disponibile anche come download separato per PowerShell versione 3.0 e successive.

Non sovraplicare le cose. Mantenere semplice e usare il modo più semplice per eseguire un'attività. Evitare alias e parametri posizionali in qualsiasi codice riutilizzato. Formattare il codice per la leggibilità. Non codificare i valori in modo fisso; utilizza parametri e variabili. Non scrivere codice non necessario anche se non fa male a nulla. Aggiunge complessità non necessarie. Prestare attenzione ai dettagli è fondamentale quando si scrive del codice PowerShell.

Denominazione

Quando si assegnano nomi alle funzioni in PowerShell, usare un caso Pascal nome con un verbo approvato e un sostantivo singolare. Per ottenere un elenco di verbi approvati in PowerShell, eseguire Get-Verb. Nell'esempio seguente, i risultati di Get-Verb sono ordinati in base alla proprietà Verb.

Get-Verb | Sort-Object -Property Verb

La proprietà Group offre un'idea del modo in cui usare i verbi.

Verb        Group
----        -----
Add         Common
Approve     Lifecycle
Assert      Lifecycle
Backup      Data
Block       Security
Checkpoint  Data
Clear       Common
Close       Common
Compare     Data
Complete    Lifecycle
Compress    Data
Confirm     Lifecycle
Connect     Communications
Convert     Data
ConvertFrom Data
ConvertTo   Data
Copy        Common
Debug       Diagnostic
Deny        Lifecycle
Disable     Lifecycle
Disconnect  Communications
Dismount    Data
Edit        Data
Enable      Lifecycle
Enter       Common
Exit        Common
Expand      Data
Export      Data
Find        Common
Format      Common
Get         Common
Grant       Security
Group       Data
Hide        Common
Import      Data
Initialize  Data
Install     Lifecycle
Invoke      Lifecycle
Join        Common
Limit       Data
Lock        Common
Measure     Diagnostic
Merge       Data
Mount       Data
Move        Common
New         Common
Open        Common
Optimize    Common
Out         Data
Ping        Diagnostic
Pop         Common
Protect     Security
Publish     Data
Push        Common
Read        Communications
Receive     Communications
Redo        Common
Register    Lifecycle
Remove      Common
Rename      Common
Repair      Diagnostic
Request     Lifecycle
Reset       Common
Resize      Common
Resolve     Diagnostic
Restart     Lifecycle
Restore     Data
Resume      Lifecycle
Revoke      Security
Save        Data
Search      Common
Select      Common
Send        Communications
Set         Common
Show        Common
Skip        Common
Split       Common
Start       Lifecycle
Step        Common
Stop        Lifecycle
Submit      Lifecycle
Suspend     Lifecycle
Switch      Common
Sync        Data
Test        Diagnostic
Trace       Diagnostic
Unblock     Security
Undo        Common
Uninstall   Lifecycle
Unlock      Common
Unprotect   Security
Unpublish   Data
Unregister  Lifecycle
Update      Data
Use         Other
Wait        Lifecycle
Watch       Common
Write       Communications

È importante usare un verbo approvato per le funzioni di PowerShell. I moduli che contengono funzioni con verbi non approvati generano un messaggio di avviso quando vengono importati in una sessione di PowerShell. Questo messaggio di avviso fa apparire le tue funzioni poco professionali. Anche i verbi non approvati limitano l'individuabilità delle funzioni.

Una funzione semplice

Una funzione in PowerShell viene dichiarata con la parola chiave function seguita dal nome della funzione e quindi da una parentesi graffa di apertura e chiusura ({ }). Il codice eseguito dalla funzione è contenuto all'interno di tali parentesi graffe.

function Get-Version {
    $PSVersionTable.PSVersion
}

La funzione illustrata nell'esempio seguente è un semplice esempio che restituisce la versione di PowerShell.

Get-Version
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  693

Quando si usa un nome generico per le funzioni, ad esempio Get-Version, potrebbe causare conflitti di denominazione. I comandi predefiniti aggiunti in futuro o i comandi che altri potrebbero scrivere potrebbero entrare in conflitto con essi. Anteporre la parte sostantiva dei nomi delle funzioni per evitare conflitti di denominazione. Ad esempio: <ApprovedVerb>-<Prefix><SingularNoun>.

Nell'esempio seguente viene usato il prefisso PS.

function Get-PSVersion {
    $PSVersionTable.PSVersion
}

Oltre al nome, questa funzione è identica a quella precedente.

Get-PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  693

È comunque possibile avere un conflitto di nomi anche quando si aggiunge un prefisso al sostantivo. Mi piace anteporre i miei sostantivi di funzione con le mie iniziali. Sviluppare uno standard e attenersi a esso.

function Get-MrPSVersion {
    $PSVersionTable.PSVersion
}

Questa funzione non è diversa dai due precedenti, ad eccezione dell'uso di un nome più univoco per tentare di evitare conflitti di denominazione con altri comandi di PowerShell.

Get-MrPSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      14393  693

Dopo il caricamento in memoria, è possibile visualizzare le funzioni nella funzione PSDrive.

Get-ChildItem -Path Function:\Get-*Version
CommandType     Name                                               Version
-----------     ----                                               -------
Function        Get-Version
Function        Get-PSVersion
Function        Get-MrPSVersion

Per rimuovere queste funzioni dalla sessione corrente, rimuoverle dalla funzione PSDrive o chiudere e riaprire PowerShell.

Get-ChildItem -Path Function:\Get-*Version | Remove-Item

Verificare che le funzioni siano state effettivamente rimosse.

Get-ChildItem -Path Function:\Get-*Version

Se le funzioni sono state caricate come parte di un modulo, è possibile scaricare il modulo per rimuoverle.

Remove-Module -Name <ModuleName>

Il cmdlet Remove-Module rimuove i moduli di PowerShell dalla memoria nella sessione di PowerShell corrente. Non li rimuove dal sistema o dal disco.

Parametri

Non assegnare valori in modo statico. Usare invece parametri e variabili. Quando si assegnano nomi ai parametri, usare lo stesso nome dei cmdlet predefiniti per i nomi dei parametri, quando possibile.

Nella funzione seguente si noti che è stato usato ComputerName e non Computer, ServerNameo Host per il nome del parametro. L'uso di ComputerName standardizza il nome del parametro in modo che corrisponda in nome e maiuscole/minuscole a quello dei cmdlet predefiniti.

function Test-MrParameter {

    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

La funzione seguente esegue una query su tutti i comandi nel sistema e restituisce il numero con nomi di parametri specifici.

function Get-MrParameterCount {
    param (
        [string[]]$ParameterName
    )

    foreach ($Parameter in $ParameterName) {
        $Results = Get-Command -ParameterName $Parameter -ErrorAction SilentlyContinue

        [pscustomobject]@{
            ParameterName   = $Parameter
            NumberOfCmdlets = $Results.Count
        }
    }
}

Come si può notare nei risultati seguenti, 39 comandi con un parametro ComputerName. Non sono disponibili comandi con parametri quali Computer, ServerName, Hosto Machine.

Get-MrParameterCount -ParameterName ComputerName, Computer, ServerName,
    Host, Machine
ParameterName NumberOfCmdlets
------------- ---------------
ComputerName               39
Computer                    0
ServerName                  0
Host                        0
Machine                     0

Usare lo stesso formato maiuscolo/minuscolo per i nomi dei parametri come nei cmdlet predefiniti. Ad esempio, usare ComputerName, non computername. Questo schema di denominazione consente agli utenti che hanno familiarità con PowerShell di individuare le funzioni e di avere un aspetto simile ai cmdlet predefiniti.

L'istruzione param consente di definire uno o più parametri. Una virgola (,) separa le definizioni dei parametri. Per altre informazioni, vedere about_Functions_Advanced_Parameters.

Funzioni avanzate

Trasformare una funzione in una funzione avanzata in PowerShell è semplice. Una delle differenze tra una funzione e una funzione avanzata è che le funzioni avanzate hanno parametri comuni che vengono aggiunti automaticamente. I parametri comuni includono parametri quali Verboso e Debug.

Iniziare con la funzione Test-MrParameter usata nella sezione precedente.

function Test-MrParameter {

    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Esistono due modi diversi per visualizzare i parametri comuni. Uno consiste nel visualizzare la sintassi con Get-Command.

Get-Command -Name Test-MrParameter -Syntax

Si noti che la funzione Test-MrParameter non ha parametri comuni.

Test-MrParameter [[-ComputerName] <Object>]

Un altro consiste nell'approfondire la proprietà parameters di Get-Command.

(Get-Command -Name Test-MrParameter).Parameters.Keys
ComputerName

Aggiungere l'attributo CmdletBinding per trasformare la funzione in una funzione avanzata.

function Test-MrCmdletBinding {

    [CmdletBinding()] # Turns a regular function into an advanced function
    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Quando si specifica CmdletBinding, i parametri comuni vengono aggiunti automaticamente. CmdletBinding richiede un blocco param, ma il blocco param può essere vuoto.

Get-Command -Name Test-MrCmdletBinding -Syntax
Test-MrCmdletBinding [[-ComputerName] <Object>] [<CommonParameters>]

Il drill-down nella proprietà dei parametri di Get-Command mostra i nomi effettivi dei parametri, inclusi quelli comuni.

(Get-Command -Name Test-MrCmdletBinding).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable

SupportsShouldProcess

L'attributo SupportsShouldProcess aggiunge i parametri di mitigazione dei rischi What If e Confirm. Questi parametri sono necessari solo per i comandi che apportano modifiche.

function Test-MrSupportsShouldProcess {

    [CmdletBinding(SupportsShouldProcess)]
    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Si noti che ora ci sono i parametri WhatIf e Confirm.

Get-Command -Name Test-MrSupportsShouldProcess -Syntax
Test-MrSupportsShouldProcess [[-ComputerName] <Object>] [-WhatIf] [-Confirm]
[<CommonParameters>]

Anche in questo caso, è possibile usare Get-Command per restituire un elenco dei nomi effettivi dei parametri, inclusi quelli comuni, insieme a WhatIf e Confirm.

(Get-Command -Name Test-MrSupportsShouldProcess).Parameters.Keys
ComputerName
Verbose
Debug
ErrorAction
WarningAction
InformationAction
ErrorVariable
WarningVariable
InformationVariable
OutVariable
OutBuffer
PipelineVariable
WhatIf
Confirm

Convalida dei parametri

Convalidare l'input in anticipo. Non consentire al codice di continuare in un percorso quando non può essere completato senza input valido.

Specificare sempre un tipo di dati per le variabili usate per i parametri. Nell'esempio seguente, Stringa viene specificato come tipo di dati per il parametro ComputerName. Questa convalida limita a consentire solo la specifica di un singolo nome di computer per il parametro NomeComputer.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [string]$ComputerName
    )

    Write-Output $ComputerName

}

Se si specificano più nomi di computer, viene generato un errore.

Test-MrParameterValidation -ComputerName Server01, Server02
Test-MrParameterValidation : Cannot process argument transformation on
parameter 'ComputerName'. Cannot convert value to type System.String.
At line:1 char:42
+ Test-MrParameterValidation -ComputerName Server01, Server02
+                                          ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [Test-MrParameterValidation]
   , ParameterBindingArgumentTransformationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-MrP
   arameterValidation

Il problema con la definizione corrente è che è valido omettere il valore del parametro ComputerName, ma per il completamento della funzione è necessario un valore. Questo scenario è il caso in cui l'attributo del parametro Mandatory sia utile.

La sintassi usata nell'esempio seguente è compatibile con PowerShell versione 3.0 e successive. [Parameter(Mandatory=$true)] si può specificare per rendere la funzione compatibile con la versione 2.0 di PowerShell o successiva.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$ComputerName
    )

    Write-Output $ComputerName

}

Ora che è necessario il nome del computer , se uno non è specificato, la funzione ne richiede uno.

Test-MrParameterValidation
cmdlet Test-MrParameterValidation at command pipeline position 1
Supply values for the following parameters:
ComputerName:

Se si desidera consentire più valori per il parametro ComputerName, usare il tipo di dati String ma aggiungere parentesi quadre () al tipo di dati per consentire una matrice di stringhe.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string[]]$ComputerName
    )

    Write-Output $ComputerName

}

Forse vuoi specificare un valore predefinito per il parametro ComputerName se non è specificato. Il problema è che i valori predefiniti non possono essere usati con parametri obbligatori. Usare invece l'attributo di convalida dei parametri ValidateNotNullOrEmpty con un valore predefinito.

Anche quando si imposta un valore predefinito, provare a non usare valori statici. Nell'esempio seguente, $env:COMPUTERNAME viene usato come valore predefinito, che viene convertito automaticamente nel nome del computer locale se non viene specificato un valore.

function Test-MrParameterValidation {

    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    Write-Output $ComputerName

}

Output verboso

I commenti inline sono utili se si scrive codice complesso, ma gli utenti non li vedono a meno che non esaminino il codice.

La funzione nell'esempio seguente ha un commento inline nel ciclo foreach. Anche se questo particolare commento potrebbe non essere difficile da individuare, immaginare se la funzione conteneva centinaia di righe di codice.

function Test-MrVerboseOutput {

    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    foreach ($Computer in $ComputerName) {
        #Attempting to perform an action on $Computer <<-- Don't use
        #inline comments like this, use write verbose instead.
        Write-Output $Computer
    }

}

Un'opzione migliore consiste nell'usare Write-Verbose anziché commenti inline.

function Test-MrVerboseOutput {

    [CmdletBinding()]
    param (
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME
    )

    foreach ($Computer in $ComputerName) {
        Write-Verbose -Message "Attempting to perform an action on $Computer"
        Write-Output $Computer
    }

}

L'output verbose non viene visualizzato quando la funzione viene chiamata senza il parametro Verbose.

Test-MrVerboseOutput -ComputerName Server01, Server02

L'output dettagliato viene visualizzato quando la funzione viene chiamata con il parametro Verbose.

Test-MrVerboseOutput -ComputerName Server01, Server02 -Verbose

Input della pipeline

Quando si vuole che la funzione accetti l'input della pipeline, è necessario codice aggiuntivo. Come accennato in precedenza in questo libro, i comandi possono accettare input della pipeline per valore per tipo o in base al nome della proprietà. È possibile scrivere funzioni come i comandi nativi in modo che accettino uno o entrambi questi tipi di input.

Per accettare input della pipeline per valore, specificare l'attributo ValueFromPipeline per quel particolare parametro. Si può accettare l'input della pipeline solo per valore da un parametro per ciascun tipo di dati. Se si dispone di due parametri che accettano un input di tipo stringa, solo uno di essi può accettare l'input della pipeline per valore. Se specifichi per valore per entrambi i parametri stringa, l'input non saprebbe a quale parametro associarsi. Questo scenario è un altro motivo per cui io chiamo questo tipo di input della pipeline in base al tipo anziché in base al valore.

L'input della pipeline viene ricevuto un elemento alla volta, in modo analogo al modo in cui gli elementi vengono gestiti in un ciclo di foreach. È necessario un blocco process per elaborare ogni elemento se la funzione accetta una matrice come input. Se la funzione accetta solo un singolo valore come input, non è necessario un blocco process ma consigliato per la coerenza.

function Test-MrPipelineInput {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline)]
        [string[]]$ComputerName
    )

    process {
        Write-Output $ComputerName
    }

}

L'accettazione dell'input della pipeline in base al nome della proprietà è simile, tranne che viene specificata con l'attributo del parametro ValueFromPipelineByPropertyName e può essere applicata a qualsiasi numero di parametri, indipendentemente dal tipo di dati. La chiave è che l'output del comando passato tramite pipe deve avere un nome di proprietà corrispondente al nome del parametro o a un alias del parametro della tua funzione.

function Test-MrPipelineInput {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
            Write-Output $ComputerName
    }

}

i blocchi begin e end sono facoltativi. begin viene specificato prima del blocco process e viene usato per eseguire qualsiasi operazione iniziale prima che gli elementi vengano ricevuti dalla pipeline. I valori inviati tramite pipe in non sono accessibili nel blocco begin. Il blocco end viene specificato dopo il blocco process e viene utilizzato per la pulizia dopo l'elaborazione di tutti gli elementi inviati tramite pipe in.

Gestione degli errori

La funzione illustrata nell'esempio seguente genera un'eccezione non gestita quando non è possibile contattare un computer.

function Test-MrErrorHandling {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
        foreach ($Computer in $ComputerName) {
            Test-WSMan -ComputerName $Computer
        }
    }

}

Esistono due modi diversi per gestire gli errori in PowerShell. Try/Catch è il modo più moderno per gestire gli errori.

function Test-MrErrorHandling {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
        foreach ($Computer in $ComputerName) {
            try {
                Test-WSMan -ComputerName $Computer
            }
            catch {
                Write-Warning -Message "Unable to connect to Computer: $Computer"
            }
        }
    }

}

Anche se la funzione illustrata nell'esempio precedente usa la gestione degli errori, genera un'eccezione non gestita perché il comando non genera un errore irreversibile. Vengono rilevati solo errori che provocano la terminazione. Specificare il parametro ErrorAction con Stop come valore per trasformare un errore non terminante in uno terminante.

function Test-MrErrorHandling {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory,
                   ValueFromPipeline,
                   ValueFromPipelineByPropertyName)]
        [string[]]$ComputerName
    )

    process {
        foreach ($Computer in $ComputerName) {
            try {
                Test-WSMan -ComputerName $Computer -ErrorAction Stop
            }
            catch {
                Write-Warning -Message "Unable to connect to Computer: $Computer"
            }
        }
    }

}

Non modificare la variabile $ErrorActionPreference globale a meno che non sia assolutamente necessario. Se lo si modifica in un ambito locale, viene ripristinato il valore precedente quando si esce da tale ambito.

Se si usa qualcosa di simile a .NET direttamente dall'interno della funzione di PowerShell, non è possibile specificare il parametro ErrorAction nel comando stesso. È possibile modificare la variabile $ErrorActionPreference subito prima di chiamare il metodo .NET.

Guida basata su commenti

L'aggiunta di guida alle funzioni è considerata una buona pratica. Le funzioni di aiuto consentono alle persone con cui vengono condivise di sapere come utilizzarle.

function Get-MrAutoStoppedService {

<#
.SYNOPSIS
    Returns a list of services that are set to start automatically, are not
    currently running, excluding the services that are set to delayed start.

.DESCRIPTION
    Get-MrAutoStoppedService is a function that returns a list of services
    from the specified remote computer(s) that are set to start
    automatically, are not currently running, and it excludes the services
    that are set to start automatically with a delayed startup.

.PARAMETER ComputerName
    The remote computer(s) to check the status of the services on.

.PARAMETER Credential
    Specifies a user account that has permission to perform this action. The
    default is the current user.

.EXAMPLE
     Get-MrAutoStoppedService -ComputerName 'Server1', 'Server2'

.EXAMPLE
     'Server1', 'Server2' | Get-MrAutoStoppedService

.EXAMPLE
     Get-MrAutoStoppedService -ComputerName 'Server1' -Credential (Get-Credential)

.INPUTS
    String

.OUTPUTS
    PSCustomObject

.NOTES
    Author:  Mike F. Robbins
    Website: https://mikefrobbins.com
    Twitter: @mikefrobbins
#>

    [CmdletBinding()]
    param (

    )

    #Function Body

}

Quando aggiungi una documentazione basata su commenti alle tue funzioni, è possibile richiamare l'aiuto per esse, proprio come avviene per i comandi predefiniti.

La sintassi per scrivere una funzione in PowerShell può sembrare opprimente per chi inizia. Se non è possibile ricordare la sintassi per un elemento, aprire una seconda istanza di PowerShell Integrated Scripting Environment (ISE) in un monitor separato e visualizzare il frammento "Cmdlet (funzione avanzata) - Complete" durante la digitazione nel codice per le funzioni. È possibile accedere ai frammenti di codice in PowerShell ISE usando la combinazione di tasti CTRL + J.

Sommario

In questo capitolo sono state illustrate le nozioni di base sulla scrittura di funzioni in PowerShell, tra cui le procedure seguenti:

  • Creare funzioni avanzate
  • Usare la convalida dei parametri
  • Usare l'output dettagliato
  • Supporto per l'input della pipeline
  • Gestire gli errori
  • Creare un aiuto basato su commenti

Recensione

  1. Come si ottiene un elenco di verbi approvati in PowerShell?
  2. Come si trasforma una funzione di PowerShell in una funzione avanzata?
  3. Quando si dovrebbero aggiungere i parametri WhatIf e Confirm alle funzioni di PowerShell?
  4. Come si trasforma un errore non terminante in un errore terminante?
  5. Perché è consigliabile aggiungere una Guida basata su commenti alle funzioni?

Referenze

Passaggi successivi

Nel capitolo 10 si apprenderà come creare pacchetti di funzioni nei moduli di script. Si esamineranno la struttura dei moduli, i manifesti, l'esportazione di comandi pubblici e le procedure consigliate per organizzare, condividere e gestire gli strumenti di PowerShell riutilizzabili.