PowerShell ForEach Loops: 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 em PowerShell e entender do que se tratam.

Quase todas as linguagens de programação possuem uma construção 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 é a variedade de opções disponíveis. Não há apenas uma maneira de processar cada item em uma coleção; existem três!

Neste artigo, você vai 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 foreach. Um loop foreach lê um conjunto de objetos (itera) e é concluído quando termina o último. A coleção de objetos lidos é geralmente representada por uma matriz ou uma hashtable.

NOTA: 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 chamado de iterar sobre o conjunto.

Talvez você precise criar um arquivo de texto em alguns diretórios espalhados por um sistema de arquivos. Vamos supor que os caminhos dos diretórios sejam C:\Pasta, C:\Arquivos de Programas\Pasta2 e C:\Pasta3. Sem um loop, teríamos que fazer referência ao 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 Caminho. O valor de Caminho é o único valor que está mudando entre cada um desses.

Você está duplicando muito código. Está desperdiçando 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 matriz. Você está pronto para usar um loop para iterar sobre cada um desses elementos. Antes de fazer isso, porém, é um bom momento para mencionar um tópico que geralmente confunde os iniciantes no PowerShell. Ao contrário de outros tipos de loops, os loops foreach não são todos iguais.

Existem tecnicamente 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 conforme 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 instrução foreach é conhecida por ser uma alternativa mais rápida do que usar o cmdlet ForEach-Object.

O cmdlet ForEach-Object

Se foreach é uma instrução e só pode ser usada de uma maneira, ForEach-Object é um cmdlet com parâmetros que podem ser empregados de muitas maneiras diferentes. Assim como a instruçã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 um 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")

Observação: Para complicar as coisas, o cmdlet ForEach-Object tem um alias chamado foreach. Dependendo de como o termo “foreach” é chamado, a instruçã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 novos 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() tem 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 sob o capô.

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

Mini Projeto: Iterando Sobre um Conjunto de Nomes de Servidores

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 nosso primeiro mini projeto, vamos construir algum 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 é uma pista ali mesmo. 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 PowerShell mais comum usado; o loop foreach.

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

Primeiro, crie um array com todos os nomes dos meus servidores e chame-o de $servers.

$servers = Get-Content .\servers.txt

Agora que você tem o array criado, 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. Este cmdlet realiza alguns testes de conexão em um computador para verificar se está online ou não.

Ao executar 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 Test-Connection. É assim que o PowerShell faz um loop por 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, ficará algo assim:

Looping Test-Connection through a list of server names

Agora você testou com sucesso a conexão de um arquivo de texto cheio de nomes de servidor! Neste ponto, você pode adicionar ou remover nomes de servidor no arquivo de texto à vontade sem alterar o código.

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

Exemplos de ForEach no 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, os quais você pode modificar para atender às suas necessidades.

Exemplo 1: Criando um Arquivo em Cada Subpasta em um Diretório usando o Statement 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 é backup diariamente. Após cada backup ser concluído, um arquivo chamado BackupState.txt é criado dentro de cada pasta contendo a data em que foi feito o backup.

Você pode ver um exemplo de como isso pode ser parecido 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 o statement foreach.

# Define a pasta de nível superior
$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"

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

# Criar um arquivo de texto em cada subpasta e adicionar 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 em Subdiretórios

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

  • Encontre recursivamente todos os arquivos BackupState.txt dentro de cada subdiretório.
  • Usando a declaração foreach, leia cada arquivo de texto para obter o valor “última hora 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

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

Assim que o script for 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 de sistemas 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 ver o 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 estado de execução.
  • Próximo, os itens na lista são encaminhados 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.
## Obtenha uma lista de serviços automáticos que estão parados.
$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}

## Passe cada objeto de serviço para o pipeline e processe-os 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)"
    }
}

Quando o script é executado, ele se parecerá com a captura de tela de saída abaixo. Como você pode ver, cada serviço recebeu um comando de início. Alguns foram iniciados com sucesso, enquanto outros falharam ao iniciar.

Using the ForEach-Object loop to start services

Exemplo 4: Leitura de 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.

Neste próximo exemplo, presume-se que você tenha um arquivo CSV com duas colunas – Nome e Sobrenome. Este arquivo CSV deve então ser preenchido com os nomes dos novos usuários a serem criados. O arquivo CSV se pareceria 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, usando o método foreach(), percorra os nomes e crie os novos usuários no Active Directory.

# Importe a 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 do 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 é usada 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 quanto ao uso de cada um. Você também viu os três tipos de loops foreach em ação usando diferentes cenários de amostra.

Leitura Adicional

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