Profissionais de TI raramente trabalham apenas em nosso computador local. Usando o cmdlet Invoke-Command do PowerShell, não precisamos! Este cmdlet nos permite escrever código de forma transparente como se estivéssemos trabalhando em nosso computador local.
Usando o recurso de Remoting do PowerShell, o cmdlet Invoke-Command é um cmdlet do PowerShell comumente usado 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 eles sejam implementados de maneira diferente, o conceito é o mesmo. Pegue um pouco de código ou comando e execute-o “localmente” no computador remoto.
No entanto, para que o Invoke-Command funcione, você deve ter o Remoting do PowerShell habilitado e disponível no computador remoto. Por padrão, todas as máquinas Windows Server 2012 R2 ou posterior têm isso habilitado junto com as exceções adequadas no firewall. Se você tiver o azar de ainda ter máquinas com o Server 2008, existem várias maneiras de configurar o Remoting, mas uma maneira fácil é executar o comando winrm quickconfig
ou Enable-PSRemoting
no computador remoto.
Para demonstrar como Invoke-Command funciona com um “comando ad-hoc”, o que significa que não requer a criação de uma nova PSSession, vamos supor que você tenha um computador remoto Windows Server 2012 R2 ou posterior, integrado a um domínio. As coisas ficam um pouco bagunçadas quando trabalhando em computadores de grupo de trabalho. Vou abrir meu console do 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 provar que o código dentro do scriptblock é executado no computador remoto, vamos executar apenas o comando hostname
. Este comando retornará o nome do computador em que está sendo executado. Executando hostname
em meu computador local, temos o nome.
Agora, vamos passar um scriptblock com o mesmo código dentro de um scriptblock para Invoke-Command
. Antes disso, porém, estamos esquecendo de um parâmetro obrigatório: ComputerName
. Devemos dizer ao Invoke-Command
em qual computador remoto executar este 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 fácil de Invoke-Command
, mas ele pode fazer muito mais.
Passando variáveis locais para scriptblocks remotos.
Você não vai ter uma única referência do Invoke-Command dentro de um script. Seu script provavelmente terá dezenas de linhas, terá variáveis definidas em locais, funções definidas em módulos e assim por diante. Mesmo que apenas envolver algum código em algumas chaves pareça inocente, você está de fato mudando 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 ideia de todo o código local em sua máquina além do que está no scriptblock.
Por exemplo, talvez você tenha uma função com nome de computador e um parâmetro de caminho de arquivo. O objetivo desta função é executar algum instalador de software no computador remoto. Você é capaz de 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.
Ela falha com uma mensagem de erro obscura devido ao meu uso do operador ampersand. O código não estava errado, mas falhou porque o $InstallerFilePath
estava vazio, mesmo que você tenha passado um valor com o 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" }
Observe que o valor de $InstallerFilePath
é vazio. A variável não foi expandida porque não foi passada para a máquina remota. Para passar variáveis definidas localmente para o scriptblock remoto, temos duas opções: podemos anteceder o nome da variável com $using:
dentro do scriptblock ou podemos usar o parâmetro ArgumentList
do Invoke-Command
. Vamos analisar ambos.
O parâmetro ArgumentList
Uma forma de passar variáveis locais para um scriptblock remoto é usar o parâmetro ArgumentList
do Invoke-Command
. Esse parâmetro permite que você passe variáveis locais para o parâmetro e substitua as 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 alterá-lo para { & $args[0] }
ou {param($foo) & $foo }
. Ambas as formas funcionam da mesma maneira, mas qual delas 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 apenas passando um.
Quando executado, o cmdlet Invoke-Command pega essa coleção e a injeta no scriptblock, transformando-a em uma matriz chamada $args
. Lembre-se de que $args -eq ArgumentList
. Neste ponto, você se refere a cada índice da coleção como se fosse uma matriz. No nosso caso, tínhamos apenas um elemento na coleção ($InstallerFilePath
) que “traduzido” para $args[0]
significa o primeiro índice dessa coleção. No entanto, se você tiver mais, você se referiria a eles como $args[1]
, $args[2]
e assim por diante.
Além disso, se você preferir atribuir melhores nomes de variáveis às variáveis do scriptblock, você também pode adicionar parâmetros ao scriptblock, assim como em uma função. Afinal, um scriptblock é apenas uma função anônima. Para criar parâmetros de scriptblock, crie um bloco param com o nome do parâmetro. Depois de criado, você pode se referir a esse parâmetro no scriptblock da seguinte maneira:
Neste exemplo, os elementos da 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 da 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 Construtor $Using
O construto $using
é outra forma popular de passar variáveis locais para um bloco de script remoto. Esse construto permite reutilizar as variáveis locais existentes, apenas acrescentando o nome da variável com o prefixo $using:
. Não é necessário se preocupar com uma coleção $args
nem adicionar um bloco de parâmetros.
O construto $using
do PowerShell é muito mais simples, mas se você algum dia se aprofundar no aprendizado do Pester, verá que ArgumentList
será seu amigo.
Invoke-Command e New-PSSession
Tecnicamente, esta postagem é apenas sobre o Invoke-Command, mas para demonstrar sua utilidade, precisamos mencionar brevemente o comando New-PSSession
também. Lembre-se de que mencionei anteriormente que o Invoke-Command
pode usar comandos “ad-hoc” ou usar sessões existentes.
Ao longo desta postagem, apenas executamos comandos “ad-hoc” em computadores remotos. Estamos criando uma nova sessão, executando o código e encerrando-a. Isso é bom para situações pontuais, mas não é tão adequado quando você está executando dezenas de comandos no mesmo computador. Nesse caso, é melhor reutilizar uma PSSession existente, criando-a com o New-PSSession
antecipadamente.
Antes de executar qualquer comando, você primeiro 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 terminar, é 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. É um que eu pessoalmente uso mais do que quase todos os outros. Sua facilidade de uso e capacidade de executar qualquer código em computadores remotos é extremamente poderosa e é um comando que eu recomendo aprender de cima a baixo!