web-dev-qa-db-fra.com

Valeur de retour de la fonction dans PowerShell

J'ai développé une fonction PowerShell qui effectue un certain nombre d'actions impliquant le provisionnement SharePoint Sites d'équipe. En fin de compte, je souhaite que la fonction renvoie l'URL du site mis à disposition sous forme de chaîne. Ainsi, à la fin de ma fonction, j'ai le code suivant:

$rs = $url.ToString();
return $rs;

Le code qui appelle cette fonction ressemble à ceci:

$returnURL = MyFunction -param 1 ...

Donc, je m'attends à une chaîne, mais ce n'est pas le cas. À la place, il s'agit d'un objet de type System.Management.Automation.PSMethod. Pourquoi renvoie-t-il ce type au lieu d'un type String?

158
ChiliYago

PowerShell a une sémantique de retour vraiment farfelue, du moins dans une perspective de programmation plus traditionnelle. Il y a deux idées principales à retenir:

  • Toutes les sorties sont capturées et renvoyées
  • Le mot-clé de retour indique simplement un point de sortie logique

Ainsi, les deux blocs de script suivants feront exactement la même chose:

$a = "Hello, World"
return $a

$a = "Hello, World"
$a
return

La variable $ a du deuxième exemple reste en sortie sur le pipeline et, comme indiqué, toute la sortie est renvoyée. En fait, dans le deuxième exemple, vous pouvez omettre complètement le retour et vous obtiendrez le même comportement (le retour serait impliqué car la fonction se termine et se termine naturellement).

Sans plus de votre définition de fonction, je ne peux pas dire pourquoi vous obtenez un objet PSMethod. À mon avis, il y a probablement quelques lignes qui ne sont pas capturées et placées dans le pipeline de sortie.

Il est également intéressant de noter que vous probablement n'avez pas besoin de ces points-virgules - à moins que vous imbriquiez plusieurs expressions sur une seule ligne.

Pour en savoir plus sur la sémantique des retours, consultez la page about_Return de TechNet ou appelez la commande help return de PowerShell.

246
Goyuix

Cette partie de PowerShell est probablement l’aspect le plus stupide. Toute sortie superflue générée lors d'une fonction pollue le résultat. Parfois, il n'y a pas de sortie et, dans certaines conditions, il existe une autre sortie non planifiée, en plus de la valeur de retour planifiée.

Donc, ce que je fais est de supprimer l'affectation de l'appel de fonction d'origine pour que la sortie soit affichée à l'écran, puis de passer à autre chose jusqu'à ce que quelque chose que je n'ai pas planifié n'apparaisse dans la fenêtre du débogueur (à l'aide du PS ISE).

Même des choses comme réserver des variables dans des portées externes provoquent une sortie, comme [boolean]$isEnabled qui cracera un False de manière gênante à moins que vous ne le fassiez [boolean]$isEnabled = $false.

Un autre bon est $someCollection.Add("thing") qui crache le nouveau nombre de collections.

45
Luke Puplett

Avec PowerShell 5, nous avons maintenant la possibilité de créer des classes. Changer votre fonction dans une classe peut non seulement vous pouvez la taper et retourner retournera UNIQUEMENT l'objet qui le précède immédiatement. Voici un véritable exemple simple.

class test_class{
    [int]return_what() {
        Write-Output "Hello World"
        return 808979
    }
}
$tc = New-Object -TypeName test_class
$tc.return_what()

S'il s'agissait d'une fonction, la sortie attendue serait

Hello World
808979

mais en tant que classe, la seule chose renvoyée est un INT 808979. Une classe est en quelque sorte une garantie qu'elle ne renverra que le type déclaré ou null.

33
DaSmokeDog

En guise de solution de contournement, j'ai renvoyé le dernier objet du tableau que vous récupérez de la fonction ... ce n'est pas une bonne solution mais c'est mieux que rien:

someFunction {
   $a = "hello"
   "Function is running"
   return $a
}

$b = someFunction
$b = $b[($b.count - 1)]  # or 
$b = $b[-1]              # simpler
23
Jonesy

Je passe autour d'un objet Hashtable simple avec un seul membre de résultat pour éviter la folie de retour, car je souhaite également effectuer une sortie sur la console. Il agit en passant par référence.

function sample-loop($returnObj) {
  for($i = 0; $i -lt 10; $i++) {
    write-Host "loop counter: $i"
    $returnObj.result++
  }
}

function main-sample() {
  $countObj = @{ result = 0 }
  sample-loop -returnObj $countObj
  write-Host "_____________"
  write-Host "Total = " ($countObj.result)
}

main-sample

Vous pouvez voir un exemple réel d'utilisation sur mon projet GitHub unpackTunes

5
What Would Be Cool

C'est difficile à dire sans regarder le code. Assurez-vous que votre fonction ne renvoie pas plus d'un objet et que vous capturez les résultats obtenus à partir d'autres appels. Qu'est-ce que vous obtenez pour:

@($returnURL).count

Quoi qu'il en soit, deux suggestions:

Transforme l'objet en chaîne:

...
return [string]$rs

Ou simplement le mettre entre guillemets, comme ci-dessus mais plus court pour taper:

...
return "$rs"
4
Shay Levy

La description des résultats de la fonction par Luke dans ces scénarios semble être exacte. Vouloir seulement comprendre la cause première et que l’équipe produit PowerShell ferait quelque chose à propos du comportement, semble être assez courante et a coûté trop de temps de débogage.

Pour résoudre ce problème, j'ai utilisé des variables globales plutôt que de renvoyer et d'utiliser la valeur de l'appel de fonction.

Voici un autre sujet sur l'utilisation des variables globales: Définition d'une variable globale PowerShell à partir d'une fonction dans laquelle le nom de la variable globale est une variable transmise à la fonction

4
Ted B.

Ce qui suit renvoie simplement 4 comme réponse. Lorsque vous remplacez les expressions ajoutées pour les chaînes, la première chaîne est renvoyée.

Function StartingMain {
  $a = 1 + 3
  $b = 2 + 5
  $c = 3 + 7
  Return $a
}
Function StartingEnd($b) {
  Write-Host $b
}
StartingEnd(StartingMain)

Cela peut aussi être fait pour un tableau. L'exemple ci-dessous renverra "Text 2"

Function StartingMain {
  $a = ,@("Text 1","Text 2","Text 3")
  Return $a
}
Function StartingEnd($b) {
  Write-Host $b[1]
}
StartingEnd(StartingMain)

Notez que vous devez appeler la fonction située sous la fonction elle-même, sinon la première fois qu'elle sera exécutée, elle renverra un message d'erreur indiquant qu'il ne sait pas ce que "StartingMain" est.

J'espère que ça aide.

3
Edwin

Au lieu de return, je suggérerais Write-Output.

Write-Output peut prendre une entrée dans le pipeline et la transférer, ou bien l'imprimer sur la console s'il s'agit de la dernière commande. Cependant, Write-Output ne termine pas le script comme le ferait return, ce qui peut être un avantage si la sortie doit être utilisée/transférée/etc. en boucle.

0
Alex_P