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-Output para retornar objetos, evitando Write-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 ValueFromPipelineByPropertyName para aceitar objetos complexos.

Referências

Exercícios

  1. Crie uma função avançada chamada Get-Squared que 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)
        }
    }
  2. Crie uma função Get-ProcessMemory que use ValueFromPipelineByPropertyName para 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"
            }
        }
    }
  3. Escreva uma função Invoke-Calculator que 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. Use ValueFromPipelineByPropertyName para 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
  4. Crie uma função Get-FileStats que 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
            })
        }
    }
  5. Modifique a função do exercício 1 para usar o bloco Process explicitamente e adicione suporte a -Verbose mostrando 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)
        }
    }