Projeto: criando um módulo reutilizável
LidoNesta aula, você aprenderá a criar um módulo PowerShell reutilizável do zero, cobrindo estrutura de pastas, funções exportadas, documentação de ajuda e testes básicos. O projeto prático consolidará boas práticas de modularização, versionamento e distribuição.
Nesta aula, vamos construir um módulo PowerShell completo e reutilizável, aplicando as melhores práticas de estruturação, documentação e testes. Um módulo bem projetado não apenas organiza seu código, mas também facilita a manutenção, o compartilhamento e a automação de tarefas repetitivas. Você verá na prática como transformar funções soltas em um pacote coeso, pronto para ser distribuído e usado em diversos projetos.
O projeto que criaremos será um módulo chamado MeuModulo, com funções utilitárias para manipulação de arquivos e logs. Ao longo da aula, exploraremos cada etapa: desde a estrutura de pastas até a validação com testes automatizados. Ao final, você terá um módulo funcional e um roteiro claro para criar seus próprios módulos.
Estrutura
A estrutura de um módulo PowerShell é fundamental para sua reutilização e manutenção. Um módulo típico possui uma pasta raiz com o nome do módulo, contendo um manifesto (.psd1) e um ou mais arquivos de script (.psm1). A organização interna pode ser simples ou escalável, dependendo da complexidade.
Para nosso projeto, adotaremos uma estrutura modular que separa funções em arquivos individuais dentro de uma pasta Functions. Isso facilita a localização de código e permite que cada função seja desenvolvida e testada de forma isolada. A estrutura final será:
MeuModulo/
├── MeuModulo.psd1
├── MeuModulo.psm1
├── Functions/
│ ├── Get-ArquivoInfo.ps1
│ ├── Write-Log.ps1
│ └── New-Pasta.ps1
└── Tests/
└── MeuModulo.Tests.ps1
O manifesto .psd1 define metadados, versão, autor e as funções exportadas. O .psm1 é o script principal que carrega as funções. Vamos criar o manifesto com o cmdlet New-ModuleManifest e depois editá-lo para incluir as funções exportadas.
New-ModuleManifest -Path .\MeuModulo\MeuModulo.psd1 -RootModule MeuModulo.psm1 -ModuleVersion "1.0.0" -Author "Seu Nome" -Description "Módulo de utilitários para automação"
O arquivo .psm1 deve carregar os arquivos de funções. Podemos usar Get-ChildItem para importar dinamicamente. Exemplo:
# MeuModulo.psm1
$functionsPath = Join-Path $PSScriptRoot 'Functions'
Get-ChildItem -Path $functionsPath -Filter '*.ps1' | ForEach-Object {
. $_.FullName
}
Dessa forma, todas as funções definidas nos arquivos da pasta Functions estarão disponíveis no módulo. A estrutura modular facilita a adição de novas funções sem alterar o arquivo principal.
Funções exportadas
No PowerShell, as funções definidas em um módulo não são automaticamente expostas ao usuário. Precisamos exportá-las explicitamente no manifesto ou no próprio script. A prática recomendada é listar as funções no manifesto para controlar a superfície pública do módulo.
No manifesto .psd1, a chave FunctionsToExport controla isso. Podemos especificar uma lista ou usar * para exportar todas, mas é melhor ser explícito. Vamos editar o manifesto para exportar nossas três funções:
FunctionsToExport = @('Get-ArquivoInfo', 'Write-Log', 'New-Pasta')
Agora, vamos implementar as funções. Exemplo de Get-ArquivoInfo:
function Get-ArquivoInfo {
param(
[Parameter(Mandatory=$true)]
[string]$Path
)
if (Test-Path $Path) {
Get-Item $Path | Select-Object Name, Length, LastWriteTime
} else {
Write-Warning "Arquivo não encontrado: $Path"
}
}
E Write-Log:
function Write-Log {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[string]$LogPath = ".\app.log"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path $LogPath -Value "[$timestamp] $Message"
}
Além disso, New-Pasta:
function New-Pasta {
param(
[Parameter(Mandatory=$true)]
[string]$Path
)
if (-not (Test-Path $Path)) {
New-Item -ItemType Directory -Path $Path -Force
} else {
Write-Warning "A pasta $Path já existe."
}
}
Essas funções são simples, mas demonstram o padrão: parâmetros com validação, uso de cmdlets nativos e mensagens de aviso. Ao exportar explicitamente, evitamos expor funções internas auxiliares que possam poluir o namespace do usuário.
Ajuda
Documentação é essencial para qualquer módulo reutilizável. O PowerShell suporta comentários de ajuda baseados em # .SYNOPSIS, # .DESCRIPTION, # .PARAMETER e # .EXAMPLE. Esses comentários são exibidos com Get-Help e podem ser convertidos em arquivos de ajuda externos, mas o mais simples é incorporá-los nas funções.
Vamos adicionar ajuda à função Get-ArquivoInfo:
function Get-ArquivoInfo {
<#
.SYNOPSIS
Obtém informações de um arquivo.
.DESCRIPTION
Retorna nome, tamanho e data de modificação do arquivo especificado.
.PARAMETER Path
Caminho completo do arquivo.
.EXAMPLE
Get-ArquivoInfo -Path "C:\temp\arquivo.txt"
#>
param(
[Parameter(Mandatory=$true)]
[string]$Path
)
# ...
}
Da mesma forma, adicione ajuda a Write-Log e New-Pasta. Após importar o módulo, o usuário pode executar Get-Help Get-ArquivoInfo e ver a documentação. Isso melhora a experiência e reduz a curva de aprendizado.
Além dos comentários, podemos gerar um arquivo de ajuda externo (.xml) usando o cmdlet New-HelpFile (disponível no módulo Plaster ou manualmente), mas para este projeto os comentários são suficientes.
Testes
Testes automatizados garantem que o módulo se comporte como esperado e facilitam a detecção de regressões. O PowerShell não possui uma estrutura de testes nativa, mas podemos usar o módulo Pester, que é o padrão de fato. Instale o Pester com Install-Module Pester -Force.
Vamos criar um arquivo de teste MeuModulo.Tests.ps1 na pasta Tests. O Pester usa blocos Describe, Context e It para definir os testes. Exemplo:
# Tests\MeuModulo.Tests.ps1
BeforeAll {
Import-Module .\MeuModulo\MeuModulo.psd1 -Force
}
Describe "Get-ArquivoInfo" {
It "Retorna informações de um arquivo existente" {
$tempFile = New-TemporaryFile
$result = Get-ArquivoInfo -Path $tempFile.FullName
$result.Name | Should -Be $tempFile.Name
Remove-Item $tempFile
}
It "Emite aviso para arquivo inexistente" {
{ Get-ArquivoInfo -Path "C:\nao\existe.txt" } | Should -Throw
}
}
Describe "Write-Log" {
It "Adiciona mensagem ao arquivo de log" {
$logFile = Join-Path $TestDrive "test.log"
Write-Log -Message "Teste" -LogPath $logFile
(Get-Content $logFile).Count | Should -Be 1
}
}
Executamos os testes com Invoke-Pester:
Invoke-Pester -Script .\Tests\MeuModulo.Tests.ps1
O Pester fornece saída colorida indicando quais testes passaram ou falharam. Escrever testes para todas as funções é uma boa prática, e você pode expandir este exemplo para cobrir mais cenários, como parâmetros inválidos e casos de borda.
Boas práticas e observações finais
Ao criar módulos, considere as seguintes boas práticas:
- Use nomes de funções no padrão Verbo-Substantivo (aprovados pelo PowerShell).
- Exporte apenas o que for necessário, mantendo funções auxiliares internas.
- Documente com comentários de ajuda e, se possível, gere arquivos de ajuda externos.
- Estruture o código em pastas para facilitar a navegação e manutenção.
- Escreva testes com Pester para garantir a qualidade.
- Versionize o módulo e publique em um repositório (ex.: PowerShell Gallery) para compartilhar.
Lembre-se de que um módulo bem projetado é um investimento: você economizará tempo no futuro ao reutilizar código confiável e documentado. Experimente adicionar novas funções e testes ao seu módulo para solidificar o aprendizado.
Referências
- Sobre Módulos - Microsoft Learn
- Como escrever um manifesto de módulo
- Escrevendo ajuda para scripts e funções
- Pester - Quick Start
- Verbos aprovados para comandos PowerShell
- PowerShell Gallery
Exercícios
- Exercício 1: Crie a estrutura de pastas para um módulo chamado
MeuModulocom as pastasFunctionseTests. Dentro deFunctions, crie um arquivo vazio para uma função chamadaGet-Saudacao. Use comandos PowerShell para criar a estrutura. - Exercício 2: No manifesto do módulo, configure
FunctionsToExportpara exportar apenas as funçõesGet-ArquivoInfoeWrite-Log. Suponha que o manifesto já exista. - Exercício 3: Adicione comentários de ajuda (SYNOPSIS, DESCRIPTION, PARAMETER, EXAMPLE) à função
Write-Log. - Exercício 4: Escreva um teste Pester para a função
New-Pastaque verifica se a pasta é criada e se um aviso é emitido quando a pasta já existe. - Exercício 5: Execute os testes do módulo com Pester e explique o que a saída indica.
New-Item -Path .\MeuModulo\Functions -ItemType Directory -Force
New-Item -Path .\MeuModulo\Tests -ItemType Directory -Force
New-Item -Path .\MeuModulo\Functions\Get-Saudacao.ps1 -ItemType File -Force
.psd1 e altere a linha:FunctionsToExport = @('Get-ArquivoInfo', 'Write-Log')
function Write-Log {
<#
.SYNOPSIS
Adiciona uma mensagem a um arquivo de log.
.DESCRIPTION
Escreve uma mensagem com timestamp no arquivo especificado.
.PARAMETER Message
Texto da mensagem a ser registrada.
.PARAMETER LogPath
Caminho do arquivo de log. Padrão é .\app.log.
.EXAMPLE
Write-Log -Message "Início do processo" -LogPath "C:\logs\app.log"
#>
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[string]$LogPath = ".\app.log"
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content -Path $LogPath -Value "[$timestamp] $Message"
}
Describe "New-Pasta" {
It "Cria uma nova pasta" {
$testPath = Join-Path $TestDrive "nova"
New-Pasta -Path $testPath
Test-Path $testPath | Should -Be $true
}
It "Emite aviso se a pasta já existe" {
$testPath = Join-Path $TestDrive "existente"
New-Item -ItemType Directory -Path $testPath -Force | Out-Null
{ New-Pasta -Path $testPath } | Should -Throw
}
}
Invoke-Pester -Script .\Tests\MeuModulo.Tests.ps1. A saída mostra o número de testes executados, quantos passaram e quantos falharam, além de detalhes de cada falha. Uma saída verde indica sucesso, enquanto vermelho indica problemas. Isso ajuda a validar o comportamento do módulo.