Como passo vários parâmetros para uma função no PowerShell?

436

Se eu tiver uma função que aceita mais de um parâmetro de string, o primeiro parâmetro parece obter todos os dados atribuídos a ele e os parâmetros restantes são passados ​​como vazios.

Um script de teste rápido:

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

Test("ABC", "DEF")

A saída gerada é

$arg1 value: ABC DEF
$arg2 value: 

A saída correta deve ser:

$arg1 value: ABC
$arg2 value: DEF

Isso parece ser consistente entre v1 e v2 em várias máquinas, então, obviamente, estou fazendo algo errado. Alguém pode apontar exatamente o que?

Nasir
fonte
2
Você acabou de chamar assim:Test "ABC" "DEF"
Ranadip Dutta 17/02

Respostas:

575

Os parâmetros nas chamadas para funções no PowerShell (todas as versões) são separados por espaço, não por vírgula . Além disso, os parênteses são totalmente desnecessários e causarão um erro de análise no PowerShell 2.0 (ou posterior) se Set-StrictModeestiver ativo. Os argumentos entre parênteses são usados ​​apenas nos 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
fonte
20
A coisa mais importante que finalmente ajudou a 'enfiar' isso na minha mente é a última frase: "Argumentos entre parênteses são usados ​​apenas nos métodos .NET."
Ashley
1
Eu prefiro usar a parêntese e a vírgula separadas .. é possível fazer isso no PowerShell?
sam yi
8
@samyi Não. Passar a (1,2,3)para uma função é efetivamente tratado como uma matriz; um único argumento. Se você deseja usar argumentos de estilo de método OO, use os módulos:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
x0n
4
Powershellé uma linguagem shell e é comum que as linguagens shell usem espaços como um separador de token. Eu não diria que Powershellé ser diferente aqui, é bem em linha com outros shells padrão do sistema, como cmd, sh, bash, etc.
Bender the Greatest
269

A resposta correta já foi fornecida, mas esse problema parece prevalecer o suficiente para justificar alguns detalhes adicionais para quem deseja entender as sutilezas.

Eu teria adicionado isso apenas como um comentário, mas queria incluir uma ilustração - rasguei isso do meu gráfico de referência rápida nas funções do PowerShell. Isso pressupõe que a assinatura da função f seja f($a, $b, $c):

Armadilhas da sintaxe de uma chamada de função

Assim, pode-se chamar uma função com parâmetros posicionais separados por espaço ou parâmetros nomeados independentes de ordem . As outras armadilhas revelam que você precisa conhecer vírgulas, parênteses e espaços em branco.

Para uma leitura mais aprofundada, consulte o meu artigo Abaixo da toca do coelho: um estudo em dutos, funções e parâmetros do PowerShell . O artigo também contém um link para o gráfico de referência rápida / mural.

Michael Sorens
fonte
4
A explicação com a sintaxe mais detalhada chamando cada parâmetro e atribuindo um valor a ele realmente cimentou. Obrigado!
ConstantineK
7
Obrigado, isso estava me deixando mentalmente sem entender o que estava fazendo de errado. Quando finalmente fiz tudo certo, estava com fome de uma explicação para esse comportamento.
BSAFH
1
Obrigado por postar isso como resposta. Saber por que algo errado é tão importante quanto o que está errado.
Gavin Ward
4
Esta é uma resposta muito melhor. Deveria estar mais adiante.
Mark-Bertenshaw #
53

Há algumas respostas boas aqui, mas eu queria apontar algumas outras coisas. Os parâmetros de função são, na verdade, um local onde o PowerShell brilha. Por exemplo, você pode ter parâmetros nomeados ou posicionais em funções avançadas como:

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

Em seguida, você pode chamá-lo especificando o nome do parâmetro ou apenas usar parâmetros posicionais, já que os definiu explicitamente. Então, qualquer um destes funcionaria:

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

O primeiro exemplo funciona, embora Nameseja fornecido em segundo lugar, porque usamos explicitamente o nome do parâmetro. O segundo exemplo funciona com base na posição, portanto, Nameseria necessário primeiro. Sempre que possível, tento sempre definir posições para que ambas as opções estejam disponíveis.

O PowerShell também tem a capacidade de definir conjuntos de parâmetros. Ele usa isso no lugar da sobrecarga do método e, novamente, é 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
    )
}

Agora, a função terá um nome ou um ID, mas não os dois. Você pode usá-los posicionalmente ou pelo nome. Como eles são de um tipo diferente, o PowerShell descobrirá isso. Então, tudo isso funcionaria:

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

Você também pode atribuir parâmetros adicionais aos vários conjuntos de parâmetros. (Esse foi um exemplo bastante básico, obviamente.) Dentro da função, você pode determinar qual conjunto de parâmetros foi usado com a propriedade $ PsCmdlet.ParameterSetName. Por exemplo:

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

Em uma nota lateral relacionada, também há validação de parâmetro no PowerShell. Esse é um dos meus recursos favoritos do PowerShell e torna o código dentro de suas funções muito limpo. Existem inúmeras validações que você pode usar. Alguns exemplos são:

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

No primeiro exemplo, ValidatePattern aceita uma expressão regular que garante que o parâmetro fornecido corresponda ao que você está esperando. Caso contrário, é lançada uma exceção intuitiva, informando exatamente o que está errado. Portanto, nesse exemplo, 'Something' funcionaria bem, mas 'Summer' não passaria na validação.

ValidateRange garante que o valor do parâmetro esteja no intervalo esperado para um número inteiro. Portanto, 10 ou 99 funcionariam, mas 101 lançaria uma exceção.

Outro útil é o ValidateSet, que permite definir explicitamente uma matriz de valores aceitáveis. Se algo mais for inserido, uma exceção será lançada. Também existem outros, mas provavelmente o mais útil é o ValidateScript. Isso requer um bloco de script que deve ser avaliado como $ true, para que o céu seja o limite. Por exemplo:

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

Neste exemplo, temos a certeza não apenas de que $ Path existe, mas também de um arquivo (em oposição a um diretório) e de uma extensão .csv. ($ _ refere-se ao parâmetro, quando dentro do seu bloco de script.) Você também pode passar blocos de script com várias linhas muito maiores, se esse nível for necessário, ou usar vários blocos de script como eu fiz aqui. É extremamente útil e contribui para boas funções limpas e exceções intuitivas.

user2233949
fonte
3
+1 para demonstrar My_Function -NamedParamater "ParamValue"o estilo de chamada de função. Esse é um padrão que mais códigos de script PS devem seguir para facilitar a leitura.
Mister_Tom
45

Você chama funções do PowerShell sem parênteses e sem usar a vírgula como separador. Tente usar:

test "ABC" "DEF"

No PowerShell, a vírgula (,) é um operador de matriz, por exemplo

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

Ele define $auma matriz com três valores.

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

Test "ABC" "DEF"
John B
fonte
11

Se você é um desenvolvedor de C # / Java / C ++ / Ruby / Python / Escolha a linguagem deste século deste século e deseja chamar sua função com vírgulas, porque é isso que sempre fez, então precisa de algo como isso:

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

Agora ligue para:

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

e você verá

arg1 = ABC, and arg2 = DEF
Ryan Shillington
fonte
Java, C ++, Ruby e Python não são deste século (apenas C #), presumindo o calendário gregoriano (embora alguns tenham evoluído mais que outros).
Peter Mortensen
Heh. @PeterMortensen, seu argumento é que eu deveria dizer "Escolha um idioma deste século ou do último"? :-)
Ryan Shillington
10

Se você não souber (ou se importar) com quantos argumentos estará passando para a função, também poderá usar uma abordagem muito simples como;

Código :

function FunctionName()
{
    Write-Host $args
}

Isso imprimiria todos os argumentos. Por exemplo:

FunctionName a b c 1 2 3

Resultado

a b c 1 2 3

Acho isso particularmente útil ao criar funções que usam comandos externos que podem ter muitos parâmetros diferentes (e opcionais), mas confiam no comando para fornecer feedback sobre erros de sintaxe etc.

Aqui está outro exemplo do mundo real (criando uma função para o comando tracert, que eu odeio ter que lembrar do nome truncado);

Código :

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

Se você tentar:

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

você obtém:

$arg1 value: ABC GHI
$arg2 value: DEF

Então você vê que os parênteses separam os parâmetros


Se você tentar:

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

você obtém:

$arg1 value: ABC
$arg2 value: DEF

Agora você pode encontrar alguma utilidade imediata entre parênteses - um espaço não se tornará um separador para o próximo parâmetro - em vez disso, você tem uma função eval.

RaSor
fonte
4
Parens não separa parâmetros. Eles definem matriz.
N12rd
1
Parens não define uma matriz, eles definem um grupo, que o PowerShell pode interpretar como uma matriz. As matrizes são definidas com um sinal ( @) antes de os paren principais, como esta matriz vazia: @(); ou esta matriz com dois números: @(1, 2).
VertigoRay
5

Como essa é uma pergunta frequente, quero mencionar que uma função do PowerShell deve usar verbos aprovados ( Verbo-Substantivo como o nome da função). A parte do verbo do nome identifica a ação que o cmdlet executa. A parte substantiva do nome identifica a entidade na qual a ação é executada. Essa regra simplifica o uso de seus cmdlets para usuários avançados do PowerShell.

Além disso, você pode especificar coisas como se o parâmetro é obrigatório e a posição do 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 passar o parâmetro para a função, você pode usar a posição :

Test-Script "Hello" "World"

Ou você especifica o nome do parâmetro :

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

Você não usa parênteses como quando chama uma função em C #.


Eu recomendaria sempre passar os nomes dos parâmetros ao usar mais de um parâmetro, pois isso é mais legível .

Martin Brandl
fonte
FYI, aprovado verbos lista de links obras já não, mas agora pode ser encontrado aqui - docs.microsoft.com/en-us/powershell/developer/cmdlet/...
Keith Langmead
@KeithLangmead Obrigado Keith, eu atualizei minha resposta também.
Martin Brandl
1
"Substantivo-verbo", como no verbo e no substantivo, está em maiúscula? Talvez mude a resposta para ser mais explícito sobre isso?
Peter Mortensen
@ PeterMortensen Obrigado por mencionar isso. Eu atualizei minha resposta.
Martin Brandl
1
bem, considere expor um Get-Nodecmdlet. Ficaria claro para nós que precisamos invocar Get-Node, não Retrieve-Node, nem Receive-Node, nem .....
Martin Brandl
4

Não sei o que você está fazendo com a função, mas veja a palavra-chave 'param'. É um pouco mais poderoso para passar parâmetros para uma função e a torna mais amigável ao usuário. Abaixo está um link para um artigo excessivamente complexo da Microsoft sobre o assunto. Não é tão complicado quanto o artigo faz parecer.

Uso de parâmetros

Além disso, aqui está um exemplo de uma pergunta neste site:

Confira.

Rodney Fisk
fonte
Obrigado pela resposta. No entanto, eu estava tendo problemas ao chamar a função. Não importava se a função foi declarada com param ou sem ele.
Nasir
3
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")
kleopatra
fonte
O resultado sairá como deveria:
2

Eu afirmei o seguinte anteriormente:

O problema comum é usar a forma singular $arg, que está incorreta. Deve sempre ser plural como $args.

O problema não é esse. De fato, $argpode ser qualquer outra coisa. O problema era o uso da vírgula e dos parênteses.

Eu executo o seguinte código que funcionou e a saída a seguir:

Código:

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

Teste "ABC" "DEF"

Resultado:

$var1 value: ABC
$var2 value: DEF
Eric
fonte
4
Obrigado, meu amigo, no entanto, você está atrasado alguns anos :-) As três principais respostas aqui abordaram suficientemente o problema. Posso sugerir ir para a seção Não respondida e tentar algumas dessas perguntas?
Nasir
2
Function Test {
    Param([string]$arg1, [string]$arg2)

    Write-Host $arg1
    Write-Host $arg2
}

Esta é uma paramsdeclaração adequada .

Consulte about_Functions_Advanced_Parameters .

E de fato funciona.

Serhii Kimlyk
fonte
1

Você pode passar parâmetros em uma função como esta também:

function FunctionName()
{
    Param ([string]$ParamName);
    # Operations
}
Kaushal Khamar
fonte
3
Isso seria definir parâmetros para uma função, a pergunta original era sobre como especificar parâmetros quando você chama a função.
Nasir
1

Não o vejo mencionado aqui, mas dividir seus argumentos é uma alternativa útil e se torna especialmente útil se você estiver construindo os argumentos para um comando dinamicamente (ao contrário de usar Invoke-Expression). Você pode dividir com matrizes para argumentos posicionais e hashtables para argumentos nomeados. aqui estão alguns exemplos:

Splat com matrizes (argumentos posicionais)

Conexão de teste com argumentos posicionais

Test-Connection www.google.com localhost

Com matriz de splatting

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

Observe que ao fazer splatting, referenciamos a variável splatted com um em @vez de a $. É o mesmo ao usar um Hashtable para splat também.

Splat com Hashtable (argumentos nomeados)

Conexão de teste com argumentos nomeados

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

Com Hashtable Splatting

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

Argumentos posicionais e nomeados de Splat simultaneamente

Conexão de teste com argumentos posicionais e nomeados

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

Matriz de splatting e tabelas de hash juntas

$argumentHash = @{
  Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentHash @argumentArray
Bender the Greatest
fonte