PowerShell Invoke-Command: Um Guia Abrangente

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.

PS> Invoke-Command
cmdlet Invoke-Command at command pipeline position 1
Supply values for the following parameters:
ScriptBlock:

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.

PS> hostname
MACWINVM

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.

PS> Invoke-Command -ScriptBlock { hostname } -ComputerName WEBSRV1 WEBSRV1

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.

function Install-Stuff {
    param(
    	[Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$InstallerFilePath
	)
    Invoke-Command -ComputerName $ComputerName -ScriptBlock { & $InstallerFilePath }
}

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
The expression after '&' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : BadExpression
+ PSComputerName        : websrv1

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" }

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
Installer path is:
PS>

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.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $InstallerFilePath } -ArgumentList $InstallerFilePath

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.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { param($foo) & $foo } -ArgumentList $InstallerFilePath

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.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $using:InstallerFilePath }

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.

Invoke-Command -Session $session -ScriptBlock { & $using:InstallerFilePath }

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!

Source:
https://adamtheautomator.com/invoke-command/