O cmdlet ForEach-Object é uma ferramenta fundamental no PowerShell para processar cada item que chega pelo pipeline. Diferente de loops tradicionais, ele opera no contexto do pipeline, permitindo transformar, filtrar ou executar ações em cada objeto de forma eficiente e com baixo consumo de memória. Nesta aula, vamos explorar desde o uso básico com o iterador automático $_ até blocos avançados como Begin, Process e End, além de comparar com a instrução foreach.

Iteração no pipeline

O pipeline do PowerShell permite encadear comandos, passando objetos de um cmdlet para outro. O ForEach-Object é projetado para consumir objetos do pipeline e executar um bloco de script para cada um. Isso é útil quando você precisa aplicar uma operação a cada elemento, como converter, extrair propriedades ou gerar saída personalizada.

Exemplo básico: listar processos e exibir apenas o nome e o uso de memória.

Get-Process | ForEach-Object { $_.Name + ' - ' + $_.WorkingSet }

Nesse caso, Get-Process envia objetos de processo para o pipeline, e ForEach-Object executa o bloco para cada um, acessando as propriedades via $_. A saída é uma string formatada para cada processo.

$_

O símbolo $_ (dollar underscore) é uma variável automática que representa o objeto atual no pipeline. Dentro do bloco de script de ForEach-Object, $_ contém o objeto que está sendo processado na iteração corrente. É equivalente a $PSItem (introduzido no PowerShell 3.0).

Exemplo: selecionar apenas processos com mais de 10 MB de memória.

Get-Process | ForEach-Object { if ($_.WorkingSet -gt 10MB) { $_ } }

Note que $_ é uma variável de contexto, disponível apenas dentro do bloco de script. Fora dele, ela não tem valor. É comum usar $_ em combinação com propriedades, métodos e operadores.

Blocos Begin/Process/End

O ForEach-Object aceita até três blocos de script: Begin, Process e End. Eles permitem executar lógica antes de processar o primeiro item, para cada item, e depois do último item, respectivamente.

  • Begin: executa uma vez antes de qualquer item ser processado. Útil para inicializar variáveis, abrir arquivos, etc.
  • Process: executa para cada item do pipeline. O bloco padrão quando apenas um script é fornecido.
  • End: executa uma vez após todos os itens terem sido processados. Ideal para finalizar recursos, gerar resumos, etc.

Exemplo: contar processos por nome de empresa, exibindo o total no final.

Get-Process | ForEach-Object -Begin { $count = @{} } -Process { $company = $_.Company; if ($company) { $count[$company]++ } } -End { $count.GetEnumerator() | Sort-Object Value -Descending }

Aqui, Begin cria um hashtable vazio, Process incrementa a contagem para cada empresa, e End exibe o resultado ordenado.

vs foreach statement

A instrução foreach (ou ForEach-Object quando usado como alias) e o cmdlet ForEach-Object são diferentes, embora ambos iterem sobre coleções.

  • foreach statement: foreach ($item in $collection) { ... } carrega toda a coleção na memória antes de iterar. É mais rápido para coleções pequenas e pré-carregadas, mas consome mais memória para grandes conjuntos.
  • ForEach-Object cmdlet: processa itens um a um conforme eles chegam pelo pipeline, sem carregar tudo na memória. Ideal para pipelines que geram muitos objetos ou quando você quer combinar com outros cmdlets.

Exemplo de foreach statement:

$processes = Get-Process
foreach ($p in $processes) { $p.Name }

Exemplo equivalente com ForEach-Object:

Get-Process | ForEach-Object { $_.Name }

Na prática, prefira ForEach-Object quando estiver no meio de um pipeline e foreach quando já tiver a coleção em memória e precisar de desempenho máximo.

Boas práticas

  • Use $_ com moderação e prefira nomes descritivos em scripts longos, atribuindo a variável a um nome: $process = $_.
  • Para operações simples, o bloco Process é suficiente. Use Begin e End para inicialização e finalização.
  • Evite modificar a coleção que está sendo iterada; isso pode causar comportamentos inesperados.
  • Considere o uso de ForEach-Object -Parallel (PowerShell 7+) para paralelismo, mas cuidado com concorrência.

Referências

Exercícios

  1. Crie um comando que liste todos os serviços (Get-Service) e exiba apenas o nome e o status (Running/Stopped) usando ForEach-Object.

    ✓ Resposta:
    Get-Service | ForEach-Object { $_.Name + ' - ' + $_.Status }
  2. Utilizando os blocos Begin/Process/End, crie um comando que calcule a soma total do WorkingSet (memória) de todos os processos e exiba o resultado no final.

    ✓ Resposta:
    Get-Process | ForEach-Object -Begin { $total = 0 } -Process { $total += $_.WorkingSet } -End { Write-Host "Total de memória: $total bytes" }
  3. Explique a diferença entre foreach statement e ForEach-Object em termos de consumo de memória.

    ✓ Resposta:

    O foreach statement carrega toda a coleção na memória antes de iterar, consumindo mais memória para grandes conjuntos. Já o ForEach-Object processa itens um a um conforme chegam pelo pipeline, sem armazenar tudo, sendo mais eficiente em memória para grandes volumes de dados.

  4. Usando $_ e Where-Object, filtre apenas os processos com mais de 50 MB de WorkingSet e exiba o nome e a memória em MB.

    ✓ Resposta:
    Get-Process | Where-Object { $_.WorkingSet -gt 50MB } | ForEach-Object { $_.Name + ' - ' + ($_.WorkingSet / 1MB).ToString('F2') + ' MB' }
  5. Crie um script que, usando ForEach-Object, leia um arquivo de texto (Get-Content) e exiba cada linha com o número da linha (dica: use uma variável no Begin).

    ✓ Resposta:
    Get-Content arquivo.txt | ForEach-Object -Begin { $linha = 1 } -Process { "Linha $linha`: $_"; $linha++ }