Funktionsrückgabewert in PowerShell

188

Ich habe eine PowerShell-Funktion entwickelt, die eine Reihe von Aktionen zum Bereitstellen von SharePoint Team-Websites ausführt . Letztendlich möchte ich, dass die Funktion die URL der bereitgestellten Site als Zeichenfolge zurückgibt, sodass ich am Ende meiner Funktion den folgenden Code habe:

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

Der Code, der diese Funktion aufruft, sieht folgendermaßen aus:

$returnURL = MyFunction -param 1 ...

Ich erwarte also einen String, aber das ist es nicht. Stattdessen handelt es sich um ein Objekt vom Typ System.Management.Automation.PSMethod. Warum wird dieser Typ anstelle eines String-Typs zurückgegeben?

ChiliYago
quelle
2
Können wir die Funktionsdeklaration sehen?
zdan
1
Nein, nicht der Funktionsaufruf, zeigen Sie uns, wie / wo Sie "MyFunction" deklariert haben. Was passiert, wenn Sie tun: Get-Item-Funktion: \ MyFunction
zdan
1
Vorgeschlagene alternative Möglichkeit, dies zu beheben
Feru
Ich denke, dies ist eine der wichtigsten PowerShell-Fragen zum Stapelüberlauf in Bezug auf Funktionen / Methoden. Wenn Sie PowerShell-Skripte lernen möchten, sollten Sie die gesamte Seite lesen und gründlich verstehen. Du wirst dich später hassen, wenn du es nicht tust.
Birdman

Antworten:

271

PowerShell hat eine wirklich verrückte Rückkehrsemantik - zumindest aus einer traditionelleren Programmierperspektive. Es gibt zwei Hauptideen, um den Kopf herumzureißen:

  • Alle Ausgaben werden erfasst und zurückgegeben
  • Das Schlüsselwort return gibt wirklich nur einen logischen Exit-Punkt an

Somit bewirken die folgenden zwei Skriptblöcke genau dasselbe:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

Die Variable $ a im zweiten Beispiel bleibt als Ausgabe in der Pipeline und wie erwähnt wird die gesamte Ausgabe zurückgegeben. Tatsächlich könnten Sie im zweiten Beispiel die Rückgabe vollständig weglassen und das gleiche Verhalten erhalten (die Rückgabe würde impliziert, wenn die Funktion auf natürliche Weise abgeschlossen und beendet wird).

Ohne mehr von Ihrer Funktionsdefinition kann ich nicht sagen, warum Sie ein PSMethod-Objekt erhalten. Ich vermute, dass Sie wahrscheinlich ein paar Zeilen haben, die nicht erfasst und in die Ausgabepipeline eingefügt werden.

Es ist auch erwähnenswert, dass Sie wahrscheinlich diese Semikolons nicht benötigen - es sei denn, Sie verschachteln mehrere Ausdrücke in einer einzelnen Zeile.

Weitere Informationen zur Rückgabesemantik finden Sie auf der Seite about_Return in TechNet oder durch Aufrufen des help returnBefehls in PowerShell.

Goyuix
quelle
13
Gibt es also eine Möglichkeit, einen Skalierungswert von einer Funktion zurückzugeben?
ChiliYago
10
Wenn Ihre Funktion eine Hashtabelle zurückgibt, wird das Ergebnis als solches behandelt. Wenn Sie jedoch irgendetwas im Funktionskörper "wiedergeben", einschließlich der Ausgabe anderer Befehle, wird das Ergebnis plötzlich gemischt.
Luke Puplett
5
Wenn Sie write-debug$DebugPreference = "Continue""SilentlyContinue"
Inhalte
6
Dies hat geholfen, aber dieser stackoverflow.com/questions/22663848/… hat noch mehr geholfen. Übertragen Sie unerwünschte Ergebnisse von Befehlen / Funktionsaufrufen in der aufgerufenen Funktion über "... | Out-Null" und geben Sie dann einfach das gewünschte Objekt zurück.
Straff
1
@ LukePuplett echowar das Problem für mich! Dieser kleine Nebenpunkt ist sehr, sehr wichtig. Ich gab eine Hashtabelle zurück und folgte allem anderen, aber der Rückgabewert war immer noch nicht das, was ich erwartet hatte. Entfernte das echound arbeitete wie ein Zauber.
Ayo I
53

Dieser Teil von PowerShell ist wahrscheinlich der dümmste Aspekt. Jede fremde Ausgabe, die während einer Funktion erzeugt wird, verschmutzt das Ergebnis. Manchmal gibt es keine Ausgabe, und unter bestimmten Bedingungen gibt es zusätzlich zu Ihrem geplanten Rückgabewert eine andere ungeplante Ausgabe.

Daher entferne ich die Zuweisung aus dem ursprünglichen Funktionsaufruf, sodass die Ausgabe auf dem Bildschirm angezeigt wird, und gehe dann durch, bis etwas, das ich nicht geplant hatte, im Debugger-Fenster angezeigt wird (mithilfe der PowerShell ISE ).

Sogar Dinge wie das Reservieren von Variablen in äußeren Bereichen verursachen eine Ausgabe, [boolean]$isEnableddie nervig ein False ausspuckt, wenn Sie es nicht machen [boolean]$isEnabled = $false.

Eine andere gute ist, $someCollection.Add("thing")die die neue Sammlungsanzahl ausspuckt.

Luke Puplett
quelle
2
Warum sollten Sie nur einen Namen reservieren, anstatt die bewährte Methode zur ordnungsgemäßen Initialisierung der Variablen mit einem explizit definierten Wert anzuwenden?
BeowulfNode42
2
Ich nehme an, Sie meinen, dass Sie zu Beginn jedes Bereichs einen Block von Variablendeklarationen für jede in diesem Bereich verwendete Variable haben. Sie sollten noch oder möglicherweise bereits alle Variablen initialisieren, bevor Sie sie in einer beliebigen Sprache einschließlich C # verwenden. Wichtig ist auch, dass [boolean]$ain Powershell die Variable $ a nicht deklariert wird. Sie können dies bestätigen, indem Sie dieser Zeile mit der Zeile folgen $a.gettype()und diese Ausgabe mit der Deklaration und Initialisierung mit der Zeichenfolge vergleichen $a = [boolean]$false.
BeowulfNode42
3
Sie haben wahrscheinlich Recht, ich würde nur ein expliziteres Design bevorzugen, um versehentliches Schreiben zu verhindern.
Luke Puplett
4
Aus der Sicht eines Programmierers bin ich mir zwar nicht sicher, ob dies der schlimmste der vielen nervigen Aspekte der PS-Syntax ist, obwohl ich ein PS-n00b bin. Wie um alles in der Welt hielt es jemand für vorzuziehen, "x -gt y" anstelle von "x> y" zu schreiben?!? Sicherlich hätten zumindest die eingebauten Operatoren eine benutzerfreundlichere Syntax verwenden können?
Der
5
@TheDag, weil es zuerst eine Shell ist, dann die Programmiersprache; >und <werden bereits für die Umleitung von E / A-Streams zu / von Dateien verwendet. >=schreibt stdout in eine Datei namens =.
TessellatingHeckler
38

Mit PowerShell 5 können wir jetzt Klassen erstellen. Ändern Sie Ihre Funktion in eine Klasse, und return gibt nur das Objekt unmittelbar davor zurück. Hier ist ein wirklich einfaches Beispiel.

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

Wenn dies eine Funktion wäre, wäre die erwartete Ausgabe

Hello World
808979

Als Klasse wird jedoch nur die Ganzzahl 808979 zurückgegeben. Eine Klasse ist eine Art Garantie dafür, dass nur der deklarierte oder ungültige Typ zurückgegeben wird.

DaSmokeDog
quelle
6
Hinweis. Es werden auch staticMethoden unterstützt . [test_class]::return_what()
Führt
1
"Wenn dies eine Funktion wäre, wäre die erwartete Ausgabe 'Hello World \ n808979" - Ich denke nicht, dass das richtig ist? Der Ausgabewert ist immer nur der Wert der letzten Anweisung, in Ihrem Fall return 808979Funktion oder nicht.
Amn
1
@amn Danke! Sie sind sehr korrekt. Das Beispiel würde dies nur zurückgeben, wenn es eine Ausgabe innerhalb der Funktion erstellt. Welches ist, was mich nervt. Manchmal kann Ihre Funktion eine Ausgabe generieren und diese Ausgabe sowie alle Werte, die Sie in der return-Anweisung hinzufügen, werden zurückgegeben. Wie Sie wissen, geben Klassen nur den deklarierten oder ungültigen Typ zurück. Wenn Sie lieber Funktionen verwenden, besteht eine einfache Methode darin, die Funktion einer Variablen zuzuweisen. Wenn mehr als der Rückgabewert zurückgegeben wird, ist die Variable ein Array und der Rückgabewert ist das letzte Element im Array.
DaSmokeDog
1
Was erwarten Sie von einem Befehl namens Write-Output? Es ist nicht die "Konsolen" -Ausgabe, auf die hier Bezug genommen wird, sondern die Ausgabe für die Funktion / das Cmdlet. Wenn Sie Material auf der Konsole auszugeben wollen , gibt es Write-Verbose, Write-Hostund Write-ErrorFunktionen, unter anderem. Grundsätzlich Write-Outputist näher an der returnSemantik als an einer Konsolenausgabeprozedur. Missbrauche es nicht, benutze es Write-Verbosefür Ausführlichkeit, dafür ist es da.
Amn
1
@amn Ich bin mir sehr bewusst, dass dies nicht die Konsole ist. Es war ein einfacher Weg, um zu zeigen, wie eine Rückgabe mit unerwarteten Ausgaben innerhalb einer Funktion gegenüber einer Klasse umgeht. In einer Funktion werden alle Ausgaben + der von Ihnen zurückgegebene Wert zurückgegeben. Es wird jedoch nur der Wert übergeben, der in der Rückgabe einer Klasse angegeben ist. Versuchen Sie, sie selbst zu betreiben.
DaSmokeDog
30

Um dieses Problem zu umgehen, habe ich das letzte Objekt im Array zurückgegeben, das Sie von der Funktion erhalten haben. Es ist keine großartige Lösung, aber es ist besser als nichts:

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

$b = someFunction
$b = $b[($b.count - 1)]  # Or
$b = $b[-1]              # Simpler

Alles in allem könnte eine einzeiligere Art, dasselbe zu schreiben, sein:

$b = (someFunction $someParameter $andAnotherOne)[-1]
Jonesy
quelle
7
$b = $b[-1]Dies wäre eine einfachere Möglichkeit, das letzte Element oder das Array abzurufen. Noch einfacher wäre es jedoch, keine Werte auszugeben, die Sie nicht möchten.
Duncan
@Duncan Und was ist, wenn ich das Zeug in der Funktion ausgeben möchte, während ich gleichzeitig auch einen Rückgabewert möchte?
Utkan Gezer
@ThoAppelsin Wenn Sie meinen, Sie möchten etwas in das Terminal schreiben, verwenden Sie es, um write-hostes direkt auszugeben.
Duncan
7

Ich übergebe ein einfaches Hashtable-Objekt mit einem einzelnen Ergebniselement, um die Rückkehrverrücktheit zu vermeiden, da ich auch auf der Konsole ausgeben möchte. Es wirkt durch Referenzübergabe.

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

Sie können eine echte Beispielverwendung in meinem GitHub-Projekt unpackTunes sehen .

Was wäre cool
quelle
4

Es ist schwer zu sagen, ohne sich den Code anzusehen. Stellen Sie sicher, dass Ihre Funktion nicht mehr als ein Objekt zurückgibt und dass Sie alle Ergebnisse anderer Aufrufe erfassen. Wofür bekommst du:

@($returnURL).count

Wie auch immer, zwei Vorschläge:

Wandeln Sie das Objekt in einen String um:

...
return [string]$rs

Oder schließen Sie es einfach in doppelte Anführungszeichen ein, wie oben, aber kürzer zu tippen:

...
return "$rs"
Shay Levy
quelle
1
Oder auch nur "$rs"- das returnist nur erforderlich, wenn Sie frühzeitig von der Funktion zurückkehren. Das Auslassen der Rückgabe ist eine bessere PowerShell-Sprache.
David Clarke
4

Lukes Beschreibung der Funktionsergebnisse in diesen Szenarien scheint richtig zu sein. Ich möchte nur die Grundursache verstehen und das PowerShell-Produktteam würde etwas gegen das Verhalten unternehmen. Es scheint ziemlich häufig zu sein und hat mich zu viel Debugging-Zeit gekostet.

Um dieses Problem zu umgehen, habe ich globale Variablen verwendet, anstatt den Wert aus dem Funktionsaufruf zurückzugeben und zu verwenden.

Hier ist eine weitere Frage zur Verwendung globaler Variablen: Festlegen einer globalen PowerShell-Variablen aus einer Funktion, bei der der globale Variablenname eine an die Funktion übergebene Variable ist

Ted B.
quelle
3

Das Folgende gibt einfach 4 als Antwort zurück. Wenn Sie die Add-Ausdrücke für Zeichenfolgen ersetzen, wird die erste Zeichenfolge zurückgegeben.

Function StartingMain {
  $a = 1 + 3
  $b = 2 + 5
  $c = 3 + 7
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b
}

StartingEnd(StartingMain)

Dies kann auch für ein Array erfolgen. Das folgende Beispiel gibt "Text 2" zurück.

Function StartingMain {
  $a = ,@("Text 1","Text 2","Text 3")
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b[1]
}

StartingEnd(StartingMain)

Beachten Sie, dass Sie die Funktion unterhalb der Funktion selbst aufrufen müssen. Andernfalls wird beim ersten Ausführen ein Fehler zurückgegeben, der nicht weiß, was "StartingMain" ist.

Edwin
quelle
2

Die vorhandenen Antworten sind korrekt, aber manchmal geben Sie etwas nicht explizit mit a Write-Outputoder a zurück return, aber die Funktionsergebnisse enthalten einen mysteriösen Wert. Dies könnte die Ausgabe einer eingebauten Funktion wie seinNew-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

Das zusätzliche Rauschen bei der Verzeichniserstellung wird gesammelt und in der Ausgabe ausgegeben. Die einfache Möglichkeit, dies zu mildern, besteht darin | Out-Null, das Ende der New-ItemAnweisung hinzuzufügen , oder Sie können das Ergebnis einer Variablen zuweisen und diese Variable einfach nicht verwenden. Es würde so aussehen ...

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-Itemist wahrscheinlich die bekannteste von diesen, aber andere umfassen alle StringBuilder.Append*()Methoden sowie die SqlDataAdapter.Fill()Methode.

StingyJack
quelle
0

Als Alternative returnwürde ich vorschlagen Write-Output.

Write-Output Sie können Eingaben aus der Pipeline übernehmen und entweder über die Pipeline weiterleiten oder an die Konsole drucken, wenn dies der letzte Befehl ist.

Beendet Write-Outputdas Skript jedoch nicht wie returngewohnt, was von Vorteil sein kann, wenn die Ausgabe in einer Schleife verwendet, weitergeleitet usw. werden soll.

Alex_P
quelle