Boas práticas em PowerShell
LidoEsta aula aborda boas práticas essenciais no PowerShell, incluindo o uso de verbos aprovados, a saída de objetos em vez de texto, a escolha entre funções e scripts, e a escrita de código idiomático. O objetivo é capacitar o aluno a escrever scripts mais profissionais, consistentes e eficientes.
Bem-vindo à aula 80 do nosso curso de PowerShell! Nesta aula, vamos nos aprofundar em boas práticas que diferenciam um script amador de um script profissional. O PowerShell não é apenas uma linguagem de script, é uma poderosa ferramenta de automação que, quando usada corretamente, permite criar soluções robustas, reutilizáveis e fáceis de manter. Ao longo desta aula, você aprenderá a aplicar padrões recomendados pela comunidade e pela própria Microsoft, tornando seu código mais consistente e alinhado ao ecossistema PowerShell.
Dominar essas práticas não só melhora a qualidade do seu código, mas também facilita a colaboração em equipe e a integração com outras ferramentas. Vamos explorar cada tópico com exemplos práticos, para que você possa aplicar imediatamente no seu dia a dia.
Verbos aprovados
O PowerShell possui uma lista de verbos aprovados que devem ser usados ao nomear funções, cmdlets e scripts. Esses verbos são padronizados para garantir que o nome de um comando transmita claramente sua ação. Por exemplo, verbos como 'Get', 'Set', 'New', 'Remove', 'Start', 'Stop' são amplamente reconhecidos. Usar verbos fora dessa lista pode confundir os usuários e violar as convenções da comunidade.
A lista completa está documentada na Microsoft Docs e inclui verbos como 'Add', 'Clear', 'Copy', 'Enter', 'Exit', 'Find', 'Format', 'Hide', 'Join', 'Lock', 'Move', 'Open', 'Optimize', 'Push', 'Pop', 'Receive', 'Rename', 'Resize', 'Search', 'Select', 'Show', 'Skip', 'Split', 'Step', 'Switch', 'Undo', 'Unlock', 'Watch', entre outros. Ao criar uma função, verifique se o verbo escolhido está na lista. Se não estiver, considere usar um sinônimo ou um verbo aprovado que se aproxime.
Exemplo: em vez de criar uma função chamada 'Make-Report', use 'New-Report'. Em vez de 'Delete-File', use 'Remove-File'. Isso torna o código mais previsível e fácil de descobrir.
function Get-UserInfo {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$UserName
)
# ... implementação
}
function Set-UserStatus {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$UserName,
[Parameter(Mandatory = $true)]
[string]$Status
)
# ... implementação
}
Além disso, o PowerShell possui um módulo chamado 'PSScriptAnalyzer' que pode verificar se seus verbos estão aprovados. Você pode executar Invoke-ScriptAnalyzer -Path .\script.ps1 para obter avisos e sugestões.
Saída de objetos (não texto)
Uma das filosofias centrais do PowerShell é que os comandos devem emitir objetos, não texto. Isso permite que os dados sejam manipulados programaticamente, filtrados, formatados e passados para outros comandos sem a necessidade de parsear strings. Quando você escreve uma função, evite usar Write-Host para saída de dados, pois isso escreve diretamente no console e não pode ser capturado em uma variável ou pipeline. Em vez disso, use Write-Output (ou simplesmente coloque o objeto no fluxo de saída) para emitir objetos.
Por exemplo, uma função que retorna informações de processos deve emitir objetos com propriedades como Name, Id, CPU, etc., em vez de uma string formatada. Isso permite que o usuário faça Get-MyProcess | Where-Object { $_.CPU -gt 10 } ou Get-MyProcess | Export-Csv.
function Get-MyProcess {
[CmdletBinding()]
param(
[string]$Name
)
$processes = Get-Process -Name $Name -ErrorAction SilentlyContinue
foreach ($process in $processes) {
# Emite um objeto personalizado com as propriedades desejadas
[PSCustomObject]@{
Name = $process.ProcessName
Id = $process.Id
CPU = $process.CPU
WorkingSet = $process.WorkingSet64
}
}
}
Observe que a função acima não usa Write-Host; ela simplesmente emite objetos. O usuário pode redirecionar a saída para um arquivo, formatar como tabela ou lista, ou usar com outros cmdlets.
Outra prática importante é usar Write-Verbose, Write-Debug e Write-Warning para mensagens de diagnóstico, em vez de Write-Host. Isso permite que o usuário controle a verbosidade com parâmetros comuns como -Verbose e -Debug.
Funções vs scripts
No PowerShell, você pode escrever código em scripts (.ps1) ou em funções (dentro de módulos ou scripts). A escolha entre eles depende do contexto e do objetivo. Funções são blocos de código nomeados que podem ser reutilizados, aceitam parâmetros e podem ser incorporadas em módulos. Scripts são arquivos executáveis que geralmente realizam uma tarefa específica de ponta a ponta.
Como boa prática, prefira funções para lógica reutilizável. Ao criar uma função, ela deve ser projetada para fazer uma coisa bem feita, ter um nome baseado em verbo aprovado e aceitar parâmetros bem definidos. Scripts, por outro lado, devem ser usados para orquestrar uma sequência de operações, muitas vezes chamando funções. Um script pode ser o ponto de entrada de uma automação, enquanto as funções são os blocos de construção.
Exemplo de um script que usa funções:
# script.ps1
. .\MinhasFuncoes.ps1 # Carrega as funções
$users = Get-UserList
foreach ($user in $users) {
if (Test-UserActive -UserName $user) {
Set-UserStatus -UserName $user -Status "Active"
}
}
Além disso, funções podem ser colocadas em módulos (.psm1) para facilitar a distribuição e o carregamento automático. Módulos são a forma recomendada de empacotar código reutilizável.
Outra consideração: funções devem ser projetadas para aceitar entrada do pipeline. Isso é feito definindo parâmetros com ValueFromPipeline ou ValueFromPipelineByPropertyName. Isso torna a função mais flexível e integrada ao pipeline do PowerShell.
Idiomático
Escrever código idiomático significa usar os padrões e construções que são naturais ao PowerShell, em vez de traduzir hábitos de outras linguagens. Isso inclui o uso de cmdlets nativos em vez de loops complexos, o uso de pipeline para encadear comandos, e a adoção de convenções como usar $PSItem (ou $_) em blocos de script.
Por exemplo, em vez de usar um loop foreach para filtrar uma lista, você pode usar Where-Object:
# Não idiomático
$results = @()
foreach ($item in $items) {
if ($item.Status -eq "Active") {
$results += $item
}
}
# Idiomático
$results = $items | Where-Object { $_.Status -eq "Active" }
Outro exemplo: em vez de usar Write-Host para saída, use Write-Output ou simplesmente o objeto. Em vez de usar Format-Table dentro de uma função, deixe que o usuário formate a saída como preferir.
Também é idiomático usar try/catch/finally para tratamento de erros, e usar -ErrorAction para controlar como os erros são tratados. Além disso, use splatting para passar parâmetros a cmdlets, em vez de listas longas de argumentos:
$params = @{
Path = "C:\temp"
Recurse = $true
Filter = "*.log"
}
Get-ChildItem @params
Escrever código idiomático não é apenas uma questão de estilo, mas também de eficiência e legibilidade. O PowerShell é uma linguagem orientada a objetos e pipeline, então aproveite esses recursos.
Boas práticas adicionais
Aqui estão algumas outras práticas que complementam os tópicos anteriores:
- Use
Write-Verbosepara informações detalhadas eWrite-Debugpara depuração. - Documente suas funções com comentários baseados em ajuda (
.SYNOPSIS,.DESCRIPTION,.PARAMETER, etc.) para queGet-Helpfuncione. - Teste seu código com Pester, o framework de testes do PowerShell.
- Use o PSScriptAnalyzer para verificar problemas de estilo e possíveis erros.
- Prefira usar
Set-StrictModepara detectar referências a propriedades inexistentes.
Referências
- Verbos aprovados para comandos do PowerShell
- Funções no PowerShell
- Scripts no PowerShell
- Pipeline no PowerShell
- PSScriptAnalyzer
- Tudo sobre PSCustomObject
Exercícios
- Escreva uma função chamada
Get-FileInfoque aceite um caminho de arquivo e retorne um objeto com propriedadesName,Size,LastWriteTimeeFullName. Use um verbo aprovado e emita um objeto, não texto. - Converta o seguinte loop para usar o pipeline e
Where-Object:$result = @(); foreach ($n in 1..100) { if ($n % 2 -eq 0) { $result += $n } } - Explique por que usar
Write-Hostpara emitir dados é considerado uma má prática e o que você deve usar em seu lugar. - Crie um script que use uma função para obter todos os processos com mais de 100 MB de memória (WorkingSet) e exporte os nomes e IDs para um arquivo CSV.
- Reescreva a seguinte função para seguir as boas práticas (usar verbo aprovado, emitir objeto, e usar splatting se aplicável):
function getstuff { param($a, $b) Write-Host "A: $a B: $b" }
function Get-FileInfo {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$file = Get-Item -Path $Path -ErrorAction Stop
[PSCustomObject]@{
Name = $file.Name
Size = $file.Length
LastWriteTime = $file.LastWriteTime
FullName = $file.FullName
}
}
$result = 1..100 | Where-Object { $_ % 2 -eq 0 }
Usar Write-Host para emitir dados é ruim porque ele escreve diretamente no console e não envia objetos para o pipeline, impossibilitando que a saída seja capturada, formatada ou processada. Em vez disso, deve-se emitir objetos (usando Write-Output ou simplesmente colocando o objeto no fluxo de saída) para que o comando seja composto e reutilizável.
function Get-HighMemoryProcess {
[CmdletBinding()]
param(
[int]$MinWorkingSetMB = 100
)
$minBytes = $MinWorkingSetMB * 1MB
Get-Process | Where-Object { $_.WorkingSet64 -gt $minBytes } |
Select-Object ProcessName, Id
}
$processes = Get-HighMemoryProcess -MinWorkingSetMB 100
$processes | Export-Csv -Path "processos.csv" -NoTypeInformation
function Get-Stuff {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$A,
[Parameter(Mandatory = $true)]
[string]$B
)
[PSCustomObject]@{
A = $A
B = $B
}
}