Seltsames Verhalten mit Powershell-Skriptblocks Geltungsbereich von Variablen und Modulen, irgendwelche Vorschläge?
HINWEIS: ich bin mit PowerShell 2.0 unter Windows Vista.
Ich versuche, hinzufügen von Unterstützung für die Angabe build Argumente zu psake, aber ich ' ve stoßen einige seltsame PowerShell-variable scoping-Verhalten, das sich speziell mit den Aufruf von Funktionen, die exportiert wurden, mit Export-ModuleMember (das ist, wie psake macht es die main-Methode). Folgende ist ein einfaches PowerShell-Modul zur Veranschaulichung (benannt repoCase.psm1):
function Test {
param(
[Parameter(Position=0,Mandatory=0)]
[scriptblock]$properties = {}
)
$defaults = {$message = "Hello, world!"}
Write-Host "Before running defaults, message is: $message"
. $defaults
#At this point, $message is correctly set to "Hellow, world!"
Write-Host "Aftering running defaults, message is: $message"
. $properties
#At this point, I would expect $message to be set to whatever is passed in,
#which in this case is "Hello from poperties!", but it isn't.
Write-Host "Aftering running properties, message is: $message"
}
Export-ModuleMember -Function "Test"
Um das Modul zu testen, führen Sie die folgende Sequenz von Befehlen (werden Sie sicher, dass Sie in das gleiche Verzeichnis wie die repoCase.psm1):
Import-Module .\repoCase.psm1
#Note that $message should be null
Write-Host "Before execution - In global scope, message is: $message"
Test -properties { "Executing properties, message is $message"; $message = "Hello from properties!"; }
#Now $message is set to the value from the script block. The script block affected only the global scope.
Write-Host "After execution - In global scope, message is: $message"
Remove-Module repoCase
Dem Verhalten, dass ich erwartet wurde für den script-block habe ich weitergegeben zum Testen, um auf den lokalen Rahmen zu Testen. Es wird 'dotsourced' in, so dass alle änderungen, die er macht, sollte im Rahmen der Anrufer. Aber das ist nicht das, was passiert, scheint es zu sein, die den Umfang, wo es erklärt wurde. Hier ist die Ausgabe:
Before execution - In global scope, message is:
Before running defaults, message is:
Aftering running defaults, message is: Hello, world!
Executing properties, message is
Aftering running properties, message is: Hello, world!
After execution - In global scope, message is: Hello from properties!
Interessanterweise, wenn ich nicht die export-Test als Modul und stattdessen lediglich die Funktion deklarieren und aufrufen, es funktioniert alles so wie ich es erwarten würden. Der Skript-block wirkt sich nur auf Test-Bereich, und nicht ändern (global).
Ich bin kein PowerShell-guru, aber kann mir jemand erklären, dies Verhalten zu mir?
- Haben Sie es gemeldet als bug?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich habe dieses problem untersucht, welche sich in einem Projekt an dem ich arbeite, und entdeckte drei Dinge:
scriptBlock
physisch befindet sich irgendwo innerhalb eines .psm1-Datei, sehen wir das Verhalten.scriptBlock
befindet sich in einem separaten script-Datei (.ps1), wenn dasscriptBlock
übergeben wurde von ein Modul.scriptBlock
befindet sich überall in einem Skript-Datei (.ps1), solange diescriptBlock
nicht bestanden wurde aus einem Modul.scriptBlock
wird nicht unbedingt ausführen, die im globalen Gültigkeitsbereich. Vielmehr erscheint Sie immer ausführen, in welchem Umfang der Modul-Funktion aufgerufen wurde, aus.scriptBlock
: die ". " - operator, "&" - operator, und diescriptBlock
Objektinvoke()
Methode. In den letzten beiden Fällen, diescriptBlock
führt mit dem falschen Eltern Umfang. Dies kann untersucht werden, indem Sie versuchen, zu berufen, zum Beispiel{set-variable -name "message" -scope 1 -value "From scriptBlock"}
Ich hoffe das wirft etwas mehr Licht auf das problem, obwohl ich habe nicht ganz weit genug, um vorschlagen, dieses Problem zu umgehen.
Hat jemand noch PowerShell 1 installiert? Wenn ja, wäre es hilfreich, wenn Sie überprüfen können ob es zeigt das gleiche Verhalten.
Hier sind die Dateien für meine test-Fällen. Um diese auszuführen, erstellen Sie alle vier Dateien im selben Verzeichnis, und führen Sie dann "./all_tests.ps1" an der PowerShell ISE Befehlszeile
script_toplevel.ps1
script_infunction.ps1
Modul.psm1
all_tests.ps1
Ich glaube nicht, dass dies als ein Problem, durch das PowerShell-team, aber ich kann zumindest etwas Licht auf, wie es funktioniert.
Jedes script-block definiert ist in einem Skript oder Skript-Modul (im wörtlichen form, nicht dynamisch erstellt, mit so etwas wie
[scriptblock]::Create())
gebunden ist, um den Sitzungszustand des Moduls (oder der "main" session state, wenn nicht die Ausführung innerhalb eines Skripts-Moduls.) Es gibt auch spezifische Informationen zu der Datei, die das script-block kam, so Sachen wie breakpoints funktionieren wird, wenn der Skript-block aufgerufen wird.Wenn Sie passieren an so einem script-block als parameter über Skript-Modul Grenzen, es ist immer noch gebunden an seinen ursprünglichen Umfang, auch wenn Sie es aufrufen, aus dem inneren des Moduls.
In diesem speziellen Fall, die einfachste Lösung ist, erstellen Sie ein ungebundenes script-block durch den Aufruf
[scriptblock]::Create()
(wobei im text der Skript-block-Objekt als parameter übergebenen):Behalten Sie jedoch im Hinterkopf, dass es Potenzial für die scope-Probleme in die andere Richtung, jetzt. Wenn das Skript block stützt sich auf die in der Lage zu beheben, Variablen oder Funktionen, die verfügbar waren in dem ursprünglichen Umfang, jedoch nicht aus dem Modul, in dem Sie aufgerufen habe, wird es scheitern.
Da die Absicht der
$properties
block scheint zu sein, die set-Variablen und sonst nichts, würde ich wahrscheinlich pass in eineIDictionary
oderHashtable
- Objekt, anstatt ein Skript block. Auf diese Weise werden alle in die Ausführung erfolgt in der Anrufer-Rahmen, und Sie erhalten einen einfachen, inaktiven Objekt zu behandeln, in dem Modul, ohne Rahmen Albernheit zu kümmern:Caller-Datei:
[scriptblock]::Create($block.ToString())
machen konnte, die Dinge so funktionieren, wie ich wollte, aber hatte bis jetzt nicht die geringste Ahnung, warum! Dieser trick ist oft hilfreich für die PowerShell-DSLs.Es scheint, dass die $Nachricht, in der scriptblock übergeben wird, wird gebunden, die auf globaler Ebene z.B.:
Ausgänge:
Scheint dies ein Fehler zu sein. Ich werde prod der PowerShell MVP-Liste, um zu sehen, wenn ich kann dies bestätigen.