Laços ForEach do PowerShell: Tipos e Melhores Práticas

Quando você começa a escrever scripts em PowerShell, inevitavelmente chegará a um ponto em que precisará processar vários itens de uma coleção. É nesse momento que você precisará explorar os loops foreach do PowerShell e entender do que se trata.

Quase todas as linguagens de programação possuem uma estrutura chamada loops; o PowerShell não é diferente. Um dos tipos mais populares de loops no PowerShell é o loop foreach. Em sua forma mais básica, um loop foreach lê uma coleção inteira de itens e, para cada item, executa algum tipo de código.

Uma das partes mais confusas do loop foreach do PowerShell para iniciantes são todas as opções disponíveis. Não há apenas uma maneira de processar cada item em uma coleção; existem três!

Neste artigo, você aprenderá como cada tipo de loop foreach funciona e quando usar um em vez do outro. Ao final deste artigo, você terá uma boa compreensão de cada tipo de loop foreach.

Princípios Básicos do Loop ForEach do PowerShell

Um dos tipos mais comuns de loops que você usará no PowerShell é o tipo de loop foreach. Um loop foreach lê um conjunto de objetos (itera) e é concluído quando termina com o último. A coleção de objetos que é lida é geralmente representada por uma matriz ou uma tabela de hash.

OBSERVAÇÃO: O termo iteração é um termo de programação que se refere a cada execução de um loop. Cada vez que um loop completa um ciclo, isso é chamado de iteração. O ato de executar um loop sobre um conjunto de objetos é comumente referido como iterar sobre o conjunto.

Talvez você precise criar um arquivo de texto em várias pastas distribuídas por um sistema de arquivos. Vamos dizer que os caminhos das pastas são C:\Pasta, C:\Program Files\Pasta2 e C:\Pasta3. Sem um loop, teríamos que referenciar o cmdlet Add-Content três vezes.

Add-Content -Path 'C:\Folder\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Program Files\Folder2\textfile.txt' -Value 'This is the content of the file'
Add-Content -Path 'C:\Folder2\textfile.txt' -Value 'This is the content of the file'

Qual é a única diferença entre cada uma dessas referências de comando? É o valor de Path. O valor de Path é o único valor que está mudando entre cada um desses.

Você está duplicando muito código. Está perdendo tempo digitando e se expondo a problemas no futuro. Em vez disso, você deve criar um “conjunto” que inclua apenas todos os itens que estão mudando. Para este exemplo, usaremos uma matriz.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

Agora você tem cada um desses caminhos armazenados em um único “conjunto” ou array. Agora você está preparado para usar um loop para iterar sobre cada um desses elementos. Antes de fazer isso, no entanto, é um bom momento para mencionar um tópico que geralmente confunde aqueles que são novos no PowerShell. Ao contrário de outros tipos de loops, os loops foreach não são todos iguais.

Tecnicamente, existem três tipos de loops foreach no PowerShell. Embora cada um seja semelhante de usar, é importante entender a diferença.

A declaração foreach

O primeiro tipo de loop foreach é uma declaração. foreach é uma palavra-chave interna do PowerShell que não é um cmdlet nem uma função. A declaração foreach é sempre usada na forma: foreach ($i in $array).

Usando o exemplo acima, a variável $i representa o iterador ou o valor de cada item em $path enquanto ele itera sobre cada elemento no array.

Observe que a variável do iterador não precisa ser $i. O nome da variável pode ser qualquer coisa.

No exemplo abaixo, você pode realizar a mesma tarefa de repetir a referência Add-Content fazendo o seguinte:

# Criar um array de pastas
$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')

# Realizar iteração para criar o mesmo arquivo em cada pasta
foreach ($i in $folders) {
    Add-Content -Path "$i\SampleFile.txt" -Value "This is the content of the file"
}

A declaração foreach é conhecida por ser uma alternativa mais rápida do que usar o ForEach-Object cmdlet.

O CmdLet ForEach-Object

Se foreach é uma declaração e só pode ser usada de uma maneira, ForEach-Object é um cmdlet com parâmetros que podem ser empregados de várias maneiras diferentes. Como a declaração foreach, o cmdlet ForEach-Object pode iterar sobre um conjunto de objetos. Só que desta vez, ele passa esse conjunto de objetos e a ação a ser executada em cada objeto como parâmetro, como mostrado abaixo.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders | ForEach-Object (Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file")

Nota: Para complicar as coisas, o cmdlet ForEach-Object tem um alias chamado foreach. Dependendo de como o termo “foreach” é chamado, a declaração foreach é executada, ou o ForEach-Object é executado.

A good way to differentiate these situations is to notice if the term “foreach” is followed by ($someVariable in $someSet). Otherwise, in any other context, the code author is probably using the alias for ForEach-Object.

O Método foreach()

Um dos mais recentes loops foreach foi introduzido no PowerShell v4, chamado de método foreach(). Este método existe em um objeto de array ou coleção. O método foreach() possui um parâmetro de bloco de script padrão que contém as ações a serem executadas a cada iteração, assim como os outros.

$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')
$folders.ForEach({
	Add-Content -Path "$_\SampleFile.txt" -Value "This is the content of the file"
})

A diferença mais significativa com o método foreach() é como ele funciona internamente.

O uso do método foreach() é consideravelmente mais rápido e perceptivelmente superior em conjuntos grandes. Recomenda-se usar este método em vez dos outros dois, se possível.

Mini-Projeto: Iterando sobre um conjunto de nomes de servidor

Um dos usos mais comuns para um loop em PowerShell é ler um conjunto de servidores de alguma fonte e realizar alguma ação em cada um deles. Para o nosso primeiro mini-projeto, vamos construir um código que nos permitirá ler nomes de servidores (um por linha) de um arquivo de texto e pingar cada servidor para determinar se eles estão online ou não.

List of server names in a text file

Para este fluxo de trabalho, qual tipo de loop você acha que funcionaria melhor?

Observe que mencionei a palavra “conjunto” como em “conjunto de servidores”. Isso já é uma dica. Você já tem um número especificado de nomes de servidores e gostaria de realizar alguma ação em cada um deles. Isso parece uma ótima oportunidade para experimentar o loop foreach, que é o loop mais comumente usado no PowerShell.

A primeira tarefa é criar um conjunto de nomes de servidor em código. A forma mais comum de fazer isso é criando uma matriz. Felizmente para nós, o Get-Content, por padrão, retorna uma matriz em que cada elemento é representado por uma única linha no arquivo de texto.

Primeiro, crie uma matriz com todos os nomes dos meus servidores e chame-a de $servers.

$servers = Get-Content .\servers.txt

Agora que você criou a matriz, precisará confirmar se cada servidor está online ou não. Um ótimo cmdlet para testar a conexão de um servidor é chamado de Test-Connection. Esse cmdlet realiza alguns testes de conexão em um computador para saber se ele está online ou não.

Ao executar o Test-Connection dentro de um loop foreach para ler cada linha do arquivo, você pode passar cada nome de servidor representado pela variável $server para o Test-Connection. É assim que o PowerShell percorre um arquivo de texto. Você pode ver um exemplo de como isso funciona abaixo.

foreach ($server in $servers) {
	try {
		$null = Test-Connection -ComputerName $server -Count 1 -ErrorAction STOP
		Write-Output "$server - OK"
	}
	catch {
		Write-Output "$server - $($_.Exception.Message)"
	}
}

Quando o loop foreach é executado, ele ficará parecido com isto:

Looping Test-Connection through a list of server names

Você testou com sucesso a conexão de um arquivo de texto com vários nomes de servidor! Neste ponto, você pode adicionar ou remover nomes de servidor dentro do arquivo de texto à vontade, sem precisar alterar o código.

Você praticou com sucesso o que temos pregado, o método DRY.

Exemplos de ForEach em PowerShell

Vamos dar uma olhada em alguns exemplos de como usar o loop ForEach. Esses exemplos são baseados em casos de uso do mundo real que mostram o conceito, o qual você pode modificar para atender às suas necessidades.

Exemplo 1: Criando um Arquivo em Cada Subpasta em um Diretório usando a Instrução ForEach

Este exemplo demonstra o uso comum do foreach do PowerShell em uma pasta de diretório.

Suponha que existam dez subpastas dentro da pasta C:\ARCHIVE_VOLUMES. Cada subpasta representa um volume de arquivo que é copiado diariamente. Após cada backup ser concluído, um arquivo chamado BackupState.txt é criado dentro de cada pasta, contendo a data do backup.

Você pode ver um exemplo de como isso pode parecer abaixo.

Sub-directories under C:\ARCHIVE_VOLUMES

O script abaixo realiza três ações:

  • obtém a lista de todas as subpastas dentro de C:\ARCHIVE_VOLUMES
  • percorre cada pasta
  • cria um arquivo de texto chamado BackupState.txt contendo a data e hora atuais como seu valor

O exemplo abaixo está usando a instrução foreach.

# Defina a pasta de NÍVEL SUPERIOR
$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

# Obtenha todas as subpastas recursivamente
$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }

# Crie um arquivo de texto em cada subpasta e adicione a data/hora atual como valor.
foreach ($foldername in $Child_Folders.FullName) {
   (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force
}

Usando o cmdlet Get-ChildItem, você pode confirmar que os arquivos foram criados ou atualizados dentro de cada uma das subpastas.

Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include backupstate.txt | Select-Object Fullname,CreationTime,LastWriteTime,Length

A captura de tela abaixo mostra a saída do script exibindo todos os arquivos BackupState.txt encontrados em cada subdiretório.

A text file is created in each sub-directory

Exemplo 2: Lendo o Conteúdo de cada Arquivo de Texto nos Subdiretórios

Em seguida, para demonstrar o uso do PowerShell para cada arquivo em um diretório, o script abaixo lerá cada arquivo BackupState.txt criado no Exemplo 1.

  • Encontre recursivamente todos os arquivos BackupState.txt dentro de cada subdiretório.
  • Usando a instrução foreach, leia cada arquivo de texto para obter o valor “último horário de backup”.
  • Exiba o resultado na tela.
## Encontrar todos os arquivos BackupState.txt em C:\ARCHIVE_VOLUMES
$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName

## Ler o conteúdo de cada arquivo
foreach ($file in $files) {
    Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))
}

Depois que o script é executado em sua sessão do PowerShell, você deverá ver uma saída semelhante à captura de tela abaixo. Isso mostra que o PowerShell percorre os arquivos, lê o conteúdo e exibe a saída.

Using foreach to loop through files and read its contents.

Exemplo 3: Obtendo Serviços e Iniciando-os usando o CmdLet ForEach-Object

Os administradores do sistema frequentemente precisam obter o status dos serviços e acionar um fluxo de trabalho manual ou automático para remediar quaisquer serviços falhados. Vamos dar uma olhada no script de exemplo usando o ForEach-Object cmdlet.

Para este exemplo, o script abaixo fará o seguinte:

  • Obter uma lista de serviços configurados para inicialização automática, mas que atualmente não estão em um estado em execução.
  • A seguir, os itens da lista são enviados para o cmdlet ForEach-Object para tentar iniciar cada serviço.
  • A message of either success or failed is displayed depending on the result of the Start-Service command.
## Obter uma lista de serviços automáticos que estão parados.
$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## Passar cada objeto de serviço para o pipeline e processá-los com o cmdlet Foreach-Object
$services | ForEach-Object {
    try {
        Write-Host "Attempting to start '$($.DisplayName)'"
        Start-Service -Name $.Name -ErrorAction STOP
        Write-Host "SUCCESS: '$($.DisplayName)' has been started"
    } catch {
        Write-output "FAILED: $($.exception.message)"
    }
}

Ao executar o script, ele se parecerá com a captura de tela de saída abaixo. Como você pode ver, cada serviço recebeu um comando de inicialização. Alguns foram iniciados com sucesso, enquanto alguns deles falharam ao iniciar.

Using the ForEach-Object loop to start services

Exemplo 4: Lendo Dados de um CSV Usando o Método ForEach()

O uso de dados de arquivos CSV é popular entre os administradores do sistema. Colocar registros dentro de um arquivo CSV facilita a execução de operações em massa usando a combinação Import-CSV e ForEach. Essa combinação é comumente usada para criar vários usuários no Active Directory.

No próximo exemplo, é assumido que você tenha um arquivo CSV com duas colunas – PrimeiroNome e UltimoNome. Este arquivo CSV deve então ser populado com os nomes dos novos usuários a serem criados. O arquivo CSV ficaria algo assim.

"Firstname","Lastname"
"Grimm","Reaper"
"Hell","Boy"
"Rick","Rude"

Agora para o próximo bloco de script. Primeiro, importe o arquivo CSV passando o caminho do conteúdo para o cmdlet Import-CSV. Em seguida, utilizando o método foreach(), percorra os nomes e crie os novos usuários no Active Directory.

# Importar lista de Nome e Sobrenome do arquivo CSV
$newUsers = Import-Csv -Path .\Employees.csv

Add-Type -AssemblyName System.Web

# Processar a lista
$newUsers.foreach(
    {
        # Gerar uma senha aleatória
        $password = [System.Web.Security.Membership]::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3)
        $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force

        # Formular um nome de usuário
        $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName

        # Construir novos atributos de usuário
        $NewUserParameters = @{
            GivenName       = $_.FirstName
            Surname         = $_.LastName
            Name            = $userName
            AccountPassword = $secPw
        }

        try {
            New-AdUser @NewUserParameters -ErrorAction Stop
            Write-Output "User '$($userName)' has been created."
        }
        catch {
            Write-Output $_.Exception.Message
        }
    }
)

Ao executar, você deverá ter criado um usuário AD para cada linha no arquivo CSV!

Resumo

A lógica por trás do loop foreach no PowerShell não é diferente daquela de outras linguagens de programação. Apenas muda com a forma como é utilizada e qual variação do loop foreach é escolhida para qualquer tarefa específica.

Neste artigo, você aprendeu os diferentes tipos de loops foreach disponíveis no PowerShell e o que considerar em relação a qual usar. Você também viu os três tipos de loops foreach em ação usando diferentes cenários de exemplo.

Leitura adicional

Source:
https://adamtheautomator.com/powershell-foreach/