PowerShell: Simplifier la création de fonction avec les bons arguments

April 16, 2013

Depuis l’arrivée de SharePoint 2010, l’utilisation de PowerShell est devenue presque incontournable. Pour industrialiser certaines procédures, il n’est pas rare de créer des fonctions utilitaires, pour modulariser ses scripts et parfois même étendre la console PowerShell avec des modules personnalisés.

L’article d’aujourd’hui a pour objectif de vous montrer comment créer des fonctions plus simples à utiliser, en jouant avec le typage des paramètres.

Tout d’abord, prenons une fonction classique, sans enrichissement des paramètres :

function Get-ListCount($web){
    $web.Lists.Count
}

$web = Get-SPWeb http://localhost
Get-ListCount $web
# résultat : 29 sur mon site de test

Premier problème: les arguments ne sont pas vérifiés

Vous pouvez passer n’importe quoi à cette fonction, mais sans avoir d’information sur éventuelle erreur. Tout ces appels vont passer :

Get-ListCount http://localhost
# résultat: rien

Get-ListCount azerty
# résultat: rien

Get-ListCount $null
# résultat: rien

Get-ListCount -web $null
# résultat: rien

Toutefois, aucun de ces appels ne va montrer que quelque chose ne va pas.

Modifions notre fonction pour lui ajouter la définition des paramètres et rendre obligatoire le paramètre $web :

function Get-ListCount{
    param(
        [Parameter(Mandatory=$true)]
        $web
    )
    $web.Lists.Count
}

Le résultat des mêmes appels que précédement :

$web = Get-SPWeb http://localhost
Get-ListCount $web
# résultat : 29 sur mon site de test

Get-ListCount http://localhost
# résultat: rien

Get-ListCount azerty
# résultat: rien

Get-ListCount $null
# résultat: erreur : Get-ListCount : Impossible de lier l'argument au paramètre « web », car il a la valeur Null.

Get-ListCount -web $null
# résultat: erreur : Get-ListCount : Impossible de lier l'argument au paramètre « web », car il a la valeur Null.

On progresse. Maintenant, la fonction ne s’exécute pas si $web vaut null.

Deuxième problème: les arguments ne sont pas typés

Comme vous l’avez vu, l’argument $web doit être présent. Mais rien n’empêche de passer autre chose qu’un SPWeb !

Modifions encore notre fonction pour ajouter le typage du paramètre :

function Get-ListCount{
    param(
        [Parameter(Mandatory=$true)]
        [Microsoft.SharePoint.SPWeb]
        $web
    )
    $web.Lists.Count
}

$web = Get-SPWeb http://localhost
Get-ListCount $web
# résultat : 29 sur mon site de test

Get-ListCount http://localhost
# résultat: erreur : Get-ListCount : Impossible de traiter la transformation d'argument sur le paramètre « web ». Impossible de convertir la valeur « http://localhost » du type « System.String » en type « Microsoft.SharePoint.SPWeb ».
Get-ListCount azerty
# résultat: erreur : Get-ListCount : Impossible de traiter la transformation d'argument sur le paramètre « web ». Impossible de convertir la valeur « azerty » du type « System.String » en type « Microsoft.SharePoint.SPWeb ».
Get-ListCount $null
# résultat: erreur : Get-ListCount : Impossible de lier l'argument au paramètre « web », car il a la valeur Null.

Get-ListCount -web $null
# résultat: erreur : Get-ListCount : Impossible de lier l'argument au paramètre « web », car il a la valeur Null.

On est maintenant obligé de passer un objet de type SPWeb pour que la fonction passe

Troisième problème: l’utilisateur est obligé d’avoir un objet de type SPWeb

Ce n’est pas vraiment un problème technique, mais une question de confort d’utilisation. L’utilisateur peut vouloir appeler cette fonction avec un objet SPWeb, ou aussi une simple chaîne de caractère contenant l’url. Comme le montre les exemples ci dessus, passer http://localhost n’est pas reconnu.

Heureusement Microsoft fourni, pour les principaux objets gérable dans PowerShell des équivalent *PipeBind. Par exemple, la classe Microsoft.SharePoint.PowerShell.SPWebPipeBind permet de passer différentes formes de réprésentation d’un SPWeb.

En effet, cette classe définit les constructeurs suivants :

public SPWebPipeBind(SPWeb instance);
public SPWebPipeBind(Guid guid);
public SPWebPipeBind(string inputString);
public SPWebPipeBind(Uri webUri);

On peut en conclure que l’on peut passer soit un objet SPWeb directement, soit un ID, son url sous forme de chaine ou son url sous forme d’Uri.

Notre fonction transformée deviendra donc:

    function Get-ListCount{
         param(
             [Parameter(Mandatory=$true)]
             [Microsoft.SharePoint.PowerShell.SPWebPipeBind]
             $web
         )
         $actualWeb = $web.Read()
         $actualWeb.Lists.Count
     }

Notez la différence dans le corps de la fonction, notamment l’appel à la méthode Read() sur l’objet PipeBind

Avec comme résultats :

$web = Get-SPWeb http://localhost

Get-ListCount $web
# résultat : 29 sur mon site de test

Get-ListCount http://localhost
# résultat: 29 sur mon site de test

Get-ListCount azerty
# résultat: erreur : Exception lors de l'appel de « Read » avec « 0 » argument(s) : « Impossible de trouver un objet SPWeb avec Id or Url : azerty. »

Cette fois ci, l’appel avec la simple url du site aura fonctionné!

Quatrième problème: la fonction ne peut être utilisée dans le pipe

L’une des principales fonctionnalités de PowerShell est de proposer un pipeline. Aussi, il est naturel de vouloir utiliser la syntaxe suivante :

"http://localhost" | Get-ListCount
Get-SPWeb http://localhost | Get-ListCount

En l’état, la fonction va demander à l’utilisateur de saisir la valeur de l’argument $web en ignorant totalement le contenu du pipeline.

Modifions encore une fois notre fonction pour extraire du pipeline cet argument :

function Get-ListCount{
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [Microsoft.SharePoint.PowerShell.SPWebPipeBind]
        $web
    )
    $actualWeb = $web.Read()
    $actualWeb.Lists.Count
}

L’attribut ValueFromPipeLine indique à PowerShell d’extraire la valeur de l’argument à partir du Pipeline. Le résultat est alors celui escompté :

"http://localhost" | Get-ListCount
# résultat : 29 sur mon site de test

Get-SPWeb http://localhost | Get-ListCount
# résultat : 29 sur mon site de test

Conclusion: des meilleures fonctions!

Avec un peu d’habitude, vous améliorerez la qualité de vos fonctions. Non pas dans leur corps, mais dans la façon de les appeler. Correctement définir les paramètres permet de déceler certains problèmes en amont (typage, nullité, etc.). Hors, comme vous le savez, plus tôt ça plante mieux on se porte 🙂