Partager via


Chapitre 9 : Fonctions

Les lignes uniques PowerShell et les scripts qui doivent souvent être modifiés sont de bons candidats pour être transformés en fonctions réutilisables.

Écrivez des fonctions dès que vous le pouvez, car elles sont davantage orientées outils. Vous pouvez ajouter les fonctions à un module de script, placer ce module à un endroit défini dans le $env:PSModulePath et appeler les fonctions sans avoir à localiser l’endroit où les fonctions ont été enregistrées. Avec le module PowerShellGet, vous pouvez facilement partager vos modules PowerShell dans un référentiel NuGet. PowerShellGet est fourni avec PowerShell version 5.0 et versions ultérieures. Il est également disponible en téléchargement pour PowerShell version 3.0 et versions ultérieures.

Ne compliquez pas les choses. Privilégiez la simplicité et choisissez le moyen le plus rapide d'effectuer une tâche. N’utilisez pas d’alias ni de paramètres positionnels dans du code que vous comptez réutiliser. Mettez en forme votre code dans un souci d’en faciliter la lecture. Ne codez pas les valeurs en dur, utilisez des paramètres et des variables. N’écrivez pas de code inutile, même s’il n’a aucun effet gênant. Cela rend les choses plus complexes inutilement. L’attention aux détails est très importante lors de l’écriture d’un code PowerShell.

Nomination

Quand vous nommez vos fonctions dans PowerShell, utilisez un nom casse Pascal avec un verbe approuvé et un nom au singulier. Pour obtenir la liste des verbes approuvés dans PowerShell, exécutez Get-Verb. Dans l’exemple suivant, les résultats de Get-Verb sont triés par la propriété Verbe.

Get-Verb | Sort-Object -Property Verb

La propriété Group vous donne une idée de la manière dont les verbes sont censés être utilisés.

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

Il est important d'utiliser un verbe approuvé pour vos fonctions PowerShell. Les modules qui contiennent des fonctions avec des verbes non approuvés génèrent un message d’avertissement lorsqu’ils sont importés dans une session PowerShell. Ce message d’avertissement donne une image peu professionnelle à vos fonctions. De plus, les verbes non approuvés rendent vos fonctions plus difficiles à détecter.

Une fonction simple

Dans PowerShell, une fonction est déclarée avec le mot-clé function, suivi du nom de la fonction et d'une accolade ouvrante et fermante ({ }). Le code exécuté par la fonction est contenu dans ces accolades.

function Get-Version {
    $PSVersionTable.PSVersion
}

La fonction illustrée dans l’exemple suivant est un exemple simple. Elle renvoie la version de PowerShell.

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

Lorsque vous utilisez un nom générique pour vos fonctions, comme Get-Version, des conflits de nommage peuvent survenir. De futures commandes par défaut ou les commandes d’autres utilisateurs peuvent entrer en conflit avec ces noms. Préfixez la partie nom de vos noms de fonction pour éviter les conflits de noms. Par exemple : <ApprovedVerb>-<Prefix><SingularNoun>.

Dans l’exemple suivant, le préfixe PS est utilisé.

function Get-PSVersion {
    $PSVersionTable.PSVersion
}

En dehors du nom, cette fonction est identique à la précédente.

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

Même en ajoutant un préfixe, il existe toujours un risque de conflit. J'aime préfixer mes noms de fonctions avec mes initiales. Établissez une convention et respectez-la.

function Get-MrPSVersion {
    $PSVersionTable.PSVersion
}

Cette fonction n’est pas différente des deux précédentes, sauf qu'elle utilise un nom plus original pour éviter les conflits de nommage avec d’autres commandes PowerShell.

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

Une fois les fonctions chargées en mémoire, vous les voyez sur le PSDrive Function.

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

Si vous souhaitez supprimer ces fonctions de votre session active, supprimez-les du PSDrive Function ou fermez et rouvrez PowerShell.

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

Vérifiez que les fonctions ont bien été supprimées.

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

Si les fonctions ont été chargées dans un module, vous pouvez retirer le module pour les supprimer.

Remove-Module -Name <ModuleName>

L’applet de commande Remove-Module supprime les modules PowerShell de la mémoire de votre session PowerShell active. Il ne les supprime pas de votre système ou de votre disque.

Paramètres

N’attribuez pas de valeurs de manière statique. Utilisez plutôt des paramètres et des variables. Lorsque vous nommez vos paramètres, utilisez dans la mesure du possible le nom des applets de commande par défaut pour vos noms de paramètres.

Dans la fonction suivante, vous remarquerez que j’ai utiliséComputerName et non Computer, ServerName, ou Host pour le nom du paramètre. L'utilisation de ComputerName normalise le nom du paramètre pour qu'il corresponde au nom du paramètre et à la casse comme les cmdlets par défaut.

function Test-MrParameter {

    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

La fonction suivante interroge toutes les commandes de votre système et renvoie leur nombre avec les noms de paramètres spécifiques.

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

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

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

Comme vous pouvez le voir dans les résultats suivants, 39 commandes ont un paramètre ComputerName. Il n’y a pas de commande avec les paramètres Computer, ServerName, Host ou Machine.

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

Utilisez le même format de majuscules/minuscules pour vos noms de paramètres que pour les cmdlets par défaut. Par exemple, utilisez ComputerName et non computername. Cette convention de dénomination aide les personnes familiarisées avec PowerShell à découvrir vos fonctions et leur donner l'apparence et le comportement des applets de commande par défaut.

L’instruction param vous permet de définir un ou plusieurs paramètres. Une virgule (,) sépare les définitions de paramètres. Pour plus d’informations, consultez about_Functions_Advanced_Parameters.

Fonctions avancées

Il n'est pas difficile de transformer une fonction en fonction avancée dans PowerShell. L’une des différences entre une fonction et une fonction avancée tient au fait que des paramètres courants sont automatiquement ajoutés à cette dernière. Les paramètres courants comprennent des paramètres tels que Verbose et Debug.

Commencez par la fonction Test-MrParameter qui a été utilisée dans la section précédente.

function Test-MrParameter {

    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Il existe plusieurs façons de voir les paramètres courants. L'un d'eux consiste à afficher la syntaxe à l'aide de Get-Command.

Get-Command -Name Test-MrParameter -Syntax

Veuillez noter que la fonction Test-MrParameter n’a aucun paramètre commun.

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

Une autre consiste à parcourir la propriété des paramètres de Get-Command.

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

Ajoutez l'attribut CmdletBinding pour transformer la fonction en fonction avancée.

function Test-MrCmdletBinding {

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

    Write-Output $ComputerName

}

Lorsque vous spécifiez CmdletBinding, les paramètres communs sont automatiquement ajoutés. CmdletBinding requiert un bloc param, mais le bloc param peut être vide.

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

L'examen de la propriété des paramètres de Get-Command affiche les noms de paramètres réels, y compris les plus courants.

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

Prend en charge ShouldProcess

L’attribut SupportsShouldProcess ajoute les paramètres d’atténuation des risques WhatIf et Confirm. Ces paramètres sont nécessaires uniquement pour les commandes de modification.

function Test-MrSupportsShouldProcess {

    [CmdletBinding(SupportsShouldProcess)]
    param (
        $ComputerName
    )

    Write-Output $ComputerName

}

Notez qu’il existe maintenant les paramètres WhatIf et Confirm.

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

Ici encore, vous pouvez utiliser Get-Command pour renvoyer une liste des noms de paramètres réels, avec ceux des paramètres courants plus WhatIf et Confirm.

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

Validation des paramètres

La validation de l’entrée doit se faire très tôt. Ne permettez pas à votre code de continuer sur un chemin d’accès lorsqu’il ne peut pas se terminer sans entrée valide.

Spécifiez toujours un type de données pour les variables utilisées pour les paramètres. Dans l'exemple suivant, String est spécifié comme type de données pour le paramètre ComputerName. Cette validation le contraint à n'autoriser qu'un seul nom d’ordinateur unique pour le paramètre ComputerName.

function Test-MrParameterValidation {

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

    Write-Output $ComputerName

}

Une erreur est générée si plusieurs noms d’ordinateurs sont spécifiés.

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

Le problème avec la définition actuelle est qu’il est possible d’omettre la valeur du paramètre ComputerName, alors qu’une valeur est attendue pour que la fonction puisse s’exécuter. Il s'agit d'un cas pour lequel l’attribut de paramètre Mandatory est utile.

La syntaxe utilisée dans l’exemple suivant est compatible avec PowerShell 3.0 et versions ultérieures. [Parameter(Mandatory=$true)] doit être utilisé pour rendre la fonction compatible avec PowerShell version 2.0 ou versions ultérieures.

function Test-MrParameterValidation {

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

    Write-Output $ComputerName

}

Maintenant que le nom d'ordinateur est requis, s'il n'est pas spécifié, la fonction en demande un.

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

Si vous voulez autoriser plusieurs valeurs pour le paramètre ComputerName, choisissez le type de données String, mais ajoutez des crochets ([]) pour autoriser un tableau de chaînes.

function Test-MrParameterValidation {

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

    Write-Output $ComputerName

}

Vous voudrez peut-être spécifier une valeur par défaut à utiliser pour le paramètre ComputerName quand aucune valeur n’est spécifiée. Le problème est que l’utilisation de valeurs par défaut n’est pas possible avec des paramètres obligatoires. À la place, utilisez l’attribut de validation de paramètre ValidateNotNullOrEmpty avec une valeur par défaut.

Même si vous définissez une valeur par défaut, évitez d’utiliser des valeurs statiques. Dans l’exemple suivant, $env:COMPUTERNAME est utilisé comme valeur par défaut, qui est automatiquement remplacée par le nom de l’ordinateur local si aucune valeur n’est fournie.

function Test-MrParameterValidation {

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

    Write-Output $ComputerName

}

Sortie détaillée.

Les commentaires inline sont utiles si vous écrivez du code complexe, mais que les utilisateurs ne les voient pas, sauf s’ils examinent le code.

Dans l’exemple suivant, la fonction contient un commentaire inline dans la boucle foreach. Ce commentaire particulier n’est pas difficile à localiser, mais imaginez la même situation avec une fonction contenant des centaines de lignes de code.

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
    }

}

Il est préférable d’utiliser la fonction Write-Verbose au lieu de commentaires 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
    }

}

La sortie verbeuse n'est pas affichée lorsque la fonction est appelée sans le paramètre Verbose.

Test-MrVerboseOutput -ComputerName Server01, Server02

La sortie détaillée est affichée lorsque la fonction est appelée avec le paramètre Verbose.

Test-MrVerboseOutput -ComputerName Server01, Server02 -Verbose

Entrée pipeline

Un code supplémentaire est nécessaire lorsque vous voulez que votre fonction accepte l’entrée de pipeline. Comme mentionné plus haut dans ce livre, les commandes peuvent accepter l’entrée de pipeline par valeur (par type) ou par nom de propriété. Vous pouvez écrire vos fonctions comme des commandes natives pour qu’elles acceptent l’un de ces types d’entrée ou les deux.

Pour accepter une entrée de pipeline par valeur, spécifiez l'attribut de paramètre ValueFromPipeline pour ce paramètre particulier. Vous ne pouvez accepter une entrée pipeline par valeur qu'à partir d'un seul paramètre de chaque type de données. Si vous avez deux paramètres qui acceptent des chaînes de caractères, un seul d'entre eux peut accepter une entrée pipeline par valeur. Si vous avez spécifié par valeur pour les deux paramètres de chaîne, l’entrée ne sait pas quel paramètre associer. Ce scénario est une autre raison pour laquelle j'appelle ce type d'entrée de pipeline par type plutôt que par valeur.

L'entrée par pipeline est reçue un élément à la fois, de la même manière que les éléments sont traités dans une boucle foreach. Un bloc process est nécessaire pour traiter chaque élément si votre fonction accepte un tableau comme entrée. Si votre fonction n'accepte qu'une valeur simple comme entrée, un bloc process n’est pas nécessaire, mais il est recommandé pour la cohérence.

function Test-MrPipelineInput {

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

    process {
        Write-Output $ComputerName
    }

}

L’acceptation de l’entrée du pipeline par le nom de propriété est similaire, sauf que vous devez la définir avec l’attribut de paramètre ValueFromPipelineByPropertyName, et elle peut être définie pour un nombre quelconque de paramètres, quel que soit le type de données. La clé est que la sortie de la commande redirigée doit avoir un nom de propriété qui correspond au nom du paramètre ou à un alias de paramètre de votre fonction.

function Test-MrPipelineInput {

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

    process {
            Write-Output $ComputerName
    }

}

Les blocs begin et end sont facultatifs. begin est spécifié avant le bloc process et est utilisé pour effectuer tout travail initial avant que les éléments ne soient reçus du pipeline. Les valeurs qui sont entrées par pipeline ne sont pas accessibles dans le bloc begin. Le bloc end est spécifié après le bloc process et est utilisé pour le nettoyage après que tous les éléments acheminés sont traités.

Gestion des erreurs

La fonction illustrée dans l’exemple suivant génère une exception non prise en charge quand un ordinateur ne peut pas être contacté.

function Test-MrErrorHandling {

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

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

}

Il y a plusieurs manières de gérer les erreurs dans PowerShell. Try/Catch est le moyen le plus moderne de gérer les erreurs.

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"
            }
        }
    }

}

Bien que la fonction illustrée dans l’exemple précédent utilise la gestion des erreurs, elle génère une exception non prise en charge, car la commande ne génère pas d’erreur avec fin d’exécution. Seules les erreurs avec fin d’exécution sont interceptées. Spécifiez le paramètre ErrorAction avec la valeur Stop pour transformer une erreur non terminale en erreur terminale.

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"
            }
        }
    }

}

Ne modifiez pas la variable $ErrorActionPreference globale sauf si cela est absolument nécessaire. Si vous la modifiez pour une étendue locale, elle revient à la valeur précédente lorsque vous quittez cette étendue.

Si vous utilisez du code comme .NET directement dans votre fonction PowerShell, vous ne pouvez pas spécifier le paramètre ErrorAction dans la commande elle-même. Vous pouvez modifier la variable $ErrorActionPreference juste avant d’appeler la méthode .NET.

Aide basée sur les commentaires

L’ajout d’aide à vos fonctions est considéré comme une bonne pratique. L'aide permet aux personnes avec lesquelles vous partagez de savoir comment les utiliser.

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

}

Lorsque vous ajoutez une aide sous forme de commentaires à vos fonctions, l'aide peut être récupérée comme pour les commandes intégrées par défaut.

Au départ, la syntaxe d’écriture d’une fonction dans PowerShell peut sembler compliquée. Si vous ne vous souvenez pas de la syntaxe d’un élément, ouvrez une deuxième instance de l’environnement d’écriture de scripts intégré de PowerShell (ISE) sur un autre moniteur et affichez l’extrait de code « Applet de commande (fonction avancée) - Terminer » tout en tapant le code de vos fonctions. Vous pouvez accéder aux extraits de code dans PowerShell ISE en appuyant sur les touches Ctrl + J.

Récapitulatif

Dans ce chapitre, vous avez appris les bases de l’écriture de fonctions dans PowerShell, en particulier comment :

  • Créer des fonctions avancées
  • Utiliser la validation des paramètres
  • Utilisez une sortie détaillée.
  • Prendre en charge l'entrée du pipeline
  • Gérer les erreurs
  • Créer une aide basée sur des commentaires

Révision

  1. Comment pouvez-vous obtenir une liste des verbes approuvés dans PowerShell ?
  2. Comment pouvez-vous transformer une fonction PowerShell en fonction avancée ?
  3. Quand devez-vous ajouter les paramètres WhatIf et Confirm à vos fonctions PowerShell ?
  4. Comment transformer une erreur non terminale en erreur terminale ?
  5. Pourquoi devez-vous ajouter une aide basée sur les commentaires à vos fonctions ?

Références