Valeur de retour de fonction dans PowerShell

188

J'ai développé une fonction PowerShell qui effectue un certain nombre d'actions impliquant le provisionnement de sites d'équipe SharePoint . En fin de compte, je souhaite que la fonction renvoie l'URL du site mis en service 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 j'attends un String, mais ce n'est pas le cas. Au lieu de cela, il s'agit d'un objet de type System.Management.Automation.PSMethod. Pourquoi renvoie-t-il ce type au lieu d'un type String?

ChiliYago
la source
2
Pouvons-nous voir la déclaration de fonction?
zdan
1
Non, pas l'invocation de la fonction, montrez-nous comment / où vous avez déclaré "MyFunction". Que se passe-t-il lorsque vous faites: Fonction Get-Item: \
MyFunction
1
Suggéré une autre façon de résoudre ce problème: stackoverflow.com/a/42842865/992301
Feru
Je pense que c'est l'une des questions les plus importantes de PowerShell sur le débordement de pile concernant les fonctions / méthodes. Si vous prévoyez d'apprendre la création de scripts PowerShell, vous devez lire cette page entière et la comprendre complètement. Vous vous détesterez plus tard si vous ne le faites pas.
Birdman

Réponses:

271

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 pour comprendre:

  • Toute la sortie est capturée et renvoyée
  • Le mot-clé return indique simplement un point de sortie logique

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

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

La variable $ a dans le deuxième exemple est laissée en sortie sur le pipeline et, comme mentionné, 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 implicite lorsque 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. Je suppose que vous avez probablement quelque chose de quelques lignes qui n'est pas capturé et qui est placé sur le pipeline de sortie.

Il est également intéressant de noter que vous n'avez probablement pas besoin de ces points-virgules, sauf si vous imbriquez plusieurs expressions sur une seule ligne.

Vous pouvez en savoir plus sur la sémantique de retour sur la page about_Return sur TechNet ou en appelant la help returncommande à partir de PowerShell lui-même.

Goyuix
la source
13
alors existe-t-il un moyen de renvoyer une valeur de scaler à partir d'une fonction?
ChiliYago
10
Si votre fonction renvoie une table de hachage, alors elle traitera le résultat comme tel, mais si vous "faites écho" quelque chose dans le corps de la fonction, y compris la sortie d'autres commandes, et soudainement le résultat est mélangé.
Luke Puplett
5
Si vous souhaitez afficher des éléments à l'écran à partir d'une fonction sans renvoyer ce texte dans le cadre du résultat de la fonction, utilisez la commande write-debugaprès avoir défini la variable $DebugPreference = "Continue"au lieu de"SilentlyContinue"
BeowulfNode42
7
Cela a aidé, mais ce stackoverflow.com/questions/22663848/… a aidé encore plus. Dirigez les résultats indésirables des commandes / appels de fonction dans la fonction appelée via "... | Out-Null", puis renvoyez simplement ce que vous voulez.
Straff
1
@LukePuplett echoétait le problème pour moi! Ce petit point secondaire est très très important. Je retournais une table de hachage, en suivant tout le reste, mais la valeur de retour n'était toujours pas ce à quoi je m'attendais. Supprimé le echoet a fonctionné comme un charme.
Ayo I
54

Cette partie de PowerShell est probablement l'aspect le plus stupide. Toute sortie étrangère générée pendant une fonction polluera le résultat. Parfois, il n'y a pas de sortie, puis dans certaines conditions, il y a une autre sortie non planifiée, en plus de votre valeur de retour prévue.

Donc, je supprime l'affectation de l'appel de fonction d'origine, de sorte que la sortie se termine à l'écran, puis je passe à travers jusqu'à ce que quelque chose que je n'avais pas prévu apparaisse dans la fenêtre du débogueur (en utilisant le PowerShell ISE ).

Même des choses comme la réservation de variables dans des portées externes provoquent une sortie, comme [boolean]$isEnabledqui crachera un faux à moins que vous ne le fassiez.[boolean]$isEnabled = $false .

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

Luke Puplett
la source
2
Pourquoi réserveriez-vous simplement un nom au lieu de faire la meilleure pratique d'initialiser correctement la variable avec une valeur explicitement définie.
BeowulfNode42
2
Je suppose que vous voulez dire que vous avez un bloc de déclarations de variables au début de chaque portée pour chaque variable utilisée dans cette portée. Vous devriez toujours, ou peut-être déjà, initialiser toutes les variables avant de les utiliser dans n'importe quel langage, y compris C #. De plus, faire [boolean]$adans PowerShell ne déclare pas réellement la variable $ a. Vous pouvez le confirmer en suivant cette ligne avec la ligne $a.gettype()et en comparant cette sortie au moment où vous déclarez et initialisez avec la chaîne $a = [boolean]$false.
BeowulfNode42
3
Vous avez probablement raison, je préférerais juste un design plus explicite pour éviter les écritures accidentelles.
Luke Puplett
4
Venant du point de vue d'un programmeur, bien que je sois un PS-n00b, je ne suis pas sûr de trouver que c'est le pire des nombreux aspects ennuyeux de la syntaxe PS. Comment diable at-il pensé qu'il était préférable d'écrire "x -gt y" plutôt que "x> y"?!? Au moins, les opérateurs intégrés auraient-ils pu utiliser une syntaxe plus conviviale?
The Dag
5
@TheDag parce que c'est d'abord un shell, puis un langage de programmation; >et <sont déjà utilisés pour la redirection de flux d'E / S vers / depuis des fichiers. >=écrit stdout dans un fichier appelé =.
TessellatingHeckler
38

Avec PowerShell 5, nous avons désormais la possibilité de créer des classes. Changez votre fonction en classe et return ne retournera que l'objet qui la précède immédiatement. Voici un exemple vraiment 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, le résultat attendu serait

Hello World
808979

mais en tant que classe, la seule chose retournée est l'entier 808979. Une classe est un peu comme une garantie qu'elle ne retournera que le type déclaré ou nul.

DaSmokeDog
la source
6
Remarque. Il prend également en charge les staticméthodes. Menant à la syntaxe[test_class]::return_what()
Sancarn
1
"Si c'était une fonction, le résultat attendu serait" Hello World \ n808979 "- Je ne pense pas que ce soit correct? La valeur de sortie est toujours uniquement la valeur de la dernière instruction, dans votre cas return 808979, fonction ou non.
amn le
1
@amn Merci! Vous avez tout à fait raison, l'exemple ne renverrait que s'il créait une sortie dans la fonction. C'est ce qui m'ennuie. Parfois, votre fonction peut générer une sortie et cette sortie plus toute valeur que vous ajoutez dans l'instruction de retour est renvoyée. Les classes que vous connaissez ne renverront que le type déclaré ou nul. Si vous préférez utiliser des fonctions, une méthode simple consiste à affecter la fonction à une variable et si plus que la valeur de retour est renvoyée, var est un tableau et la valeur de retour sera le dernier élément du tableau.
DaSmokeDog
1
Eh bien, qu'attendez-vous d'une commande appelée Write-Output? Ce n'est pas la sortie "console" qui est mentionnée ici, c'est la sortie de la fonction / cmdlet. Si vous voulez jeter des choses sur la console il y a Write-Verbose, Write-Hostet les Write-Errorfonctions, entre autres. Fondamentalement, Write-Outputest plus proche de la returnsémantique que d'une procédure de sortie de console. N'en abusez pas, utilisez Write-Verbosepour la verbosité, c'est à ça que ça sert .
amn le
1
@amn Je suis très conscient que ce n'est pas la console. C'était un moyen très rapide de montrer comment un retour traite une sortie inattendue dans une fonction par rapport à une classe. dans une fonction, toutes les sorties + la valeur que vous renvoyez sont toutes renvoyées. TOUTEFOIS, seule la valeur spécifiée dans le retour d'une classe est transmise pack. Essayez de les exécuter pour vous-même.
DaSmokeDog
31

Pour contourner le problème, j'ai renvoyé le dernier objet du tableau que vous récupérez de la fonction ... Ce n'est pas une excellente 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

Dans l'ensemble, une façon d'écrire la même chose pourrait être plus une ligne:

$b = (someFunction $someParameter $andAnotherOne)[-1]
Jonesy
la source
7
$b = $b[-1]serait un moyen plus simple d'obtenir le dernier élément ou le tableau, mais encore plus simple serait de ne pas afficher les valeurs que vous ne voulez pas.
Duncan
@Duncan Et si je veux sortir le contenu de la fonction, alors qu'en même temps je veux aussi une valeur de retour?
Utkan Gezer
@ThoAppelsin si vous voulez écrire des choses sur le terminal, utilisez write-hostpour les sortir directement.
Duncan
7

Je passe autour d'un simple objet Hashtable avec un seul membre de résultat pour éviter la folie de retour car je veux également sortir 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 d'utilisation réel sur mon projet GitHub unpackTunes .

Ce qui serait cool
la source
4

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

@($returnURL).count

Quoi qu'il en soit, deux suggestions:

Convertissez l'objet en chaîne:

...
return [string]$rs

Ou mettez-le simplement entre guillemets doubles, comme ci-dessus mais plus court à taper:

...
return "$rs"
Shay Levy
la source
1
Ou même simplement "$rs"- le returnn'est requis que lors du retour anticipé de la fonction. Laisser de côté le retour est un meilleur idiome PowerShell.
David Clarke
4

La description par Luke des résultats de la fonction dans ces scénarios semble être juste. Je souhaite seulement comprendre la cause profonde et l'équipe produit PowerShell ferait quelque chose pour le comportement. Cela semble être assez courant et m'a coûté trop de temps de débogage.

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

Voici une autre question sur l'utilisation des variables globales: Définition d'une variable globale PowerShell à partir d'une fonction où le nom de la variable globale est une variable passée à la fonction

Ted B.
la source
3

Ce qui suit renvoie simplement 4 comme réponse. Lorsque vous remplacez les expressions d'ajout 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 également être fait pour un tableau. L'exemple ci-dessous renverra "Texte 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 sous la fonction elle-même. Sinon, la première fois qu'il s'exécute, il renverra une erreur indiquant qu'il ne sait pas ce qu'est "StartingMain".

Edwin
la source
2

Les réponses existantes sont correctes, mais parfois vous ne renvoyez pas quelque chose explicitement avec a Write-Outputou a return, mais il y a une valeur mystérieuse dans les résultats de la fonction. Cela pourrait être la sortie d'une fonction intégrée commeNew-Item

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result


    Directory: C:\temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         2/9/2020   4:32 PM                132257575335253136
True

Tout ce bruit supplémentaire de la création du répertoire est collecté et émis dans la sortie. Le moyen le plus simple d'atténuer ce problème consiste à ajouter | Out-Nullà la fin de l' New-Iteminstruction, ou vous pouvez affecter le résultat à une variable et simplement ne pas utiliser cette variable. Cela ressemblerait à ceci ...

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory | Out-Null
>>    # -or-
>>    $throwaway = New-Item -Path $folderPath -ItemType Directory 
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True

New-Itemest probablement la plus célèbre d'entre elles, mais d'autres incluent toutes les StringBuilder.Append*()méthodes, ainsi que la SqlDataAdapter.Fill()méthode.

StingyJack
la source