PowerShell 7 para Sysadmins: Quais os 15 Scripts de Automação Essenciais para 2026?

Duarte Spínola  | 

O PowerShell 7 estabilizou-se como a shell de automação padrão para administradores Windows e multiplataforma. Com a versão 7.4 em suporte de longo prazo (LTS) e a 7.5 já disponível, há uma oportunidade clara para substituir rotinas legadas baseadas em Windows PowerShell 5.1 por scripts modernos que tiram partido de paralelismo nativo, cmdlets actualizados e tratamento de erros robusto. Este artigo cobre a instalação, as diferenças face à versão 5.1, questões de compatibilidade e 15 scripts prontos a adaptar para tarefas diárias de sysadmin — desde inventário de hardware até à exportação de relatórios HTML consolidados. As referências documentais seguem a documentação oficial Microsoft Learn e o repositório GitHub PowerShell.

1. Instalação do PowerShell 7.4+ em Windows

O PowerShell 7 coexiste com o Windows PowerShell 5.1 — não o substitui. O executável instala-se como pwsh.exe (vs powershell.exe para 5.1), pelo que podes executar ambos em paralelo sem conflitos (Microsoft Learn — Installing PowerShell on Windows).

Instalação via winget

O gestor de pacotes winget é o método recomendado em Windows 10/11 e Windows Server 2022+ (Microsoft Learn — winget):

winget install –id Microsoft.PowerShell –source winget

Para instalar uma versão específica (e.g. 7.4 LTS):

winget install –id Microsoft.PowerShell –version 7.4.7 –source winget

Instalação via MSI

Em ambientes sem winget ou para deployment automatizado, o MSI está disponível no GitHub PowerShell Releases:

$url = “https://github.com/PowerShell/PowerShell/releases/download/v7.4.7/PowerShell-7.4.7-win-x64.msi”
$dest = “$env:TEMP\PowerShell-7.4.7-win-x64.msi”
Invoke-WebRequest -Uri $url -OutFile $dest
msiexec /i $dest /qn ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 ENABLE_PSREMOTING=1 REGISTER_MANIFEST=1

Verificar a instalação após concluir:

pwsh -v
# PowerShell 7.4.7

2. Melhorias do PowerShell 7 vs Windows PowerShell 5.1

O PowerShell 7 introduz mudanças arquitecturais significativas face ao 5.1. A tabela seguinte resume as diferenças mais relevantes para sysadmins (Microsoft Learn — Migrating from Windows PowerShell 5.1 to PowerShell 7, Microsoft Learn — What’s New in PowerShell 7.4):

Característica Windows PowerShell 5.1 PowerShell 7.4+
Runtime .NET .NET Framework 4.x .NET 8 (LTS)
Paralelismo Não nativo ForEach-Object -Parallel
Operadores ternários Não condição ? valor1 : valor2
Pipeline chain Não && e `\ \ `
Null-coalescing Não ?? e ??=
Multiplataforma Windows apenas Windows, Linux, macOS
Performance Base (CLR legacy) Significativamente superior (.NET 8)
Suporte Manutenção (sem novas features) Activo (LTS 7.4, Current 7.5)
Módulos compatíveis Módulos Windows-only Maioria + módulos multiplataforma

A versão 7.5 trouxe melhorias adicionais em completar tabulações, tratamento de ANSI e novos cmdlets de diagnóstico.

3. Compatibilidade e Migração de Scripts Legados

A migração de scripts de 5.1 para 7 não é automática. A maioria dos scripts funciona sem alterações, mas há excepções conhecidas (Microsoft Learn — Writing Portable Modules):

Problema comum Causa Solução
Get-WmiObject indisponível Descontinuado em PS7 Substituir por Get-CimInstance
Módulos Windows-only Dependem de .NET Framework Verificar compatibilidade; usar módulos equivalentes
Invoke-WebRequest sem -UseBasicParsing IE Engine removido Já é o comportamento padrão em PS7
WMI remoting via DCOM Não suportado em PS7 Usar CIM remoting (New-CimSession)
Encoding por omissão PS7 usa UTF-8; PS5.1 usa ASCII/Codepage Verificar leitura/escrita de ficheiros
Paths com \ invertida Funciona em Windows mas não multiplataforma Usar Join-Path e Split-Path

Para verificar se um script é compatível antes de migrar, executa-o com pwsh num ambiente de staging e regista os warnings de módulos em falta:

pwsh -File .\script-legacy.ps1 -Verbose 2>&1 | Tee-Object -FilePath .\migration-log.txt

A compatibilidade multiplataforma é especialmente relevante se geres servidores Linux — o PowerShell 7 instala-se nativamente em Ubuntu, Debian, RHEL e outros (Microsoft Learn — Installing PowerShell on Linux).

4. Scripts de Inventário e Auditoria (1-5)

Os primeiros cinco scripts cobrem recolha de informação de sistema — a base de qualquer rotina de sysadmin.

Script 1 — Inventário de Hardware

Usa Get-ComputerInfo para consolidar informação de hardware num objecto estruturado (Microsoft Learn — Get-ComputerInfo):

try {
$info = Get-ComputerInfo -Property CsProcessors, CsTotalPhysicalMemory, CsModel, CsManufacturer, OsName, OsVersion
$inventario = [PSCustomObject]@{
Fabricante = $info.CsManufacturer
Modelo = $info.CsModel
Processador = $info.CsProcessors.Name -join ‘, ‘
MemoriaGB = [math]::Round($info.CsTotalPhysicalMemory / 1GB, 2)
SO = $info.OsName
VersaoSO = $info.OsVersion
DataRelatorio = (Get-Date).ToString(‘yyyy-MM-dd HH:mm’)
}
$inventario | Export-Csv -Path “.\inventario-hardware.csv” -NoTypeInformation -Encoding UTF8
Write-Output “Inventario exportado com sucesso.”
}
catch {
Write-Error “Falha ao obter informacao de hardware: $_”
}

Script 2 — Auditoria de Utilizadores Activos

Usa o módulo Microsoft.PowerShell.LocalAccounts para listar contas locais activas (Microsoft Learn — Get-LocalUser):

try {
$utilizadores = Get-LocalUser | Where-Object { $_.Enabled -eq $true } |
Select-Object Name, FullName, LastLogon, PasswordLastSet,
@{N=’DiasSemLogon’; E={ if ($_.LastLogon) { ((Get-Date) – $_.LastLogon).Days } else { ‘Nunca’ } }}

$utilizadores | Format-Table -AutoSize
$utilizadores | Export-Csv -Path “.\utilizadores-activos.csv” -NoTypeInformation -Encoding UTF8
Write-Output “Audit de utilizadores concluido: $($utilizadores.Count) contas activas.”
}
catch {
Write-Error “Erro na auditoria de utilizadores: $_”
}

Script 3 — Verificação de Espaço em Disco

Usa Get-Volume do módulo Storage para obter informações de volumes montados (Microsoft Learn — Get-Volume):

try {
$volumes = Get-Volume | Where-Object { $_.DriveLetter -and $_.Size -gt 0 } |
Select-Object DriveLetter, FileSystemLabel,
@{N=’TamanhoGB’; E={ [math]::Round($_.Size / 1GB, 2) }},
@{N=’LivreGB’; E={ [math]::Round($_.SizeRemaining / 1GB, 2) }},
@{N=’PercentagemLivre’; E={ [math]::Round(($_.SizeRemaining / $_.Size) * 100, 1) }}

$volumes | Format-Table -AutoSize

$criticos = $volumes | Where-Object { $_.PercentagemLivre -lt 10 }
if ($criticos) {
Write-Warning “Volumes com menos de 10% espaco livre: $($criticos.DriveLetter -join ‘, ‘)”
}
}
catch {
Write-Error “Erro ao verificar volumes: $_”
}

Script 4 — Backup de GPOs

Necessita do módulo GroupPolicy (disponível via RSAT). Faz backup de todas as GPOs para uma pasta datada (Microsoft Learn — Backup-Gpo):

$backupDir = “\\servidor\backups\gpo\$(Get-Date -Format ‘yyyy-MM-dd’)”

try {
if (-not (Test-Path $backupDir)) {
New-Item -Path $backupDir -ItemType Directory -Force | Out-Null
}

$gpos = Get-Gpo -All
$gpos | ForEach-Object -Parallel {
try {
Backup-Gpo -Guid $_.Id -Path $using:backupDir -Comment “Backup automatico $(Get-Date -Format ‘yyyy-MM-dd’)”
Write-Output “Backup OK: $($_.DisplayName)”
}
catch {
Write-Warning “Falha no backup da GPO $($_.DisplayName): $_”
}
} -ThrottleLimit 5

Write-Output “Backup de GPOs concluido: $($gpos.Count) politicas processadas.”
}
catch {
Write-Error “Erro no backup de GPOs: $_”
}

Script 5 — Relatório de Serviços Parados

Identifica serviços configurados para arranque automático que não estão em execução (Microsoft Learn — Get-Service):

try {
$servicosParados = Get-Service |
Where-Object { $_.StartType -eq ‘Automatic’ -and $_.Status -ne ‘Running’ } |
Select-Object Name, DisplayName, Status, StartType

if ($servicosParados) {
$servicosParados | Format-Table -AutoSize
$servicosParados | Export-Csv -Path “.\servicos-parados.csv” -NoTypeInformation -Encoding UTF8
Write-Warning “Encontrados $($servicosParados.Count) servicos automaticos parados.”
}
else {
Write-Output “Todos os servicos automaticos estao em execucao.”
}
}
catch {
Write-Error “Erro ao verificar servicos: $_”
}

5. Scripts de Manutenção e Segurança (6-10)

Os scripts 6 a 10 focam-se em manutenção do sistema, segurança e auditoria de acesso.

Script 6 — Auditoria de Patches Windows

Lista hotfixes instaladas e compara com as últimas actualizações esperadas (Microsoft Learn — Get-HotFix):

try {
$patches = Get-HotFix |
Sort-Object InstalledOn -Descending |
Select-Object HotFixID, Description,
@{N=’InstaladoEm’; E={ $_.InstalledOn.ToString(‘yyyy-MM-dd’) }},
InstalledBy

$patches | Format-Table -AutoSize
$patches | Export-Csv -Path “.\patches-windows.csv” -NoTypeInformation -Encoding UTF8

$ultimoPatch = $patches | Select-Object -First 1
$diasSemPatch = ((Get-Date) – [datetime]$ultimoPatch.InstaladoEm).Days
if ($diasSemPatch -gt 30) {
Write-Warning “Ultimo patch instalado ha $diasSemPatch dias. Verificar Windows Update.”
}
Write-Output “Audit de patches concluido: $($patches.Count) hotfixes instaladas.”
}
catch {
Write-Error “Erro na auditoria de patches: $_”
}

Script 7 — Limpeza de Ficheiros Temporários

Remove ficheiros das pastas temporárias do utilizador e do sistema, liberando espaço:

$caminhosTemp = @(
“$env:TEMP”,
“$env:WINDIR\Temp”,
“$env:WINDIR\SoftwareDistribution\Download”,
“$env:LOCALAPPDATA\Microsoft\Windows\Explorer”
)

$totalRemovido = 0

foreach ($caminho in $caminhosTemp) {
try {
if (Test-Path $caminho) {
$ficheiros = Get-ChildItem -Path $caminho -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-7) }

$tamanho = ($ficheiros | Measure-Object -Property Length -Sum).Sum
$totalRemovido += $tamanho

$ficheiros | Remove-Item -Force -ErrorAction SilentlyContinue
Write-Output “Limpo: $caminho — $([math]::Round($tamanho / 1MB, 2)) MB removidos.”
}
}
catch {
Write-Warning “Erro ao limpar $caminho : $_”
}
}

Write-Output “Total removido: $([math]::Round($totalRemovido / 1MB, 2)) MB.”

Script 8 — Relatório de Logs de Eventos

Extrai os últimos eventos de erro e aviso dos logs System e Application (Microsoft Learn — Get-EventLog):

try {
$logs = ‘System’, ‘Application’
$eventos = foreach ($log in $logs) {
Get-EventLog -LogName $log -EntryType Error, Warning -Newest 50 -ErrorAction SilentlyContinue |
Select-Object LogName, EntryType, Source, EventID,
@{N=’Hora’; E={ $_.TimeGenerated.ToString(‘yyyy-MM-dd HH:mm:ss’) }},
Message
}

$eventos | Group-Object LogName, EntryType |
Select-Object Name, Count | Format-Table -AutoSize

$eventos | Export-Csv -Path “.\eventos-erro-aviso.csv” -NoTypeInformation -Encoding UTF8
Write-Output “Relatorio de eventos concluido: $($eventos.Count) eventos (erro + aviso).”
}
catch {
Write-Error “Erro ao obter logs de eventos: $_”
}

Script 9 — Configuração de Firewall

Cria e audita regras de firewall usando o módulo NetSecurity (Microsoft Learn — Get-NetFirewallRule, Microsoft Learn — New-NetFirewallRule):

try {
# Auditar regras activas com portas abertas
$regras = Get-NetFirewallRule -Enabled True -Direction Inbound -Action Allow |
Where-Object { $_.Profile -match ‘Domain|Public’ } |
Select-Object DisplayName, Profile, Direction, Action

Write-Output “Regras inbound activas (Domain/Public): $($regras.Count)”

# Criar regra para permitir monitorização SNMP (exemplo)
$params = @{
DisplayName = “SNMP Monitor – UDP 161”
Direction = ‘Inbound’
Protocol = ‘UDP’
LocalPort = 161
Action = ‘Allow’
Profile = ‘Domain’
Enabled = $true
}
New-NetFirewallRule @params
Write-Output “Regra de firewall criada com sucesso.”

$regras | Export-Csv -Path “.\firewall-regras-activas.csv” -NoTypeInformation -Encoding UTF8
}
catch {
Write-Error “Erro na configuração de firewall: $_”
}

Script 10 — Auditoria de Sessões RDP

Lista sessões RDP activas e desconectadas usando Get-CimInstance para consultar Win32_LogonSession (Microsoft Learn — Get-CimInstance, Microsoft Learn — RDS Overview):

try {
$sessoes = Get-CimInstance -ClassName Win32_LogonSession |
Where-Object { $_.LogonType -in @(10, 7) } |
Select-Object LogonId, LogonType,
@{N=’Inicio’; E={ $_.StartTime.ToString(‘yyyy-MM-dd HH:mm:ss’) }},
@{N=’DuracaoHoras’; E={ [math]::Round(((Get-Date) – $_.StartTime).TotalHours, 2) }}

# Cruzar com utilizador via Win32_LoggedOnUser
$utilizadores = Get-CimInstance -ClassName Win32_LoggedOnUser |
ForEach-Object {
$user = [PSCustomObject]@{
LogonId = $_.Dependent.LogonId
Utilizador = $_.Antecedent.Name
Dominio = $_.Antecedent.Domain
}
$user
}

$relatorio = $sessoes | ForEach-Object {
$match = $utilizadores | Where-Object { $_.LogonId -eq $_.LogonId } | Select-Object -First 1
[PSCustomObject]@{
LogonId = $_.LogonId
Tipo = if ($_.LogonType -eq 10) { ‘RDP’ } else { ‘Interactive’ }
Inicio = $_.Inicio
DuracaoHoras = $_.DuracaoHoras
Utilizador = if ($match) { “$($match.Dominio)\$($match.Utilizador)” } else { ‘N/A’ }
}
}

$relatorio | Format-Table -AutoSize
$relatorio | Export-Csv -Path “.\sessoes-rdp.csv” -NoTypeInformation -Encoding UTF8
Write-Output “Audit de sessoes RDP concluido: $($relatorio.Count) sessoes.”
}
catch {
Write-Error “Erro na auditoria de sessoes RDP: $_”
}

6. Scripts de Certificados, Software e Rede (11-15)

Os últimos cinco scripts cobrem certificados, inventário de software, gestão de AD, conectividade e exportação consolidada.

Script 11 — Verificação de Certificados a Expirar

Lista certificados do arquivo local que expiram nos próximos 30 dias (Microsoft Learn — Get-Certificate):

try {
$limite = (Get-Date).AddDays(30)

$certificados = Get-ChildItem -Path Cert:\LocalMachine\My, Cert:\CurrentUser\My -ErrorAction SilentlyContinue |
Where-Object { $_.NotAfter -le $limite -and $_.NotAfter -ge (Get-Date) } |
Select-Object Subject,
@{N=’Emissor’; E={ $_.Issuer }},
@{N=’ExpiraEm’; E={ $_.NotAfter.ToString(‘yyyy-MM-dd’) }},
@{N=’DiasRestantes’; E={ ($_.NotAfter – (Get-Date)).Days }},
Thumbprint

if ($certificados) {
$certificados | Format-Table -AutoSize
$certificados | Export-Csv -Path “.\certificados-a-expirar.csv” -NoTypeInformation -Encoding UTF8
Write-Warning “$($certificados.Count) certificados expiram nos proximos 30 dias.”
}
else {
Write-Output “Nenhum certificado a expirar nos proximos 30 dias.”
}
}
catch {
Write-Error “Erro ao verificar certificados: $_”
}

Script 12 — Inventário de Software Instalado

Recolhe software instalado via registo e MSI, filtrando actualizações:

try {
$regPaths = @(
‘HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*’,
‘HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*’
)

$software = foreach ($path in $regPaths) {
Get-ItemProperty $path -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -and $_.DisplayName -notmatch ‘^KB\d+’ } |
Select-Object DisplayName, DisplayVersion, Publisher,
@{N=’DataInstalacao’; E={ if ($_.InstallDate) { [datetime]::ParseExact($_.InstallDate, ‘yyyyMMdd’, $null).ToString(‘yyyy-MM-dd’) } else { ‘N/A’ } }}
}

$software = $software | Sort-Object DisplayName -Unique
$software | Export-Csv -Path “.\inventario-software.csv” -NoTypeInformation -Encoding UTF8
Write-Output “Inventario de software concluido: $($software.Count) aplicacoes.”
}
catch {
Write-Error “Erro no inventario de software: $_”
}

Script 13 — Gestão de Grupos AD

Lista grupos do Active Directory com contagem de membros e identifica grupos vazios (Microsoft Learn — Get-ADGroup, Microsoft Learn — Get-ADGroupMember):

try {
$grupos = Get-ADGroup -Filter { GroupCategory -eq ‘Security’ } -Properties Description

$relatorio = $grupos | ForEach-Object -Parallel {
try {
$membros = (Get-ADGroupMember -Identity $_.DistinguishedName -ErrorAction SilentlyContinue).Count
[PSCustomObject]@{
Nome = $_.Name
Descricao = $_.Description
NumMembros = $membros
Vazio = if ($membros -eq 0) { ‘Sim’ } else { ‘Nao’ }
}
}
catch {
[PSCustomObject]@{
Nome = $_.Name
Descricao = $_.Description
NumMembros = ‘Erro’
Vazio = ‘N/A’
}
}
} -ThrottleLimit 10

$relatorio | Sort-Object NumMembros -Descending | Format-Table -AutoSize
$relatorio | Export-Csv -Path “.\grupos-ad-audit.csv” -NoTypeInformation -Encoding UTF8

$vazios = $relatorio | Where-Object { $_.Vazio -eq ‘Sim’ }
Write-Output “Audit de grupos AD: $($relatorio.Count) grupos, $($vazios.Count) vazios.”
}
catch {
Write-Error “Erro na gestao de grupos AD: $_”
}

Script 14 — Teste de Conectividade de Rede

Testa conectividade a múltiplos servidores em paralelo, verificando portas críticas:

$destinos = @(
@{ Nome = ‘DC-01’; Host = ‘192.168.1.10’; Portas = @(53, 88, 389, 445) }
@{ Nome = ‘FS-01’; Host = ‘192.168.1.20’; Portas = @(445, 139) }
@{ Nome = ‘RDS-01’; Host = ‘192.168.1.30’; Portas = @(3389, 443) }
@{ Nome = ‘Web-01’; Host = ‘10.0.0.50’; Portas = @(80, 443) }
)

$resultados = $destinos | ForEach-Object -Parallel {
$destino = $_
foreach ($porta in $destino.Portas) {
try {
$teste = Test-NetConnection -ComputerName $destino.Host -Port $porta -WarningAction SilentlyContinue
[PSCustomObject]@{
Nome = $destino.Nome
Host = $destino.Host
Porta = $porta
Resultado = if ($teste.TcpTestSucceeded) { ‘OK’ } else { ‘FALHA’ }
Latencia = $teste.PingReplyDetails.RoundtripTime
Timestamp = (Get-Date).ToString(‘yyyy-MM-dd HH:mm:ss’)
}
}
catch {
[PSCustomObject]@{
Nome = $destino.Nome
Host = $destino.Host
Porta = $porta
Resultado = ‘ERRO’
Latencia = -1
Timestamp = (Get-Date).ToString(‘yyyy-MM-dd HH:mm:ss’)
}
}
}
} -ThrottleLimit 4

$resultados | Format-Table -AutoSize
$resultados | Export-Csv -Path “.\conectividade-rede.csv” -NoTypeInformation -Encoding UTF8

$falhas = $resultados | Where-Object { $_.Resultado -ne ‘OK’ }
if ($falhas) {
Write-Warning “$($falhas.Count) testes de conectividade falharam.”
}
else {
Write-Output “Todos os testes de conectividade passaram.”
}

Script 15 — Exportar Relatório HTML Consolidado

Consolida os outputs dos scripts anteriores num relatório HTML único usando ConvertTo-Html (Microsoft Learn — ConvertTo-Html):

$dataRelatorio = Get-Date -Format ‘yyyy-MM-dd’

$css = @”
<style>
body { font-family: ‘Segoe UI’, Arial, sans-serif; margin: 20px; color: #333; }
h1 { color: #0078D4; border-bottom: 2px solid #0078D4; padding-bottom: 10px; }
h2 { color: #106EBE; margin-top: 30px; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
th { background-color: #0078D4; color: white; padding: 10px; text-align: left; }
td { padding: 8px 10px; border-bottom: 1px solid #ddd; }
tr:nth-child(even) { background-color: #f9f9f9; }
.warning { background-color: #FFF3CD; padding: 10px; border-left: 4px solid #FFC107; }
.footer { margin-top: 40px; font-size: 0.85em; color: #999; }
</style>
“@

try {
$secções = @()

# Hardware
if (Test-Path “.\inventario-hardware.csv”) {
$hw = Import-Csv “.\inventario-hardware.csv”
$secções += “<h2>1. Inventario de Hardware</h2>” + ($hw | ConvertTo-Html -Fragment)
}

# Utilizadores
if (Test-Path “.\utilizadores-activos.csv”) {
$users = Import-Csv “.\utilizadores-activos.csv”
$secções += “<h2>2. Utilizadores Activos</h2>” + ($users | ConvertTo-Html -Fragment)
}

# Volumes
if (Test-Path “.\volumes-disco.csv”) {
$vols = Import-Csv “.\volumes-disco.csv”
$secções += “<h2>3. Espaco em Disco</h2>” + ($vols | ConvertTo-Html -Fragment)
}

# Servicos parados
if (Test-Path “.\servicos-parados.csv”) {
$svc = Import-Csv “.\servicos-parados.csv”
if ($svc) {
$secções += “<div class=’warning’>Servicos automaticos parados detectados.</div>”
}
$secções += “<h2>4. Servicos Parados</h2>” + ($svc | ConvertTo-Html -Fragment)
}

# Patches
if (Test-Path “.\patches-windows.csv”) {
$patches = Import-Csv “.\patches-windows.csv”
$secções += “<h2>5. Patches Windows</h2>” + ($patches | ConvertTo-Html -Fragment)
}

# Certificados
if (Test-Path “.\certificados-a-expirar.csv”) {
$certs = Import-Csv “.\certificados-a-expirar.csv”
$secções += “<h2>6. Certificados a Expirar</h2>” + ($certs | ConvertTo-Html -Fragment)
}

# Software
if (Test-Path “.\inventario-software.csv”) {
$sw = Import-Csv “.\inventario-software.csv” | Select-Object -First 50
$secções += “<h2>7. Software Instalado (top 50)</h2>” + ($sw | ConvertTo-Html -Fragment)
}

# Conectividade
if (Test-Path “.\conectividade-rede.csv”) {
$net = Import-Csv “.\conectividade-rede.csv”
$secções += “<h2>8. Conectividade de Rede</h2>” + ($net | ConvertTo-Html -Fragment)
}

$html = ConvertTo-Html -Head $css -Body $secções -Title “Relatorio Sysadmin $dataRelatorio” -PostContent “<div class=’footer’>Gerado automaticamente em $dataRelatorio via PowerShell 7</div>”

$htmlPath = “.\relatorio-sysadmin-$dataRelatorio.html”
$html | Out-File -FilePath $htmlPath -Encoding UTF8
Write-Output “Relatorio HTML gerado: $htmlPath”
}
catch {
Write-Error “Erro ao gerar relatorio HTML: $_”
}

7. Paralelismo com ForEach-Object -Parallel

O paralelismo nativo é uma das maiores vantagens do PowerShell 7 face ao 5.1. O parâmetro -Parallel do ForEach-Object permite executar blocos de script em paralelo, com controlo de concorrência via -ThrottleLimit (Microsoft Learn — ForEach-Object).

Quando usar paralelismo

Cenário Paralelismo recomendado Justificação
Query AD a 500+ grupos Sim (-ThrottleLimit 10) Cada query é independente e I/O-bound
Backup de GPOs Sim (-ThrottleLimit 5) Limita carga no controlador de domínio
Teste de conectividade a N servidores Sim (-ThrottleLimit 4) Latência de rede dominante
Modificar ficheiros locais em série Não Overhead de threads superia o ganho
Operações com dependências entre si Não Race conditions e resultados indeterminados

Regras práticas

  1. Variáveis externas precisam de $using: para serem acessíveis dentro do bloco paralelo:
$limite = 30
$items | ForEach-Object -Parallel {
if ($_.Valor -gt $using:limite) { … }
}
  1. Estado partilhado exige thread-safe. Usa [System.Collections.Concurrent.ConcurrentDictionary] em vez de arrays normais:
$resultados = [System.Collections.Concurrent.ConcurrentDictionary[string, object]]::new()
$items | ForEach-Object -Parallel {
$result = Query-Something $_
$using:resultados.TryAdd($_.Id, $result) | Out-Null
}
  1. Módulos nem sempre carregam automaticamente no runspace paralelo. Importa explicitamente dentro do bloco:
$items | ForEach-Object -Parallel {
Import-Module ActiveDirectory -ErrorAction SilentlyContinue
Get-ADUser $_.SamAccountName
}
  1. -ThrottleLimit deve reflectir a capacidade do alvo. Para AD, 5-10 é seguro; para HTTP requests, 10-20; para SSH a servidores, 4-8.

8. Error Handling e Boas Práticas

O PowerShell 7 herda o modelo de tratamento de erros do 5.1 com melhorias no pipeline. A base é try/catch/finally combinado com $ErrorActionPreference (Microsoft Learn — about_Try_Catch_Finally).

Estrutura recomendada para scripts de produção

$ErrorActionPreference = ‘Stop’
$ProgressPreference = ‘SilentlyContinue’

try {
# 1. Validar pré-requisitos
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
throw “Modulo ActiveDirectory não encontrado. Instalar via RSAT.”
}

# 2. Executar operação principal
$resultado = Operacao-Critica

# 3. Validar resultado
if (-not $resultado) {
Write-Warning “Operacao concluida sem resultados.”
}

# 4. Exportar output
$resultado | Export-Csv -Path $outputPath -NoTypeInformation -Encoding UTF8
}
catch [System.Exception] {
Write-Error “Erro: $($_.Exception.Message)”
Write-Error “Script: $($_.InvocationInfo.ScriptLineNumber) linha $($_.InvocationInfo.PositionMessage)”
# Log para ficheiro
“$((Get-Date).ToString(‘yyyy-MM-dd HH:mm:ss’)) — ERROR — $_” |
Out-File -FilePath “.\error.log” -Append -Encoding UTF8
}
finally {
# 5. Limpeza: fechar sessões, libertar recursos
if ($sessao) { Remove-PSSession $sessao }
Write-Output “Script concluido em $((Get-Date).ToString(‘HH:mm:ss’)).”
}

Boas práticas de consistência

Prática Razão
Usar Get-CimInstance em vez de Get-WmiObject WMI descontinuado em PS7; CIM é o padrão multiplataforma
Sempre especificar -Encoding UTF8 PS7 usa UTF-8 por omissão, mas PS5.1 usa codepage — explicitar evita surpresas
Validar módulos antes de usar Get-Module -ListAvailable evita falhas em runtime
Usar splatting para parâmetros longos New-NetFirewallRule @params é mais legível e manutenível
Timestamps ISO 8601 em ficheiros (Get-Date).ToString('yyyy-MM-dd') garante ordenação alfabética correcta
$ErrorActionPreference = 'Stop' no início Converte erros non-terminating em terminating, permitindo catch
Usar $using: em paralelismo Evita serialização incorrecta de variáveis externas

Configurar VS Code para PowerShell 7

A experiência de desenvolvimento melhora significativamente com a extensão PowerShell no VS Code, que suporta IntelliSense, debugging e análise de código PSScriptAnalyzer (Microsoft Learn — Using VS Code). No settings.json:

{
“powershell.powerShellAdditionalExePaths”: {
“PowerShell 7”: “C:\\Program Files\\PowerShell\\7\\pwsh.exe”
},
“powershell.powerShellDefaultVersion”: “PowerShell 7”,
“powershell.scriptAnalysis.enable”: true,
“powershell.codeFormatting.preset”: “OTBS”
}

9. Automação em Larga Escala com PowerShell Remoting

Para executar os 15 scripts em múltiplos servidores, o PowerShell Remoting é a abordagem nativa. O PS7 suporta tanto WinRM como SSH-based remoting (Microsoft Learn — Running Remote Commands).

Executar um script em N servidores em paralelo

$servidores = @(‘SRV-01’, ‘SRV-02’, ‘SRV-03’, ‘SRV-04’, ‘SRV-05’)

$resultados = $servidores | ForEach-Object -Parallel {
try {
$sessao = New-PSSession -ComputerName $_ -ErrorAction Stop
$output = Invoke-Command -Session $sessao -ScriptBlock {
# Importar e executar o script remoto
Get-ComputerInfo -Property CsProcessors, CsTotalPhysicalMemory, OsName
}
Remove-PSSession $sessao
$output
}
catch {
[PSCustomObject]@{
Servidor = $_
Erro = $_.Exception.Message
}
}
} -ThrottleLimit 5

$resultados | Format-Table -AutoSize

Tabela de comparação: WinRM vs SSH Remoting

Característica WinRM SSH
Plataforma Windows apenas Windows, Linux, macOS
Configuração Enable-PSRemoting Instalar openssh-server + configurar subsystem
Autenticação Kerberos, NTLM, CredSSP Chave pública, password
Porta 5985 (HTTP), 5986 (HTTPS) 22
Recommended for Ambientes Windows homogéneos Ambientes mistos Windows/Linux

Checklist Pré-Produção

Antes de aplicar qualquer script deste artigo em ambiente produtivo, confirma:

  1. Versão do PowerShell (deve ser 7.4 LTS ou superior): pwsh -v
  2. Módulos necessários instalados: Get-Module -ListAvailable ActiveDirectory, GroupPolicy, NetSecurity, Storage
  3. Privilégios administrativos: executar como Administrator para scripts que acedem ao registo, firewall ou serviços do sistema
  4. PowerShell Remoting activado se executar contra servidores remotos: Enable-PSRemoting -Force no alvo
  5. Política de execução configurada: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
  6. Staging environment disponível (VM de teste ou servidor não-produção) para testar scripts que alteram configuração (e.g. firewall, GPOs, limpeza de temporários) antes de aplicar em produção
  7. Backup do estado antes de aplicar mudanças: snapshot de VM, export de GPO, backup do AD (wbadmin start systemstatebackup)
  8. Janela de manutenção para scripts que afectam serviços em execução ou conectividade de rede

⚠ **Atenção

** Comandos que afectam o domínio ou configuração de rede (e.g. New-NetFirewallRule, Backup-Gpo, Remove-Item em pastas do sistema) devem ser testados em staging e aplicados em janela de manutenção. Configuração incorrecta de firewall pode bloquear acesso RDP e management; remoção indiscriminada de ficheiros temporários pode afectar instalações em curso.

Artigos Relacionados

Fontes

Este artigo foi útil?

Duarte Spínola

Deixe um Comentário