Os profissionais de TI raramente trabalham apenas no nosso computador local. Com o cmdlet Invoke-Command do PowerShell, isso não é mais necessário! Este cmdlet nos permite escrever código de forma transparente, como se estivéssemos trabalhando no nosso computador local.
Ao usar o recurso de Remoting do PowerShell, o cmdlet Invoke-Command
é um cmdlet comumente utilizado que permite ao usuário executar código dentro de uma PSSession. Essa PSSession pode ser uma criada anteriormente com o cmdlet New-PSSession
ou pode criar e encerrar rapidamente uma sessão temporária também.
Relacionado: PowerShell Remoting: O Guia Definitivo
Pense no Invoke-Command como o psexec do PowerShell. Embora implementados de maneira diferente, o conceito é o mesmo. Pegue um pouco de código ou comando e execute “localmente” no computador remoto.
No entanto, para que o Invoke-Command
funcione, é necessário ter o PowerShell Remoting ativado e disponível no computador remoto. Por padrão, todas as máquinas com Windows Server 2012 R2 ou posterior têm isso ativado, juntamente com as exceções apropriadas no firewall. Se você tiver a infelicidade de ainda ter máquinas Server 2008, existem várias maneiras de configurar o Remoting, mas uma maneira fácil é executar winrm quickconfig
ou Enable-PSRemoting
na máquina remota.
Para demonstrar como o Invoke-Command funciona com um “comando ad-hoc”, significando um que não requer a criação de uma nova PSSession, digamos que você tenha um computador remoto com Windows Server 2012 R2 ou posterior, que faz parte de um domínio. As coisas ficam um pouco complicadas quando se trata de trabalhar em computadores de grupo de trabalho. Vou abrir meu console PowerShell, digitar Invoke-Command
e pressionar Enter.
I’m immediately asked to provide a scriptblock. The scriptblock is the code that we’re going to run on the remote computer.
Para comprovar que o código dentro do scriptblock está sendo executado no computador remoto, vamos apenas executar o comando hostname
. Este comando retornará o nome do host do computador em que está sendo executado. Executar hostname
no meu computador local retorna o nome dele.
Agora, vamos passar um scriptblock com esse mesmo código dentro para Invoke-Command
. Antes de fazer isso, estamos esquecendo um parâmetro obrigatório: ComputerName
. Precisamos informar ao Invoke-Command
em qual computador remoto executar esse comando.
Observe que a saída de hostname
agora é o nome do computador remoto WEBSRV1
. Você executou algum código em WEBSRV1. Executar código simples dentro de um scriptblock e passá-lo para uma única máquina remota é a aplicação mais simples do Invoke-Command
, mas ele pode fazer muito mais.
Passando Variáveis Locais para Scriptblocks Remotos
Não será necessário ter uma única referência Invoke-Command dentro de um script. O script provavelmente terá dezenas de linhas, variáveis definidas em diferentes lugares, funções definidas em módulos, entre outros. Mesmo que apenas envolver algum código com algumas chaves pareça inofensivo, na verdade você está alterando todo o escopo em que esse código está sendo executado. Afinal, você está enviando esse código para um computador remoto. Esse computador remoto não tem noção de todo o código local em sua máquina, a não ser o que está no scriptblock.
Por exemplo, talvez você tenha uma função com o nome do computador e um parâmetro para o caminho do arquivo. O objetivo dessa função é executar algum instalador de software no computador remoto. Você consegue passar o nome do computador e o caminho do arquivo “local” para o instalador que já está localizado no computador remoto.
A função abaixo parece razoável, certo? Vamos executá-la.
Porém, ela falha com uma mensagem de erro obscura devido ao uso do operador ampersand. O código não estava errado, mas falhou porque a variável `$InstallerFilePath` estava vazia, mesmo que você tenha passado um valor através do parâmetro da função. Podemos testar isso substituindo o ampersand por Write-Host
.
Nova linha da função: Invoke-Command -ComputerName $ComputerName -ScriptBlock { Write-Host "O caminho do instalador é: $InstallerFilePath" }
Repare que o valor de $InstallerFilePath
é nada. A variável não foi expandida porque não foi passada para a máquina remota. Para passar variáveis localmente definidas para o scriptblock remoto, temos duas opções; podemos prefixar o nome da variável com $using:
dentro do scriptblock ou podemos usar o parâmetro ArgumentList
do Invoke-Command
. Vamos olhar para ambos.
O Parâmetro ArgumentList
Uma maneira de passar variáveis locais para um scriptblock remoto é usar o parâmetro ArgumentList
do Invoke-Command
. Este parâmetro permite que você passe variáveis locais para o parâmetro e substitua referências de variáveis locais no scriptblock por espaços reservados.
Passar as variáveis locais para o parâmetro ArgumentList
é fácil.
A parte que confunde algumas pessoas é como estruturar as variáveis dentro do scriptblock. Em vez de usar { & $InstallerPath }
, precisamos mudá-lo para ser { & $args[0] }
ou {param($foo) & $foo }
. De qualquer forma, funciona da mesma maneira, mas qual você deve usar?
O parâmetro ArgumentList
é uma coleção de objetos. Coleções de objetos permitem que você passe um ou mais objetos de uma vez. Neste caso, estou passando apenas um.
Quando executado, o cmdlet Invoke-Command pega essa coleção e a injeta no bloco de script, essencialmente transformando-a em uma matriz chamada $args
. Lembre-se de que $args -eq ArgumentList
. Neste ponto, você referenciaria cada índice da coleção da mesma forma que faria com uma matriz. No nosso caso, tínhamos apenas um elemento na coleção ($InstallerFilePath
), que “traduzido” seria $args[0]
, significando o primeiro índice nessa coleção. No entanto, se você tivesse mais, você os referenciaria como $args[1]
, $args[2]
e assim por diante.
Além disso, se preferir atribuir nomes de variáveis mais adequados às variáveis do bloco de script, você também pode adicionar parâmetros ao bloco de script, assim como em uma função. Afinal, um bloco de script é apenas uma função anônima. Para criar parâmetros de bloco de script, crie um bloco param com o nome do parâmetro. Uma vez criado, então faça referência a esse parâmetro no bloco de script como mostrado abaixo.
Neste exemplo, os elementos na coleção ArgumentList
são “mapeados” para os parâmetros definidos na ordem. Os nomes dos parâmetros não importam; é a ordem que é importante. O Invoke-Command
pegará o primeiro elemento na coleção ArgumentList
, procurará pelo primeiro parâmetro e mapeará esses valores, fará o mesmo para o segundo, o terceiro e assim por diante.
O Construto $Using
O $using
construct é outra forma popular de passar variáveis locais para um scriptblock remoto. Esse construto permite que você reutilize as variáveis locais existentes, simplesmente antecedendo o nome da variável com $using:
. Não há necessidade de se preocupar com uma coleção $args
nem adicionar um bloco de parâmetros.
O construto $using
no PowerShell é muito mais simples, mas se você entrar no aprendizado do Pester, verá que ArgumentList
será seu aliado.
Invoke-Command e New-PSSession
Tecnicamente, este post trata apenas do Invoke-Command, mas para demonstrar sua utilidade, precisamos abordar brevemente o comando New-PSSession
também. Lembre-se de que mencionei anteriormente que Invoke-Command
pode usar comandos “ad-hoc” ou utilizar sessões existentes.
Ao longo deste post, apenas executamos comandos “ad-hoc” em computadores remotos. Estamos iniciando uma nova sessão, executando código e encerrando-a. Isso é adequado para situações pontuais, mas não tanto quando você está executando dezenas de comandos no mesmo computador. Nesse caso, é melhor reutilizar uma PSSession existente, criando-a antecipadamente com New-PSSession
.
Antes de executar qualquer comando, primeiro você precisará criar uma PSSession com New-PSSession
. Podemos fazer isso simplesmente executando $session = New-PSSession -ComputerName WEBSRV1
. Isso cria uma sessão remota no servidor, bem como uma referência a essa sessão em minha máquina local. Neste ponto, posso substituir minhas referências de ComputerName
por Session
e apontar Session
para minha variável $session
salva.
Ao executar, você notará que o desempenho é mais rápido porque a sessão já foi criada. No entanto, quando concluído, é importante remover a sessão aberta com Remove-PSSession
.
Resumo
O cmdlet Invoke-Command do PowerShell é um dos cmdlets mais comuns e poderosos que existem. É o que eu pessoalmente uso mais entre quase todos eles. Sua facilidade de uso e capacidade de executar qualquer código em computadores remotos são extremamente poderosas e é um comando que eu recomendo aprender do início ao fim!