Funções avançadas (cmdlet-style)
Aula sobre criação de funções avançadas no PowerShell que se comportam como cmdlets nativos. Aborda o uso do atributo [CmdletBinding()], parâmetros de pipeline (ValueFromPipeline) e a estrutura de blocos Begin/Process/End.
Esta aula ensina como transformar funções PowerShell em funções avançadas com comportamento idêntico ao de cmdlets nativos. Funções avançadas podem usar todos os recursos de cmdlets, como parâmetros nomeados, suporte a pipeline, e acesso a variáveis automáticas como $PSBoundParameters. O segredo está no atributo [CmdletBinding()] e na estrutura de blocos Begin, Process e End.
[CmdletBinding()]
O atributo [CmdletBinding()] é colocado antes do parâmetro param() em uma função. Ele adiciona funcionalidades de cmdlet, como parâmetros comuns (-Verbose, -Debug, -ErrorAction, etc.), suporte a pipeline e validação automática de parâmetros. Sem ele, a função é uma função simples, sem esses recursos.
Exemplo básico:
function Get-Info {
[CmdletBinding()]
param(
[string]$Name
)
Write-Host "Nome: $Name"
}
Agora Get-Info aceita -Verbose, -Debug e outros parâmetros comuns automaticamente. Você pode também especificar opções como SupportsShouldProcess (para suporte a -WhatIf e -Confirm) e ConfirmImpact.
Parâmetros do pipeline
Para que uma função receba dados do pipeline, os parâmetros precisam ser decorados com atributos de pipeline. O mais comum é [Parameter(ValueFromPipeline)], que permite que o parâmetro receba valores diretamente do pipeline. Exemplo:
function Add-One {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[int]$Number
)
Write-Output ($Number + 1)
}
Agora 1,2,3 | Add-One retorna 2,3,4. Você também pode usar ValueFromPipelineByPropertyName para mapear propriedades de objetos do pipeline para parâmetros com o mesmo nome.
ValueFromPipeline
O atributo ValueFromPipeline permite que o parâmetro receba o valor do objeto inteiro que está passando pelo pipeline. Se você precisa de mais controle, pode usar ValueFromPipelineByPropertyName, que mapeia uma propriedade do objeto para o parâmetro, se o nome coincidir.
Exemplo com propriedade:
function Get-ProcessInfo {
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Name
)
Write-Output "Processo: $Name"
}
# Agora Get-Process | Get-ProcessInfo funcionará, pois objetos de processo têm propriedade 'Name'
É possível combinar ambos: ValueFromPipeline para receber o objeto inteiro e ValueFromPipelineByPropertyName para propriedades específicas.
Begin/Process/End
Funções avançadas podem ter três blocos especiais: Begin, Process e End. Eles definem o comportamento da função quando usada no pipeline.
- Begin: Executado uma vez, antes de receber qualquer objeto do pipeline. Ideal para inicializações.
- Process: Executado para cada objeto que chega pelo pipeline. Se a função não for usada no pipeline, executa uma vez.
- End: Executado uma vez, após todos os objetos do pipeline serem processados. Ideal para finalizações.
Exemplo completo:
function Measure-Data {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[int]$Number
)
Begin {
$sum = 0
$count = 0
Write-Verbose "Iniciando medição"
}
Process {
$sum += $Number
$count++
Write-Debug "Processando número $Number"
}
End {
$average = if ($count -gt 0) { $sum / $count } else { 0 }
Write-Output "Soma: $sum, Média: $average"
}
}
# Uso: 1..5 | Measure-Data -Verbose -Debug
No exemplo, Begin inicializa variáveis, Process acumula valores e End calcula e retorna o resultado. O uso de -Verbose e -Debug mostra mensagens de diagnóstico.
Boas práticas
- Sempre use
[CmdletBinding()]em funções que serão expostas como cmdlets. - Use
Write-Outputpara retornar objetos, evitandoWrite-Host(a menos que seja para exibição direta). - Documente parâmetros com
[Parameter(Mandatory)]quando necessário. - Teste funções com pipeline usando
1..10 | SuaFuncao. - Prefira
ValueFromPipelineByPropertyNamepara aceitar objetos complexos.
Referências
- Everything About Functions - Microsoft Docs
- About Functions Advanced - Microsoft Docs
- About Functions Advanced Parameters - Microsoft Docs
- About Pipelines - Microsoft Docs
- About Functions CmdletBindingAttribute - Microsoft Docs
Exercícios
-
Crie uma função avançada chamada
Get-Squaredque aceite um número via pipeline (ValueFromPipeline) e retorne o quadrado desse número. Use[CmdletBinding()].✓ Resposta:function Get-Squared { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [int]$Number ) Process { Write-Output ($Number * $Number) } } -
Crie uma função
Get-ProcessMemoryque useValueFromPipelineByPropertyNamepara aceitar objetos com propriedade 'Name' e exiba o nome e a memória (WS) do processo. (Dica: use Get-Process para testar)✓ Resposta:function Get-ProcessMemory { [CmdletBinding()] param( [Parameter(ValueFromPipelineByPropertyName)] [string]$Name ) Process { $proc = Get-Process -Name $Name -ErrorAction SilentlyContinue if ($proc) { Write-Output "$($proc.Name): $($proc.WorkingSet) bytes" } } } -
Escreva uma função
Invoke-Calculatorque aceite dois números via pipeline (como objetos com propriedades A e B) e uma operação (Add, Subtract, Multiply, Divide) como parâmetro nomeado. UseValueFromPipelineByPropertyNamepara A e B.✓ Resposta:function Invoke-Calculator { [CmdletBinding()] param( [Parameter(ValueFromPipelineByPropertyName)] [int]$A, [Parameter(ValueFromPipelineByPropertyName)] [int]$B, [ValidateSet('Add','Subtract','Multiply','Divide')] [string]$Operation = 'Add' ) Process { switch ($Operation) { 'Add' { $result = $A + $B } 'Subtract' { $result = $A - $B } 'Multiply' { $result = $A * $B } 'Divide' { $result = if ($B -ne 0) { $A / $B } else { 'Erro: divisão por zero' } } } Write-Output $result } } # Teste: [PSCustomObject]@{A=10; B=5} | Invoke-Calculator -Operation Multiply -
Crie uma função
Get-FileStatsque aceite caminhos de arquivo via pipeline (ValueFromPipeline) e, usando Begin/Process/End, calcule o tamanho total e o número de arquivos processados. Retorne um objeto com TotalSize e Count.✓ Resposta:function Get-FileStats { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [string]$Path ) Begin { $totalSize = 0 $count = 0 } Process { if (Test-Path $Path -PathType Leaf) { $file = Get-Item $Path $totalSize += $file.Length $count++ } else { Write-Warning "$Path não é um arquivo" } } End { Write-Output ([PSCustomObject]@{ TotalSize = $totalSize Count = $count }) } } -
Modifique a função do exercício 1 para usar o bloco Process explicitamente e adicione suporte a
-Verbosemostrando o número sendo processado.✓ Resposta:function Get-Squared { [CmdletBinding()] param( [Parameter(ValueFromPipeline)] [int]$Number ) Process { Write-Verbose "Processando número $Number" Write-Output ($Number * $Number) } }