¿Cómo paso múltiples parámetros a una función en PowerShell?

438

Si tengo una función que acepta más de un parámetro de cadena, el primer parámetro parece obtener todos los datos asignados y los parámetros restantes se pasan como vacíos.

Una secuencia de comandos de prueba rápida:

Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC", "DEF")

La salida generada es

$arg1 value: ABC DEF
$arg2 value: 

La salida correcta debería ser:

$arg1 value: ABC
$arg2 value: DEF

Esto parece ser consistente entre v1 y v2 en varias máquinas, por lo que, obviamente, estoy haciendo algo mal. ¿Alguien puede señalar exactamente qué?

Nasir
fuente
3
Simplemente llama así:Test "ABC" "DEF"
Ranadip Dutta

Respuestas:

576

Los parámetros en las llamadas a funciones en PowerShell (todas las versiones) están separados por espacios, no separados por comas . Además, los paréntesis son completamente innecesarios y causarán un error de análisis en PowerShell 2.0 (o posterior) si Set-StrictModeestá activo. Los argumentos entre paréntesis se usan solo en métodos .NET.

function foo($a, $b, $c) {
   "a: $a; b: $b; c: $c"
}

ps> foo 1 2 3
a: 1; b: 2; c: 3
x0n
fuente
20
Lo más importante que finalmente ha ayudado a 'mantener' esto en mi mente es la última oración: "Los argumentos entre paréntesis se usan solo en métodos .NET".
Ashley
1
Prefiero usar la parántesis y la coma separada ... ¿es posible hacer esto en powershell?
Sam yi
8
@samyi No. Pasar un (1,2,3)a una función se trata efectivamente como una matriz; Un solo argumento. Si desea usar argumentos de estilo de método OO, use módulos:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
x0n
44
Powershelles un lenguaje shell y es común que los lenguajes shell utilicen espacios como separador de tokens. Yo no diría que Powershelles ser diferente aquí, está justo en línea con otros proyectiles predeterminados del sistema como cmd, sh, bash, etc.
Bender el más grande
270

Ya se ha proporcionado la respuesta correcta, pero este problema parece lo suficientemente frecuente como para garantizar algunos detalles adicionales para aquellos que desean comprender las sutilezas.

Hubiera agregado esto solo como un comentario, pero quería incluir una ilustración; lo eliminé de mi tabla de referencia rápida sobre las funciones de PowerShell. Esto supone que la firma de la función f es f($a, $b, $c):

Errores de sintaxis de una llamada a función

Por lo tanto, se puede llamar a una función con parámetros posicionales separados por espacios o parámetros con nombre independientes del orden . Las otras trampas revelan que debe ser consciente de las comas, paréntesis y espacios en blanco.

Para leer más, vea mi artículo Down the Rabbit Hole: A Study in PowerShell Pipelines, Functions, and Parameters . El artículo contiene un enlace a la referencia rápida / gráfico de pared también.

Michael Sorens
fuente
44
La explicación con la sintaxis más detallada llamando a cada parámetro y asignándole un valor realmente lo consolidó. ¡Gracias!
ConstantineK
77
Gracias, me estaba volviendo loco al no entender lo que estaba haciendo mal. Cuando finalmente lo hice bien, tenía hambre de una explicación de este comportamiento.
BSAFH
1
Gracias por publicar esto como respuesta. Saber por qué algo está mal es tan importante como qué está mal.
Gavin Ward el
44
Esta es una respuesta mucho mejor. Debería estar más arriba.
Mark Bertenshaw
53

Aquí hay algunas buenas respuestas, pero quería señalar un par de otras cosas. Los parámetros de función son en realidad un lugar donde brilla PowerShell. Por ejemplo, puede tener parámetros con nombre o posicionales en funciones avanzadas de esta manera:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [int] $Id
    )
}

Luego, podría llamarlo especificando el nombre del parámetro, o simplemente podría usar parámetros posicionales, ya que los definió explícitamente. Entonces, cualquiera de estos funcionaría:

Get-Something -Id 34 -Name "Blah"
Get-Something "Blah" 34

El primer ejemplo funciona aunque Namese proporciona el segundo, porque explícitamente usamos el nombre del parámetro. Sin embargo, el segundo ejemplo funciona según la posición, por Namelo que debería ser el primero. Cuando es posible, siempre trato de definir posiciones para que ambas opciones estén disponibles.

PowerShell también tiene la capacidad de definir conjuntos de parámetros. Utiliza esto en lugar de la sobrecarga de métodos, y de nuevo es bastante útil:

function Get-Something
{
    [CmdletBinding(DefaultParameterSetName='Name')]
    Param
    (
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Name')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Id')]
         [int] $Id
    )
}

Ahora la función tomará un nombre o una identificación, pero no ambos. Puede usarlos posicionalmente o por nombre. Como son de un tipo diferente, PowerShell lo resolverá. Entonces, todo esto funcionaría:

Get-Something "some name"
Get-Something 23
Get-Something -Name "some name"
Get-Something -Id 23

También puede asignar parámetros adicionales a los distintos conjuntos de parámetros. (Ese fue un ejemplo bastante básico, obviamente). Dentro de la función, puede determinar qué conjunto de parámetros se usó con la propiedad $ PsCmdlet.ParameterSetName. Por ejemplo:

if($PsCmdlet.ParameterSetName -eq "Name")
{
    Write-Host "Doing something with name here"
}

Luego, en una nota al margen relacionada, también hay validación de parámetros en PowerShell. Esta es una de mis características favoritas de PowerShell, y hace que el código dentro de sus funciones sea muy limpio. Existen numerosas validaciones que puede usar. Un par de ejemplos son:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidatePattern('^Some.*')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [ValidateRange(10,100)]
         [int] $Id
    )
}

En el primer ejemplo, ValidatePattern acepta una expresión regular que garantiza que el parámetro proporcionado coincida con lo que espera. Si no es así, se produce una excepción intuitiva que le dice exactamente qué está mal. Entonces, en ese ejemplo, 'Something' funcionaría bien, pero 'Summer' no pasaría la validación.

ValidateRange garantiza que el valor del parámetro se encuentre entre el rango que espera para un entero. Entonces 10 o 99 funcionarían, pero 101 arrojaría una excepción.

Otro útil es ValidateSet, que le permite definir explícitamente una matriz de valores aceptables. Si se ingresa algo más, se lanzará una excepción. También hay otros, pero probablemente el más útil es ValidateScript. Esto toma un bloque de script que debe evaluar a $ true, por lo que el cielo es el límite. Por ejemplo:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
         [ValidateScript({ (Get-Item $_ | select -Expand Extension) -eq ".csv" })]
         [string] $Path
    )
}

En este ejemplo, estamos seguros de que no solo existe $ Path, sino que es un archivo (a diferencia de un directorio) y tiene una extensión .csv. ($ _ se refiere al parámetro, cuando está dentro de su bloque de secuencia de comandos). También puede pasar bloques de secuencia de comandos de varias líneas mucho más grandes si se requiere ese nivel, o usar bloques de secuencia de comandos múltiples como hice aquí. Es extremadamente útil y ofrece buenas funciones de limpieza y excepciones intuitivas.

usuario2233949
fuente
3
+1 para demostrar el My_Function -NamedParamater "ParamValue"estilo de llamada de función. Este es un patrón que más código de script PS debe seguir para facilitar la lectura.
Mister_Tom
46

Llama a las funciones de PowerShell sin paréntesis y sin usar la coma como separador. Intenta usar:

test "ABC" "DEF"

En PowerShell, la coma (,) es un operador de matriz, p. Ej.

$a = "one", "two", "three"

Se establece $aen una matriz con tres valores.

Todd
fuente
16
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test "ABC" "DEF"
John B
fuente
11

Si eres un desarrollador de C # / Java / C ++ / Ruby / Python / Pick-A-Language-From-This-Century y quieres llamar a tu función con comas, porque eso es lo que siempre has hecho, entonces necesitas algo Me gusta esto:

$myModule = New-Module -ascustomobject { 
    function test($arg1, $arg2) { 
        echo "arg1 = $arg1, and arg2 = $arg2"
    }
}

Ahora llama:

$myModule.test("ABC", "DEF")

y verás

arg1 = ABC, and arg2 = DEF
Ryan Shillington
fuente
Java, C ++, Ruby y Python no son de este siglo (solo C #), suponiendo el calendario gregoriano (aunque algunos han evolucionado más que otros).
Peter Mortensen
Je @PeterMortensen, ¿su argumento es que debería decir "Elija un idioma de este siglo o del último"? :-)
Ryan Shillington
10

Si no sabe (o no le importa) cuántos argumentos pasará a la función, también podría usar un enfoque muy simple como;

Código :

function FunctionName()
{
    Write-Host $args
}

Eso imprimiría todos los argumentos. Por ejemplo:

FunctionName a b c 1 2 3

Salida

a b c 1 2 3

Encuentro esto particularmente útil al crear funciones que usan comandos externos que podrían tener muchos parámetros diferentes (y opcionales), pero se basa en dicho comando para proporcionar comentarios sobre errores de sintaxis, etc.

Aquí hay otro ejemplo del mundo real (creando una función para el comando tracert, que odio tener que recordar el nombre truncado);

Código :

Function traceroute
{
    Start-Process -FilePath "$env:systemroot\system32\tracert.exe" -ArgumentList $args -NoNewWindow
}
Draino
fuente
7

Si intentas:

PS > Test("ABC", "GHI") ("DEF")

usted obtiene:

$arg1 value: ABC GHI
$arg2 value: DEF

Entonces ves que los paréntesis separan los parámetros


Si intentas:

PS > $var = "C"
PS > Test ("AB" + $var) "DEF"

usted obtiene:

$arg1 value: ABC
$arg2 value: DEF

Ahora podría encontrar alguna utilidad inmediata de los paréntesis (un espacio no se convertirá en un separador para el siguiente parámetro), sino que tendrá una función eval.

RaSor
fuente
44
Parens no separan los parámetros. Definen matriz.
2013
1
Parens no define una matriz, definen un grupo, que PowerShell puede interpretar como una matriz. Las matrices se definen con una arroba ( @) antes de que los paren principales, como esta matriz vacía: @(); o esta matriz con dos números: @(1, 2).
VertigoRay
5

Debido a que esta es una pregunta frecuente, quiero mencionar que una función de PowerShell debe usar verbos aprobados ( Verbo-sustantivo como nombre de la función). La parte verbal del nombre identifica la acción que realiza el cmdlet. La parte sustantiva del nombre identifica la entidad en la que se realiza la acción. Esta regla simplifica el uso de sus cmdlets para usuarios avanzados de PowerShell.

Además, puede especificar cosas como si el parámetro es obligatorio y la posición del parámetro:

function Test-Script
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$arg1,

        [Parameter(Mandatory=$true, Position=1)]
        [string]$arg2
    )

    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Para pasar el parámetro a la función, puede usar la posición :

Test-Script "Hello" "World"

O puede especificar el nombre del parámetro :

Test-Script -arg1 "Hello" -arg2 "World"

No usa paréntesis como cuando llama a una función dentro de C #.


Recomendaría pasar siempre los nombres de los parámetros cuando se usa más de un parámetro, ya que es más legible .

Martin Brandl
fuente
Para su información, el enlace de la lista de verbos aprobados ya no funciona, pero ahora se puede encontrar aquí - docs.microsoft.com/en-us/powershell/developer/cmdlet/…
Keith Langmead el
@KeithLangmead Gracias Keith, también actualicé mi respuesta.
Martin Brandl
1
"Verbo-sustantivo" como en ambos verbo y sustantivo en mayúscula? ¿Quizás cambiar la respuesta para ser más explícito al respecto?
Peter Mortensen
@ PeterMortensen Gracias por mencionar eso. Actualicé mi respuesta.
Martin Brandl
1
bueno, considera que expones un Get-Nodecmdlet. Para nosotros estaría claro que tenemos que invocar Get-Node, no Retrieve-Node, ni Receive-Node, ni .....
Martin Brandl
4

No sé qué está haciendo con la función, pero eche un vistazo al uso de la palabra clave 'param'. Es bastante más potente para pasar parámetros a una función, y lo hace más fácil de usar. A continuación hay un enlace a un artículo demasiado complejo de Microsoft al respecto. No es tan complicado como el artículo lo hace sonar.

Uso de parámetros

Además, aquí hay un ejemplo de una pregunta en este sitio:

Echale un vistazo.

Rodney Fisk
fuente
Gracias por la respuesta. Sin embargo, tenía problemas al llamar a la función. No importaba si la función se declaraba con param o sin ella.
Nasir
3
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")
kleopatra
fuente
El resultado saldrá como debería:
2

Dije lo siguiente antes:

El problema común es usar la forma singular $arg, que es incorrecta. Siempre debe ser plural como$args .

El problema no es eso. De hecho,$arg puede ser cualquier otra cosa. El problema era el uso de la coma y los paréntesis.

Ejecuto el siguiente código que funcionó y la salida sigue:

Código:

Function Test([string]$var1, [string]$var2)
{
    Write-Host "`$var1 value: $var1"
    Write-Host "`$var2 value: $var2"
}

Prueba "ABC" "DEF"

Salida:

$var1 value: ABC
$var2 value: DEF
Eric
fuente
44
Gracias amigo mío, sin embargo, llegaste un par de años tarde :-) Las tres respuestas principales aquí habían abordado suficientemente el problema. ¿Puedo sugerir dirigirse a la sección Sin respuesta y probar algunas de esas preguntas?
Nasir
2
Function Test {
    Param([string]$arg1, [string]$arg2)

    Write-Host $arg1
    Write-Host $arg2
}

Esta es una paramsdeclaración adecuada .

Ver about_Functions_Advanced_Parameters .

Y de hecho funciona.

Serhii Kimlyk
fuente
1

También puede pasar parámetros en una función como esta:

function FunctionName()
{
    Param ([string]$ParamName);
    # Operations
}
Kaushal Khamar
fuente
3
Eso sería definir parámetros para una función, la pregunta original era sobre cómo especificar parámetros cuando se llama a la función.
Nasir
1

No lo veo mencionado aquí, pero salpicar sus argumentos es una alternativa útil y se vuelve especialmente útil si está creando dinámicamente los argumentos de un comando (en lugar de usarInvoke-Expression ). Puede salpicar con matrices para argumentos posicionales y tablas hash para argumentos con nombre. Aquí hay unos ejemplos:

Splat con matrices (argumentos posicionales)

Prueba de conexión con argumentos posicionales

Test-Connection www.google.com localhost

Con salpicaduras de matriz

$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentArray

Tenga en cuenta que al salpicar, hacemos referencia a la variable salpicada con un en @lugar de un $. Es lo mismo cuando se usa un Hashtable para salpicar también.

Splat With Hashtable (Argumentos nombrados)

Conexión de prueba con argumentos con nombre

Test-Connection -ComputerName www.google.com -Source localhost

Con salpicaduras de Hashtable

$argumentHash = @{
  ComputerName = 'www.google.com'
  Source = 'localhost'
}
Test-Connection @argumentHash

Splat posicional y argumentos nombrados simultáneamente

Conexión de prueba con argumentos posicionales y con nombre

Test-Connection www.google.com localhost -Count 1

Splatting Array y Hashtables juntos

$argumentHash = @{
  Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentHash @argumentArray
Bender el más grande
fuente