Dans PowerShell, comment définir une fonction dans un fichier et l'appeler à partir de la ligne de commande PowerShell?

243

J'ai un fichier .ps1 dans lequel je souhaite définir des fonctions personnalisées.

Imaginez que le fichier s'appelle MyFunctions.ps1 et que le contenu est le suivant:

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

Pour exécuter ce script et enregistrer théoriquement la fonction A1, je navigue jusqu'au dossier dans lequel réside le fichier .ps1 et exécute le fichier:

.\MyFunctions.ps1

Cela produit:

Installing functions
Done

Pourtant, lorsque j'essaie d'appeler A1, j'obtiens simplement l'erreur indiquant qu'il n'y a pas de commande / fonction de ce nom:

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

Je dois mal comprendre certains concepts PowerShell. Puis-je ne pas définir de fonctions dans les fichiers de script?

Notez que j'ai déjà défini ma stratégie d'exécution sur «RemoteSigned». Et je sais exécuter des fichiers .ps1 en utilisant un point devant le nom de fichier:. \ MyFile.ps1

willem
la source
Joli lien sur les fonctions de chargement au démarrage de PS: sandfeld.net/powershell-load-your-functions-at-startup
Andrew

Réponses:

262

Essayez ceci sur la ligne de commande PowerShell:

. .\MyFunctions.ps1
A1

L'opérateur point est utilisé pour l'inclusion de script.

rsc
la source
11
Eh bien, cela signifie «exécuter cela dans le contexte actuel au lieu d'un contexte enfant».
JasonMArcher
15
Cela signifie source le contenu de ce fichier. Identique à bash . ss64.com/bash/period.html
inquam
2
Cela ne semble pas très bien fonctionner (au moins depuis ISE), sauf si vous exécutez d'abord. \ MyFunctions.ps1 pour le rendre disponible. Je ne suis pas sûr d'exécuter strictement à partir de powershell.exe.
Mike Cheel
1
Je pensais qu'il était contre-intuitif que le dot-sourcing utilise le chemin relatif au pwd plutôt que le script, donc j'encourage les gens à se tourner vers la réponse de JoeG à la place et à utiliser des modules.
Spork
5
@Spork . "$PSScriptRoot\MyFunctions.ps1". Disponible à partir de la v3, avant cela, voir stackoverflow.com/questions/3667238/… . C'est TRÈS commun.
yzorg
233

Ce dont vous parlez est appelé dot sourcing . Et c'est mal. Mais ne vous inquiétez pas, il existe un moyen meilleur et plus facile de faire ce que vous voulez avec des modules (cela semble beaucoup plus effrayant qu'il ne l'est). Le principal avantage de l'utilisation des modules est que vous pouvez les décharger du shell si vous en avez besoin, et cela empêche les variables dans les fonctions de se glisser dans le shell (une fois que vous sourcez un fichier de fonction, essayez d'appeler l'une des variables à partir d'un fonctionner dans le shell, et vous verrez ce que je veux dire).

Donc, tout d'abord, renommez le fichier .ps1 qui contient toutes vos fonctions en MyFunctions.psm1 (vous venez de créer un module!). Maintenant, pour qu'un module se charge correctement, vous devez faire certaines choses spécifiques avec le fichier. Tout d'abord pour Import-Module pour voir le module (vous utilisez cette applet de commande pour charger le module dans le shell), il doit être dans un emplacement spécifique. Le chemin par défaut vers le dossier des modules est $ home \ Documents \ WindowsPowerShell \ Modules.

Dans ce dossier, créez un dossier nommé MyFunctions et placez-y le fichier MyFunctions.psm1 (le fichier de module doit résider dans un dossier portant exactement le même nom que le fichier PSM1).

Une fois cela fait, ouvrez PowerShell et exécutez cette commande:

Get-Module -listavailable

Si vous en voyez un appelé MyFunctions, vous l'avez bien fait, et votre module est prêt à être chargé (c'est juste pour vous assurer qu'il est bien configuré, vous ne devez le faire qu'une seule fois).

Pour utiliser le module, tapez ce qui suit dans le shell (ou placez cette ligne dans votre profil $, ou mettez-la comme première ligne dans un script):

Import-Module MyFunctions

Vous pouvez maintenant exécuter vos fonctions. La chose intéressante à ce sujet est qu'une fois que vous avez 10 à 15 fonctions, vous allez oublier le nom d'un couple. Si vous les avez dans un module, vous pouvez exécuter la commande suivante pour obtenir une liste de toutes les fonctions de votre module:

Get-Command -module MyFunctions

C'est assez doux, et le petit effort qu'il faut pour mettre en place sur la face avant en vaut vraiment la peine.

JoeG
la source
6
Qu'en est-il si vos fonctions ne sont pertinentes que pour cette application PowerShell donnée? Je veux dire, si vous installez un paquet de PS1 pour faire un travail quelque part, vous ne voudrez peut-être pas toutes les fonctions de votre profil, non?
Ian Patrick Hughes
3
Dans ce cas, je créerais un module pour cette application spécifique et le chargerais avant d'exécuter les scripts (si cela fonctionne de manière interactive) ou le chargerais dans le script. Mais d'une manière générale, si vous avez du code qui n'est spécifique qu'à une tâche donnée, vous voudriez ces fonctions dans le script. Personnellement, je n'écris que des fonctions qui génèrent une chose. Si un morceau de code est hyper spécialisé, cela n'a pas vraiment de sens de l'envelopper dans une fonction ou un module (à moins qu'il y ait plusieurs scripts qui utilisent ce même code, cela peut avoir du sens).
JoeG
17
Il n'est PAS nécessaire que le fichier de module se trouve dans un dossier portant exactement le même nom que le fichier PSM1. Cela peut être fait comme Import-Module .\buildsystem\PSUtils.psm1
Michael Freidgeim
2
@MichaelFreidgeim si c'est aussi simple que de simplement changer le .avec Import-Moduleet renommer l'extension, et ne nécessite pas que les modules soient placés dans un dossier spécifique, c'est-à-dire que je peux l'avoir dans n'importe quel répertoire que je veux, tout comme avec le sourcing de points, est Y a-t-il une raison de faire du sourcing par points sur les modules, compte tenu des avantages de la portée? (à moins bien sûr que ces "problèmes" de portée soient ce que vous voulez)
Abdul
2
@Abdul, le sourcing de points est plus simple, les modules sont beaucoup plus puissants. Voir stackoverflow.com/questions/14882332/…
Michael Freidgeim
17

. "$PSScriptRoot\MyFunctions.ps1" MyA1Func

Disponible à partir de la version 3, avant cela, voir Comment puis-je obtenir l'emplacement du système de fichiers d'un script PowerShell? . C'est TRÈS commun.

PS Je ne souscris pas à la règle "tout est un module". Mes scripts sont utilisés par d'autres développeurs hors de GIT, donc je n'aime pas mettre des choses dans un endroit spécifique ou modifier les variables d'environnement système avant que mon script ne s'exécute. C'est juste un script (ou deux ou trois).

yzorg
la source
FWIW, vous n'avez rien à faire pour exécuter le script dans un module.
Nick Cox
@NickCox J'adorerais voir quelques exemples de cela. Avez-vous des? +10 si l'exemple provient d'un projet OSS. Plus précisément, un exemple de module PS en cours de chargement via un chemin d'accès relatif (pas PSModulePath ou sans personnalisation de PSModulePath), et un exemple non trivial (c'est-à-dire où le module présente des avantages par rapport à la portée de script normale).
yzorg
J'importe fréquemment le module FluentMigrator.PowerShell à partir d'un chemin relatif. Cela nous permet de l'intégrer dans le contrôle de code source et de nous assurer que tout le monde utilise la même version. Ça marche bien.
Nick Cox
Je ne suis pas sûr des avantages et inconvénients relatifs de le conditionner en tant que module vs en tant que script: c'est peut-être un sujet à discuter avec l'auteur? Je suppose que la disponibilité Get-Command -Module FluentMigrator.PowerShellest assez agréable?
Nick Cox
@NickCox Vous n'avez pas entièrement qualifié le chemin du module dans cette commande, ce qui signifie qu'il ne serait pas trouvé à moins que vous ne copiez le module dans un dossier de module global ou que vous ajoutiez votre dossier GIT à une variable d'environnement global. Je pense que vous venez de démontrer mon point.
yzorg
7

Vous pouvez certainement définir des fonctions dans des fichiers de script (j'ai ensuite tendance à les charger via mon profil Powershell lors du chargement).

Vous devez d'abord vérifier que la fonction est chargée en exécutant:

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

Et vérifiez qu'il apparaît dans la liste (devrait être une liste de 1!), Puis faites-nous savoir quelle sortie vous obtenez!

Jonny
la source
1
Dans la fonction PowerShell est traitée comme un répertoire, c'est la même chose que dire c: \ ou d: \. De même, cela fonctionnera sans barre oblique inverse, donc la fonction ls: | où {$ _. Nom -eq "A1"}
Jonny
4

Vous pouvez ajouter une fonction à:

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

Une fonction sera disponible.

David Morrow
la source
3

Si votre fichier n'a qu'une seule fonction principale que vous souhaitez appeler / exposer, vous pouvez également simplement démarrer le fichier avec:

Param($Param1)

Vous pouvez ensuite l'appeler par exemple comme suit:

.\MyFunctions.ps1 -Param1 'value1'

Cela le rend beaucoup plus pratique si vous souhaitez appeler facilement cette fonction sans avoir à importer la fonction.

bergmeister
la source
Je dois également noter que j'ai découvert aujourd'hui (après qu'un de mes collègues me l'a dit) que PowerShell ajoute automatiquement l' [CmdletBinding()]attribut et le met à niveau gratuitement vers une fonction avancée. :-)
bergmeister
1

En supposant que vous avez un fichier de module appelé Dummy-Name.psm1 qui a une méthode appelée Function-Dumb ()

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