Articles

Adam the Automator

Quando começar a escrever os scripts PowerShell, chegará inevitavelmente a um lugar onde precisa de processar vários itens de uma colecção. É então que precisará de escavar os loops dianteiros do PowerShell e aprender do que se trata.

Inicialmente todas as linguagens de programação têm uma construção chamada loops; PowerShell não é diferente. Um dos tipos mais populares de loops no PowerShell é o laço para a testa. No seu mais básico, um laço para a frente lê uma colecção inteira de itens e um item para a frente, corre algum tipo de código.

Um dos aspectos mais confusos do laço para a frente do PowerShell para principiantes é todas as opções que tem. Não há apenas uma forma de processar cada item de uma colecção; há três!

Neste artigo, vai aprender como funciona cada tipo de laço para a testa e quando usar um sobre o outro. Quando terminar este artigo, já terá uma boa compreensão de cada tipo de laço de testa.

Table of Contents

PowerShell ForEach Loop Basics

Um dos tipos mais comuns de laços que irá utilizar no PowerShell é o foreach tipo de laço. Um foreach lê um conjunto de objectos (itera) e completa quando termina com o último. A colecção de objectos que são lidos é normalmente representada por um array ou um hashtable.

NOTE: O termo iteração é um termo de programação que faz referência a cada execução de um laço. Cada vez que um laço completa um ciclo, a isto chama-se iteração. O acto de correr um laço sobre um conjunto de objectos é normalmente referido como iteração sobre o conjunto.

Talvez seja necessário criar um ficheiro de texto em algumas pastas espalhadas por um sistema de ficheiros. Digamos que os caminhos das pastas são C:\FolderC:\Program Files\Folder2 e C:\Folder3. Sem um laço, teríamos de referir o Add-Content cmdlet 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 destas referências de comando? É o valor Path. O valor para Path é o único valor que está a mudar entre cada um destes.

Está a duplicar um monte de código. Está a perder tempo a digitar e a abrir-se para problemas ao longo do caminho. Em vez disso, deve criar um “conjunto” que inclua apenas todos os itens que estão a mudar. Para este exemplo, vamos usar um “array.

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

Tem agora cada um destes caminhos guardados num único “set” ou array. Está agora preparado para usar um laço para iterar sobre cada um destes elementos. Antes de o fazer, no entanto, é uma boa altura para mencionar um tópico que normalmente tropeça naqueles que são novos no PowerShell. Ao contrário de outros tipos de loops, foreach loops não são um dos mesmos.

Existem tecnicamente três tipos de foreach loops no PowerShell. Embora cada um seja semelhante ao uso, é importante compreender a diferença.

A declaração foreach

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

Utilizando o exemplo acima, a variável $i representa o iterador ou o valor de cada item em $path conforme itera sobre cada elemento da matriz.

Nota que a variável iteradora não tem de ser $i. O nome da variável pode ser qualquer coisa.

No exemplo abaixo, pode realizar a mesma tarefa que repetir o Add-Content referência ao fazer isto:

# Create an array of folders$folders = @('C:\Folder','C:\Program Files\Folder2','C:\Folder3')# Perform iteration to create the same file in each folderforeach ($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 utilizar a declaração ForEach-Object cmdlet.

O ForEach-Object CmdLet

Se foreach é uma declaração e só pode ser usada de uma única forma, ForEach-Object é um cmdlet com parâmetros que podem ser empregados de muitas maneiras diferentes. Como a declaração foreach, a declaração ForEach-Object cmdlet pode iterar sobre um conjunto de objectos. Apenas desta vez, passa esse conjunto de objectos e a acção a tomar sobre cada objecto como um parâmetro, como se mostra 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 tornar as coisas confusas, o ForEach-Object cmdlet tem um pseudónimo chamado foreach. Dependendo de como o termo “foreach” é chamado, o foreach declaração é executado, ou o ForEach-Object corridas.

Uma boa maneira de diferenciar estas situações é notar se o termo “foreach” é seguido por ($someVariable in $someSet). Caso contrário, em qualquer outro contexto, o autor do código estará provavelmente a utilizar o pseudónimo de ForEach-Object.

O método foreach()

Um dos mais recentes foreach loops foi introduzido no PowerShell v4 chamado a foreach() método. Este método existe sobre uma matriz ou objecto de colecção. O método foreach() tem um parâmetro de bloco de script padrão que contém as acções a tomar sobre cada iteração, tal 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 funciona sob o capô.

Utilizando o método foreach() é consideravelmente mais rápido e está visivelmente sobre grandes conjuntos. Recomenda-se a utilização deste método sobre os outros dois, se possível.

Mini-Projecto: Looping Over a Set of Server Names

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

Lista de nomes de servidor num ficheiro de texto
Lista de nomes de servidor num ficheiro de texto

Para este fluxo de trabalho, que tipo de laço acha que funcionaria melhor?

Notificação de que mencionei a palavra “set” como em “conjunto de servidores”. Esta é uma pista mesmo aqui. Já tem um número especificado de nomes de servidores, e gostaria de executar alguma acção em cada um deles. Isto parece uma grande oportunidade para experimentar o loop PowerShell mais comum utilizado; o foreach loop.

A primeira tarefa é criar um conjunto de nomes de servidores em código. A forma mais comum de o fazer é através da criação de um array. Felizmente para nós Get-Content, por defeito, devolve um array com cada elemento do array representado por uma única linha no ficheiro de texto.

Primeiro, crie um array com todos os nomes do meu servidor e chame-lhe $servers.

$servers = Get-Content .\servers.txt

Agora que tenha criado o array, terá agora de confirmar se cada servidor está online ou não. Um grande cmdlet para testar a ligação de um servidor chama-se Test-Connection. Este cmdlet realiza alguns testes de ligação num computador para ver se está online ou não.

Ao executar Test-Connection dentro de um foreach loop para ler cada linha de ficheiro, pode passar o nome de cada servidor representado pelo $server variável para Test-Connection. É desta forma que o PowerShell pode fazer um loop através de um ficheiro de texto. Pode ver um exemplo de como isto funciona abaixo.

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

Quando o laço foreach for executado, terá então um aspecto semelhante a este:

Looping Test-Connection através de uma lista de nomes de servidores
Looping Test-Ligação através de uma lista de nomes de servidor

Teste agora com sucesso a ligação de um ficheiro de texto cheio de nomes de servidor! Neste momento, pode agora adicionar ou remover nomes de servidor dentro do ficheiro de texto à vontade sem alterar o código.

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

ForEach PowerShell Exemplos

Deixe-nos ver alguns exemplos de como usar o laço ForEach. Estes são baseados em casos de utilização no mundo real mostrando o conceito que pode ser modificado para se adaptar às suas necessidades.

Exemplo 1: Criação de um ficheiro em cada subpasta num directório utilizando a pasta ForEach Statement

Este exemplo demonstra a utilização comum da pasta PowerShell foreach num directório.

Suponha que existem dez subpastas dentro da pasta C:\ARCHIVE_VOLUMES. Cada subpasta representa um volume de arquivo que é copiado diariamente. Após cada cópia de segurança estar completa, um ficheiro com o nome BackupState.txt é criado dentro de cada pasta contendo a data em que foi feita a cópia de segurança.

Veja um exemplo de como isto pode parecer abaixo.

Subdirectórios em C:\ARCHIVE_VOLUMES
Sub-directórios em C:{\a1}ARQUIVO_VOLUMES

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

  • esquece a lista de todas as sub-acçõespastas dentro de C:\ARCHIVE_VOLUMES
  • folheia cada pasta
  • cria um ficheiro de texto chamado BackupState.txt contendo a data e hora actual do seu valor

O exemplo abaixo é utilizando a declaração foreach.

# Define the TOP-level folder$TOP_FOLDER = "C:\ARCHIVE_VOLUMES"# Get all sub folders recursively$Child_Folders = Get-ChildItem -Path $TOP_FOLDER -Recurse | Where-Object { $_.PSIsContainer -eq $true }# Create a text file in each sub-folder and add the current date/time as value.foreach ($foldername in $Child_Folders.FullName) { (get-date -Format G) | Out-File -FilePath "$($foldername)\BackupState.txt" -Force}

Utilizando o Get-ChildItem cmdlet, é possível confirmar que os ficheiros foram criados ou actualizados 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 ecrã abaixo mostra a saída do script mostrando todos os ficheiros BackupState.txt encontrados em cada subdirectório.

Um ficheiro de texto é criado em cada subdirectório
Um ficheiro de texto é criado em cada subdirectório

Exemplo 2: Lendo o conteúdo de cada ficheiro de texto em subdirectórios

Next, para demonstrar a utilização do ficheiro PowerShell foreach num directório, o script abaixo irá ler cada BackupState.txt ficheiro criado no Exemplo 1.

  • Recursively find all BackupState.txt ficheiros dentro de cada subdirectório.
  • Utilizando o foreach declaração, leia cada ficheiro de texto para obter o valor “último tempo de backup”.
  • Exibir o resultado no ecrã.
## Find all BackupState.txt files in C:\ARCHIVE_VOLUMES$files = Get-ChildItem -Recurse -Path C:\ARCHIVE_VOLUMES -Include 'BackupState.txt' | Select-Object DirectoryName,FullName## Read the contents of each fileforeach ($file in $files) { Write-Output ("$($file.DirectoryName) last backup time - " + (Get-Content $file.FullName))}

Após o script ser executado na sua sessão PowerShell, deverá ver uma saída semelhante à captura de ecrã abaixo. Isto mostra que o PowerShell faz o loop através de ficheiros, lê o conteúdo e mostra a saída.

Using foreach to loop through files and read its contents.
Using foreach to loop through files and read its contents.

Exemplo 3: Obtenção de Serviços e Início dos mesmos utilizando o ForEach-Object CmdLet

Os administradores de sistemas precisam frequentemente de obter o estado dos serviços e desencadear um fluxo de trabalho manual ou automático para remediar qualquer falha nos serviços. Vejamos o script de amostra usando o ForEach-Object cmdlet.

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

  • Get uma lista de serviços que estão configurados para um arranque automático mas que não se encontram actualmente em estado de funcionamento.
  • Nextra, os itens da lista são canalizados para o ForEach-Object cmdlet para tentar iniciar cada serviço.
  • Uma mensagem de sucesso ou falha é exibida dependendo do resultado do Start-Service comando.
## Get a list of automatic services that are stopped.$services = Get-Service | Where-Object {$.StartType -eq 'Automatic' -and $.Status -ne 'Running'}## Pass each service object to the pipeline and process them with the Foreach-Object cmdlet$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, parecerá a imagem de ecrã de saída abaixo. Como se pode ver, cada serviço foi emitido um comando de início. Alguns foram iniciados com sucesso, enquanto alguns deles falharam em iniciar.

Utilizando o laço ForEach-Object para iniciar serviços
Utilizando o laço ForEach-Object para iniciar serviços

Exemplo 4: A leitura de dados de CSV usando o Método ForEach()

Utilizar dados de ficheiros CSV é popular entre os administradores do sistema. Colocar registos dentro de um ficheiro CSV facilita a execução de operações a granel usando a combinação Import-CSV e ForEach. Esta combinação é normalmente usada para criar múltiplos utilizadores em Active Directory.

Neste próximo exemplo, assume-se que tem um ficheiro CSV com duas colunas – Firstname e Lastname. Este ficheiro CSV deve então ser preenchido com os nomes dos novos utilizadores a serem criados. O ficheiro CSV pareceria algo como abaixo.

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

Agora para o próximo bloco de script. Primeiro, importar o ficheiro CSV passando o caminho do conteúdo para o ficheiro Import-CSV cmdlet. Depois, utilizando o método foreach(), faça um loop através dos nomes e crie os novos utilizadores no Active Directory.

# Import list of Firstname and Lastname from CSV file$newUsers = Import-Csv -Path .\Employees.csvAdd-Type -AssemblyName System.Web# Process the list$newUsers.foreach( { # Generate a random password $password = ::GeneratePassword((Get-Random -Minimum 20 -Maximum 32), 3) $secPw = ConvertTo-SecureString -String $password -AsPlainText -Force # Formulate a username $userName = '{0}{1}' -f $_.FirstName.Substring(0, 1), $_.LastName # Build new user attributes $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 } })

Quando executado, deverá agora ter criado um utilizador AD para cada linha no ficheiro CSV!

Resumo

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

Neste artigo, aprendeu os diferentes tipos de laços de foreach disponíveis no PowerShell, e o que considerar em relação a qual utilizar. Também viu os três tipos de loops de testa em acção usando diferentes cenários de amostra.

Leitura adicional

  • Gerir ficheiros CSV em PowerShell com Import-Csv (foreach loop)
  • Novo Utilizador-ADUser: Criar Utilizadores de Directório Activo com PowerShell
  • Basics of PowerShell Looping: Foreach | Blog de Scripting

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *