Wie definiere ich in PowerShell eine Funktion in einer Datei und rufe sie über die PowerShell-Befehlszeile auf?

242

Ich habe eine .ps1-Datei, in der ich benutzerdefinierte Funktionen definieren möchte.

Stellen Sie sich vor, die Datei heißt MyFunctions.ps1 und der Inhalt lautet wie folgt:

Write-Host "Installing functions"
function A1
{
    Write-Host "A1 is running!"
}
Write-Host "Done"

Um dieses Skript auszuführen und die A1-Funktion theoretisch zu registrieren, navigiere ich zu dem Ordner, in dem sich die .ps1-Datei befindet, und führe die Datei aus:

.\MyFunctions.ps1

Dies gibt aus:

Installing functions
Done

Wenn ich jedoch versuche, A1 aufzurufen, wird einfach die Fehlermeldung angezeigt, dass es keinen Befehl / keine Funktion mit diesem Namen gibt:

The term 'A1' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling
 of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:3
+ A1 <<<<
    + CategoryInfo          : ObjectNotFound: (A1:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Ich muss einige PowerShell-Konzepte falsch verstehen. Kann ich keine Funktionen in Skriptdateien definieren?

Beachten Sie, dass ich meine Ausführungsrichtlinie bereits auf 'RemoteSigned' festgelegt habe. Und ich weiß, dass .ps1-Dateien mit einem Punkt vor dem Dateinamen ausgeführt werden :. \ MyFile.ps1

Willem
quelle
Netter Link zu Ladefunktionen
Andrew

Antworten:

262

Versuchen Sie dies in der PowerShell-Befehlszeile:

. .\MyFunctions.ps1
A1

Der Punktoperator wird für das Skript-Include verwendet.

rsc
quelle
11
Nun, es bedeutet "Führen Sie dies im aktuellen Kontext anstelle eines untergeordneten Kontexts aus."
JasonMArcher
15
Es bedeutet , beziehen Sie den Inhalt dieser Datei. Gleich wie in Bash . ss64.com/bash/period.html
inquam
2
Es scheint jedoch nicht sehr gut zu funktionieren (zumindest von ISE), es sei denn, Sie führen zuerst. \ MyFunctions.ps1 aus, um es verfügbar zu machen. Ich bin mir nicht sicher, ob ich streng von Powershell.exe laufen soll.
Mike Cheel
1
Ich fand es nicht intuitiv, dass beim Dot-Sourcing der Pfad relativ zum pwd und nicht zum Skript verwendet wurde. Daher möchte ich die Leute dringend bitten, stattdessen auf die Antwort von JoeG zu schauen und Module zu verwenden.
Spork
5
@Schwein . "$PSScriptRoot\MyFunctions.ps1". Verfügbar ab Version 3, vorher siehe stackoverflow.com/questions/3667238/… . Es ist sehr gängig.
Yzorg
231

Sie sprechen von Dot Sourcing . Und es ist böse. Aber keine Sorge, es gibt eine bessere und einfachere Möglichkeit, mit Modulen das zu tun, was Sie wollen (es klingt viel beängstigender als es ist). Der Hauptvorteil der Verwendung von Modulen besteht darin, dass Sie sie bei Bedarf aus der Shell entladen können und die Variablen in den Funktionen nicht in die Shell eindringen können (versuchen Sie, eine der Variablen von a aufzurufen, sobald Sie eine Funktionsdatei als Punktquelle erstellt haben) Funktion in der Shell, und Sie werden sehen, was ich meine).

Benennen Sie also zuerst die .ps1-Datei mit all Ihren Funktionen in MyFunctions.psm1 um (Sie haben gerade ein Modul erstellt!). Damit ein Modul ordnungsgemäß geladen werden kann, müssen Sie einige bestimmte Dinge mit der Datei tun. Damit Import-Module das Modul sehen kann (Sie verwenden dieses Cmdlet, um das Modul in die Shell zu laden), muss es sich an einer bestimmten Stelle befinden. Der Standardpfad zum Modulordner lautet $ home \ Documents \ WindowsPowerShell \ Modules.

Erstellen Sie in diesem Ordner einen Ordner mit dem Namen MyFunctions und legen Sie die Datei MyFunctions.psm1 darin ab (die Moduldatei muss sich in einem Ordner mit genau demselben Namen wie die PSM1-Datei befinden).

Öffnen Sie anschließend PowerShell und führen Sie den folgenden Befehl aus:

Get-Module -listavailable

Wenn Sie eine mit dem Namen MyFunctions sehen, haben Sie es richtig gemacht und Ihr Modul kann geladen werden (dies dient nur dazu, sicherzustellen, dass dies richtig eingerichtet ist. Sie müssen dies nur einmal tun).

Um das Modul zu verwenden, geben Sie Folgendes in die Shell ein (oder fügen Sie diese Zeile in Ihr $ -Profil ein oder fügen Sie dies als erste Zeile in ein Skript ein):

Import-Module MyFunctions

Sie können jetzt Ihre Funktionen ausführen. Das Coole daran ist, dass Sie, sobald Sie 10-15 Funktionen haben, den Namen eines Paares vergessen werden. Wenn Sie sie in einem Modul haben, können Sie den folgenden Befehl ausführen, um eine Liste aller Funktionen in Ihrem Modul abzurufen:

Get-Command -module MyFunctions

Es ist ziemlich süß und der kleine Aufwand, der erforderlich ist, um sich auf der Vorderseite einzurichten, ist es auf jeden Fall wert.

JoeG
quelle
6
Was ist, wenn Ihre Funktionen nur für die jeweilige PowerShell-Anwendung relevant sind? Ich meine, wenn Sie ein Paket von PS1 installieren, um irgendwo einen Job zu erledigen, möchten Sie möglicherweise nicht jede Funktion in Ihrem Profil, oder?
Ian Patrick Hughes
3
In diesem Fall würde ich ein Modul für diese bestimmte Anwendung erstellen und es entweder laden, bevor die Skripte ausgeführt werden (wenn interaktiv gearbeitet wird), oder es in das Skript laden. Wenn Sie jedoch Code haben, der nur für eine bestimmte Aufgabe spezifisch ist, möchten Sie diese Funktionen im Skript. Persönlich schreibe ich nur Funktionen, die generisch eine Sache tun. Wenn ein Code hyper-spezialisiert ist, ist es nicht wirklich sinnvoll, ihn in eine Funktion oder ein Modul einzuschließen (es sei denn, es gibt mehrere Skripte, die denselben Code verwenden, ist dies möglicherweise sinnvoll).
JoeG
16
Es ist NICHT erforderlich, dass sich die Moduldatei in einem Ordner befindet, der genau den gleichen Namen wie die PSM1-Datei hat. Es kann gemacht werden wie Import-Module .\buildsystem\PSUtils.psm1
Michael Freidgeim
2
@MichaelFreidgeim, wenn es so einfach ist, nur das .mit zu ändern Import-Moduleund die Erweiterung umzubenennen , und nicht erfordert, dass die Module in einem bestimmten Ordner abgelegt werden, dh ich kann es in jedem gewünschten Verzeichnis haben, genau wie beim Dot Sourcing Gibt es einen Grund, Dot Sourcing über Module durchzuführen, wenn man die Vorteile des Scoping berücksichtigt? (es sei denn natürlich, dieser Bereich "Probleme" ist, was Sie wollen)
Abdul
2
@Abdul, Dot Sourcing ist einfacher, Module sind viel leistungsfähiger. Siehe stackoverflow.com/questions/14882332/…
Michael Freidgeim
17

. "$PSScriptRoot\MyFunctions.ps1" MyA1Func

Verfügbar ab Version 3, vorher siehe Wie kann ich den Dateisystemspeicherort eines PowerShell-Skripts abrufen ? . Es ist sehr gängig.

PS Ich abonniere nicht die Regel "Alles ist ein Modul". Meine Skripte werden von anderen Entwicklern außerhalb von GIT verwendet, daher möchte ich keine Inhalte an einem bestimmten Ort ablegen oder Systemumgebungsvariablen ändern, bevor mein Skript ausgeführt wird. Es ist nur ein Skript (oder zwei oder drei).

Yzorg
quelle
FWIW, Sie müssen keines dieser Dinge tun, um ein Skript in einem Modul auszuführen.
Nick Cox
@ NickCox Ich würde gerne einige Beispiele dafür sehen. Hast du welche? +10, wenn das Beispiel aus einem OSS-Projekt stammt. Insbesondere ein Beispiel für das Laden eines PS-Moduls über einen relativen Pfad (nicht PSModulePath oder ohne Anpassen von PSModulePath) und ein nicht triviales Beispiel (dh, wenn das Modul Vorteile gegenüber dem normalen Skriptumfang hat).
Yzorg
Ich importiere das FluentMigrator.PowerShell- Modul häufig von einem relativen Pfad. Auf diese Weise können wir die Quellcodeverwaltung überprüfen und sicherstellen, dass alle dieselbe Version verwenden. Es funktioniert gut.
Nick Cox
Ich bin mir nicht sicher, welche Vor- und Nachteile es hat, es als Modul oder als Skript zu verpacken. Vielleicht sollte man das mit dem Autor besprechen? Ich denke die Fähigkeit zu Get-Command -Module FluentMigrator.PowerShellist ganz nett?
Nick Cox
@NickCox Sie haben den Modulpfad in diesem Befehl nicht vollständig qualifiziert. Dies bedeutet, dass er nur gefunden wird, wenn Sie das Modul entweder in einen globalen Modulordner kopieren oder Ihren GIT-Ordner einer globalen Umgebungsvariablen hinzufügen. Ich denke, Sie haben gerade meinen Standpunkt demonstriert.
Yzorg
7

Sie können sicherlich Funktionen in Skriptdateien definieren (ich neige dann dazu, sie beim Laden über mein Powershell-Profil zu laden).

Zuerst müssen Sie überprüfen, ob die Funktion geladen ist, indem Sie Folgendes ausführen:

ls function:\ | where { $_.Name -eq "A1"  }

Und überprüfen Sie, ob es in der Liste erscheint (sollte eine Liste von 1 sein!), Dann teilen Sie uns mit, welche Ausgabe Sie erhalten!

Jonny
quelle
1
In PowerShell wird die Funktion als Verzeichnis behandelt, sodass sie mit c: \ oder d: \ identisch ist. Ebenso wird es ohne den Backslash funktionieren, also ls Funktion: | wo {$ _. Name -eq "A1"}
Jonny
4

Sie können folgende Funktionen hinzufügen:

c:\Users\David\Documents\WindowsPowerShell\profile.ps1

Und die Funktion wird verfügbar sein.

David Morrow
quelle
3

Wenn Ihre Datei nur eine Hauptfunktion hat, die Sie aufrufen / verfügbar machen möchten, können Sie die Datei auch einfach starten mit:

Param($Param1)

Sie können es dann zB wie folgt nennen:

.\MyFunctions.ps1 -Param1 'value1'

Dies macht es viel bequemer, wenn Sie einfach nur diese Funktion aufrufen möchten, ohne die Funktion importieren zu müssen.

bergmeister
quelle
Ich sollte auch beachten, dass ich heute festgestellt habe (nachdem mir ein Kollege davon erzählt hatte), dass PowerShell das [CmdletBinding()]Attribut automatisch hinzufügt und es kostenlos zu einer erweiterten Funktion aktualisiert. :-)
Bergmeister
1

Angenommen, Sie haben eine Moduldatei namens Dummy-Name.psm1 mit einer Methode namens Function-Dumb ()

Import-Module "Dummy-Name.psm1";
Get-Command -Module "Function-Dumb";
#
#
Function-Dumb;
Bytekoder
quelle