PowerShell Invoke-Command: Um Guia Abrangente

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.

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 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.

PS> hostname
MACWINVM

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.

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 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.

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

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

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

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.

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 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:

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

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.

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

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.

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 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!

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