Como criar módulos PowerShell reutilizáveis para o time
1. Fundamentos de Módulos PowerShell
Módulos PowerShell são pacotes que contêm funções, variáveis e recursos relacionados, organizados em uma estrutura padronizada. Diferentemente de scripts avulsos, módulos oferecem:
- Encapsulamento: funções internas ficam isoladas do escopo global
- Versionamento: controle semântico de versões
- Distribuição: facilidade para compartilhar com o time
A estrutura básica de um módulo consiste em:
MeuModulo/
├── MeuModulo.psm1 # Script do módulo (código principal)
├── MeuModulo.psd1 # Manifesto (metadados e configurações)
├── pt-BR/ # Pastas de recursos localizados (opcional)
└── en-US/
Para carregar um módulo:
# Importar módulo do diretório atual
Import-Module .\MeuModulo.psd1 -Force
# Verificar módulos carregados
Get-Module
# Remover módulo da sessão
Remove-Module MeuModulo
A diferença fundamental entre scripts e módulos está no escopo: módulos criam um contexto isolado, evitando poluição do namespace global.
2. Criando o Manifesto do Módulo (.psd1)
O manifesto é o coração do módulo. Use New-ModuleManifest para gerar um template:
New-ModuleManifest -Path .\MeuModulo\MeuModulo.psd1 `
-RootModule MeuModulo.psm1 `
-ModuleVersion "1.0.0" `
-Author "Seu Nome" `
-CompanyName "Sua Empresa" `
-Description "Módulo para automação de infraestrutura" `
-Guid "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Exemplo completo de manifesto editado manualmente:
@{
RootModule = 'MeuModulo.psm1'
ModuleVersion = '1.0.0'
GUID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
Author = 'Time de DevOps'
CompanyName = 'Empresa XYZ'
Copyright = '(c) 2025 Empresa XYZ. Todos os direitos reservados.'
Description = 'Módulo reutilizável para gerenciamento de servidores'
# Dependências
RequiredModules = @(
@{ModuleName = 'Pester'; ModuleVersion = '5.0.0'}
@{ModuleName = 'PSWriteHTML'; ModuleVersion = '1.0.0'}
)
RequiredAssemblies = @('System.Management.Automation')
# Exportação controlada
FunctionsToExport = @('Get-ServerInfo', 'Set-ServerConfig', 'New-Report')
CmdletsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
PrivateData = @{
PSData = @{
Tags = @('infraestrutura', 'monitoramento', 'devops')
ProjectUri = 'https://github.com/empresa/meu-modulo'
LicenseUri = 'https://opensource.org/licenses/MIT'
}
}
}
3. Estruturando Funções Modulares
A nomenclatura deve seguir os verbos aprovados pelo PowerShell (Get-, Set-, New-, Remove-, Test-) com sufixos de domínio específicos.
Exemplo de função bem estruturada:
<#
.SYNOPSIS
Obtém informações detalhadas de um servidor remoto.
.DESCRIPTION
Esta função consulta WMI e WinRM para retornar CPU, memória e disco.
Suporta múltiplos servidores e pipeline.
.PARAMETER ComputerName
Nome ou IP do servidor. Aceita múltiplos valores via pipeline.
.PARAMETER Credential
Credencial alternativa para conexão remota.
.EXAMPLE
Get-ServerInfo -ComputerName "SRV001"
.EXAMPLE
"SRV001", "SRV002" | Get-ServerInfo -Credential (Get-Credential)
#>
function Get-ServerInfo {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[ValidateNotNullOrEmpty()]
[string[]]$ComputerName,
[Parameter(Mandatory=$false)]
[System.Management.Automation.PSCredential]$Credential
)
begin {
Write-Verbose "Iniciando consulta de servidores..."
$results = [System.Collections.ArrayList]::new()
}
process {
foreach ($computer in $ComputerName) {
try {
Write-Verbose "Consultando servidor: $computer"
$session = New-CimSession -ComputerName $computer -Credential $Credential -ErrorAction Stop
$cpu = Get-CimInstance -CimSession $session -ClassName Win32_Processor
$memory = Get-CimInstance -CimSession $session -ClassName Win32_OperatingSystem
[void]$results.Add([PSCustomObject]@{
ComputerName = $computer
CPU = "{0}% - {1}" -f $cpu.LoadPercentage, $cpu.Name
MemoryGB = "{0:N2} GB / {1:N2} GB" -f
(($memory.TotalVisibleMemorySize - $memory.FreePhysicalMemory) / 1MB),
($memory.TotalVisibleMemorySize / 1MB)
Timestamp = Get-Date
})
Remove-CimSession -CimSession $session
}
catch {
Write-Error "Falha ao consultar $computer : $_"
}
}
}
end {
Write-Verbose "Consulta concluída. Retornando $($results.Count) resultados."
return $results
}
}
4. Práticas Avançadas de Reutilização
Organize funções internas (privadas) separadas das públicas:
# Estrutura de pastas do módulo
MeuModulo/
├── Public/ # Funções exportadas
│ ├── Get-ServerInfo.ps1
│ └── New-Report.ps1
├── Private/ # Funções internas
│ ├── Invoke-WmiQuery.ps1
│ └── Format-Output.ps1
└── MeuModulo.psm1 # Carrega tudo dinamicamente
No arquivo .psm1, use carregamento dinâmico:
# Carrega funções privadas primeiro
Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 | ForEach-Object {
. $_.FullName
}
# Depois carrega funções públicas
Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 | ForEach-Object {
. $_.FullName
}
# Exporta apenas as funções públicas
Export-ModuleMember -Function (Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 |
ForEach-Object { [System.IO.Path]::GetFileNameWithoutExtension($_.Name) })
Implemente logging estruturado:
function Invoke-MaintenanceTask {
[CmdletBinding()]
param([string]$TaskName)
Write-Information "Iniciando tarefa: $TaskName" -InformationAction Continue
try {
# Lógica principal
Write-Verbose "Executando etapa 1..."
Write-Debug "DEBUG: Variável interna = $internalVar"
# Resultado
Write-Information "Tarefa concluída com sucesso" -InformationAction Continue
}
catch {
Write-Warning "Falha na tarefa $TaskName : $_"
throw
}
}
5. Gerenciamento de Erros e Tratamento de Exceções
Implemente tratamento robusto com padrões consistentes:
function Set-ServerConfig {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Connection $_ -Quiet -Count 1})]
[string]$ComputerName,
[Parameter(Mandatory=$true)]
[ValidateSet('Production', 'Staging', 'Development')]
[string]$Environment
)
$ErrorActionPreference = 'Stop'
try {
Write-Verbose "Configurando ambiente $Environment em $ComputerName"
# Operação principal
Invoke-Command -ComputerName $ComputerName -ScriptBlock {
param($env)
# Lógica de configuração
} -ArgumentList $Environment
return [PSCustomObject]@{
ComputerName = $ComputerName
Environment = $Environment
Status = 'Configured'
Timestamp = Get-Date
}
}
catch [System.UnauthorizedAccessException] {
$errorRecord = [System.Management.Automation.ErrorRecord]::new(
$_,
'AccessDenied',
[System.Management.Automation.ErrorCategory]::PermissionDenied,
$ComputerName
)
Write-Error $errorRecord
}
catch {
Write-Error "Erro inesperado em $ComputerName : $_"
throw
}
finally {
Write-Verbose "Finalizando configuração de $ComputerName"
}
}
6. Versionamento e Distribuição para o Time
Adote versionamento semântico no manifesto:
# Exemplo de changelog (CHANGELOG.md)
# 1.1.0 - 2025-03-15
# - Adicionado suporte a Credential
# - Corrigido bug na consulta de memória
# - Melhorado logging com Write-Information
# Script de bootstrap para instalação
$moduleName = "MeuModulo"
$repoUrl = "https://nuget.empresa.com/api/v2"
if (-not (Get-Module -ListAvailable -Name $moduleName)) {
Register-PSRepository -Name "EmpresaRepo" -SourceLocation $repoUrl -InstallationPolicy Trusted
Install-Module -Name $moduleName -Repository "EmpresaRepo" -Scope CurrentUser
}
Import-Module $moduleName -Force
Para distribuição via pasta de rede:
# Publicar módulo
$destination = "\\fileserver\modules\$moduleName"
Copy-Item -Path ".\$moduleName" -Destination $destination -Recurse -Force
# Adicionar ao PSModulePath
$env:PSModulePath += ";\\fileserver\modules"
7. Testes e Validação Contínua
Crie testes unitários com Pester:
# Tests\Get-ServerInfo.Tests.ps1
BeforeAll {
$modulePath = Join-Path $PSScriptRoot '..\MeuModulo'
Import-Module $modulePath -Force
}
Describe 'Get-ServerInfo' {
Context 'Parâmetros' {
It 'Deve aceitar pipeline' {
{ 'SRV001' | Get-ServerInfo } | Should -Not -Throw
}
It 'Deve rejeitar ComputerName vazio' {
{ Get-ServerInfo -ComputerName '' } | Should -Throw
}
}
Context 'Funcionalidade' {
It 'Deve retornar objeto com propriedades corretas' {
Mock -CommandName New-CimSession -MockWith { [PSCustomObject]@{Id=1} }
Mock -CommandName Get-CimInstance -MockWith {
[PSCustomObject]@{LoadPercentage=25; Name='Intel Xeon'}
}
$result = Get-ServerInfo -ComputerName 'localhost'
$result | Should -HaveProperty 'ComputerName'
$result | Should -HaveProperty 'CPU'
$result | Should -HaveProperty 'MemoryGB'
}
}
}
Execute testes localmente ou em CI/CD:
# Executar testes
Invoke-Pester -Path .\Tests\ -Output Detailed
# Integração com GitHub Actions
# .github/workflows/test.yml
# name: Test Module
# on: [push, pull_request]
# jobs:
# test:
# runs-on: windows-latest
# steps:
# - uses: actions/checkout@v2
# - name: Run Pester tests
# shell: pwsh
# run: |
# Install-Module Pester -Force -SkipPublisherCheck
# Invoke-Pester -Path .\Tests\ -Output Detailed -PassThru
Referências
- Documentação oficial de módulos PowerShell — Guia completo sobre estrutura, criação e gerenciamento de módulos PowerShell pela Microsoft.
- Pester: Framework de testes para PowerShell — Tutorial oficial do Pester para criação de testes unitários e mocking em módulos PowerShell.
- Boas práticas para funções PowerShell — Documentação da Microsoft sobre nomenclatura, parâmetros e documentação de funções.
- Versionamento semântico para módulos PowerShell — Guia oficial sobre como versionar módulos no PowerShell Gallery e repositórios privados.
- PowerShell Best Practices and Style Guide — Guia da comunidade PowerShell com padrões de codificação, estrutura de módulos e exemplos práticos.
- Criando módulos reutilizáveis com PowerShell — Artigo do PowerShell.org com dicas avançadas de modularização e distribuição para times.