En PowerShell, ¿cómo defino una función en un archivo y la llamo desde la línea de comandos de PowerShell?

243

Tengo un archivo .ps1 en el que quiero definir funciones personalizadas.

Imagine que el archivo se llama MyFunctions.ps1, y el contenido es el siguiente:

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

Para ejecutar este script y registrar teóricamente la función A1, navego a la carpeta en la que reside el archivo .ps1 y ejecuto el archivo:

.\MyFunctions.ps1

Esto produce:

Installing functions
Done

Sin embargo, cuando intento llamar a A1, simplemente recibo el error que indica que no hay comando / función con ese nombre:

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

Debo entender mal algunos conceptos de PowerShell. ¿No puedo definir funciones en archivos de script?

Tenga en cuenta que ya he establecido mi política de ejecución en 'RemoteSigned'. Y sé ejecutar archivos .ps1 usando un punto delante del nombre del archivo:. \ MyFile.ps1

willem
fuente
Bonito enlace sobre las funciones de carga en el inicio de PS: sandfeld.net/powershell-load-your-functions-at-startup
Andrew

Respuestas:

262

Pruebe esto en la línea de comandos de PowerShell:

. .\MyFunctions.ps1
A1

El operador de punto se utiliza para incluir script.

rsc
fuente
11
Bueno, significa "ejecutar esto en el contexto actual en lugar de un contexto secundario".
JasonMArcher
15
Significa obtener el contenido de este archivo. Igual que en bash . ss64.com/bash/period.html
consulta el
2
Sin embargo, no parece funcionar muy bien (al menos desde ISE) a menos que ejecute. \ MyFunctions.ps1 primero para que esté disponible. No estoy seguro de ejecutar estrictamente desde powershell.exe.
Mike Cheel
1
Pensé que era contrario a la intuición que el abastecimiento de puntos usaba la ruta relativa al pwd en lugar del guión, por lo que instaría a las personas a mirar la respuesta de JoeG y usar módulos.
Spork
55
@Spork . "$PSScriptRoot\MyFunctions.ps1". Disponible a partir de la v3, antes de eso consulte stackoverflow.com/questions/3667238/… . Es muy común.
yzorg
233

Lo que estás hablando se llama abastecimiento de puntos . Y es malvado. Pero no se preocupe, hay una manera mejor y más fácil de hacer lo que desea con los módulos (suena mucho más aterrador de lo que es). El beneficio principal de usar módulos es que puede descargarlos del shell si lo necesita, y evita que las variables en las funciones se arrastren en el shell (una vez que obtiene un archivo de función, intente llamar a una de las variables funciona en el shell, y verás lo que quiero decir).

Primero, cambie el nombre del archivo .ps1 que tiene todas sus funciones en MyFunctions.psm1 (¡acaba de crear un módulo!). Ahora, para que un módulo se cargue correctamente, debe hacer algunas cosas específicas con el archivo. Primero, Import-Module para ver el módulo (usa este cmdlet para cargar el módulo en el shell), tiene que estar en una ubicación específica. La ruta predeterminada a la carpeta de módulos es $ home \ Documents \ WindowsPowerShell \ Modules.

En esa carpeta, cree una carpeta llamada MyFunctions y coloque el archivo MyFunctions.psm1 en ella (el archivo del módulo debe residir en una carpeta con exactamente el mismo nombre que el archivo PSM1).

Una vez hecho esto, abra PowerShell y ejecute este comando:

Get-Module -listavailable

Si ve uno llamado MyFunctions, lo hizo bien y su módulo está listo para cargarse (esto es solo para asegurarse de que está configurado correctamente, solo tiene que hacerlo una vez).

Para usar el módulo, escriba lo siguiente en el shell (o ponga esta línea en su $ profile, o ponga esto como la primera línea en un script)

Import-Module MyFunctions

Ahora puede ejecutar sus funciones. Lo bueno de esto es que una vez que tenga 10-15 funciones allí, olvidará el nombre de una pareja. Si los tiene en un módulo, puede ejecutar el siguiente comando para obtener una lista de todas las funciones de su módulo:

Get-Command -module MyFunctions

Es bastante dulce, y el pequeño esfuerzo que se requiere para configurar en la parte delantera vale la pena.

JoeG
fuente
66
¿Qué pasa si sus funciones son pertinentes solo para esa aplicación de PowerShell dada? Quiero decir, si instala un paquete de PS1 para hacer un trabajo en algún lugar, es posible que no desee todas las funciones en su perfil, ¿verdad?
Ian Patrick Hughes, el
3
En ese caso, crearía un módulo para esa aplicación específica y lo cargaría antes de ejecutar los scripts (si funciona de forma interactiva), o lo cargaría dentro del script. Pero, en general, si tiene un código que es específico solo para una tarea determinada, desearía esas funciones en el script. Personalmente solo escribo funciones que genéricamente hacen una cosa. Si un fragmento de código es hiperespecializado, realmente no tiene sentido incluirlo en una función o módulo (a menos que haya varios scripts que usen ese mismo código, entonces podría tener sentido).
JoeG
17
NO es necesario que el archivo del módulo esté en una carpeta con exactamente el mismo nombre que el archivo PSM1. Se puede hacer como Import-Module .\buildsystem\PSUtils.psm1
Michael Freidgeim
2
@MichaelFreidgeim si es tan simple como cambiar la .con Import-Moduley cambiar el nombre de la extensión, y no requiere que los módulos sean colocados en una carpeta específica, es decir, que se puede tener en cualquier directorio que quiero, al igual que con el abastecimiento de puntos, se ¿Hay alguna razón para hacer incluso el abastecimiento de puntos sobre los módulos, teniendo en cuenta los beneficios que vienen para el alcance? (a menos, por supuesto, que esos "problemas" de alcance sean lo que quieres)
Abdul
2
@Abdul, el abastecimiento de puntos es más simple, los módulos son mucho más potentes. Ver stackoverflow.com/questions/14882332/…
Michael Freidgeim el
17

. "$PSScriptRoot\MyFunctions.ps1" MyA1Func

Disponible a partir de la v3, antes de eso vea ¿Cómo puedo obtener la ubicación del sistema de archivos de un script de PowerShell? . Es muy común.

PD: No me suscribo a la regla 'todo es un módulo'. Mis scripts son utilizados por otros desarrolladores fuera de GIT, por lo que no me gusta poner cosas en un lugar específico o modificar las variables de entorno del sistema antes de que mi script se ejecute. Es solo un guión (o dos, o tres).

yzorg
fuente
FWIW, no tiene que hacer ninguna de esas cosas para ejecutar el script en un módulo.
Nick Cox
@ NickCox Me encantaría ver algunos ejemplos de eso. ¿Usted tiene alguna? +10 si el ejemplo es de un proyecto OSS. Específicamente, un ejemplo de módulo PS que se carga a través de una ruta relativa (no PSModulePath o sin personalizar PSModulePath), y un ejemplo no trivial (es decir, donde el módulo tiene beneficios sobre el alcance de secuencia de comandos normal).
yzorg
Con frecuencia importo el módulo FluentMigrator.PowerShell desde una ruta relativa. Eso nos permite verificarlo en el control de origen y asegurarnos de que todos usen la misma versión. Funciona bien.
Nick Cox
No estoy seguro de los pros y los contras relativos de empaquetarlo como un módulo frente a un guión: ¿tal vez ese sea uno de los que debatir con el autor? ¿Supongo que la capacidad de hacerlo Get-Command -Module FluentMigrator.PowerShelles bastante buena?
Nick Cox
@NickCox No calificó completamente la ruta del módulo en ese comando, lo que significa que no se encontrará a menos que copie el módulo en una carpeta de módulo global o agregue su carpeta GIT a una variable de entorno global. Creo que acabas de demostrar mi punto.
yzorg
7

Ciertamente puede definir funciones en archivos de script (luego tiendo a cargarlas a través de mi perfil de Powershell al cargar).

Primero debe verificar para asegurarse de que la función se carga ejecutando:

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

Y compruebe que aparece en la lista (¡debería ser una lista de 1!), ¡Luego díganos qué resultado obtiene!

Jonny
fuente
1
En PowerShell, la función se trata como un directorio, por lo que es lo mismo que decir c: \ or d: \. Igualmente, funcionará sin la barra invertida, por lo que ls funciona: | donde {$ _. Nombre -eq "A1"}
Jonny
4

Puede agregar funciones a:

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

Y la función estará disponible.

David Morrow
fuente
3

Si su archivo tiene solo una función principal a la que desea llamar / exponer, entonces también puede comenzar el archivo con:

Param($Param1)

Luego puede llamarlo, por ejemplo, de la siguiente manera:

.\MyFunctions.ps1 -Param1 'value1'

Esto lo hace mucho más conveniente si desea llamar fácilmente a esa función sin tener que importar la función.

Bergmeister
fuente
También debo señalar que descubrí hoy (después de que un colega mío me dijo) que PowerShell agrega automáticamente el [CmdletBinding()]atributo y lo actualiza de forma gratuita a una función avanzada. :-)
bergmeister
1

Suponiendo que tiene un archivo de módulo llamado Dummy-Name.psm1 que tiene un método llamado Function-Dumb ()

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