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):
Para instalar uma versão específica (e.g. 7.4 LTS):
Instalação via MSI
Em ambientes sem winget ou para deployment automatizado, o MSI está disponível no GitHub PowerShell Releases:
$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:
# 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:
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):
$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):
$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):
$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):
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):
$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):
$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:
“$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):
$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):
# 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):
$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):
$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:
$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):
$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:
@{ 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):
$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
- Variáveis externas precisam de
$using:para serem acessíveis dentro do bloco paralelo:
$items | ForEach-Object -Parallel {
if ($_.Valor -gt $using:limite) { … }
}
- Estado partilhado exige
thread-safe. Usa[System.Collections.Concurrent.ConcurrentDictionary]em vez de arrays normais:
$items | ForEach-Object -Parallel {
$result = Query-Something $_
$using:resultados.TryAdd($_.Id, $result) | Out-Null
}
- Módulos nem sempre carregam automaticamente no runspace paralelo. Importa explicitamente dentro do bloco:
Import-Module ActiveDirectory -ErrorAction SilentlyContinue
Get-ADUser $_.SamAccountName
}
-ThrottleLimitdeve 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
$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
$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:
- Versão do PowerShell (deve ser 7.4 LTS ou superior):
pwsh -v - Módulos necessários instalados:
Get-Module -ListAvailable ActiveDirectory, GroupPolicy, NetSecurity, Storage - Privilégios administrativos: executar como Administrator para scripts que acedem ao registo, firewall ou serviços do sistema
- PowerShell Remoting activado se executar contra servidores remotos:
Enable-PSRemoting -Forceno alvo - Política de execução configurada:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser - 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
- Backup do estado antes de aplicar mudanças: snapshot de VM, export de GPO, backup do AD (
wbadmin start systemstatebackup) - 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
- kbase.pt/windows-server-2026-diagnostico-conectividade/ — Diagnóstico avançado de conectividade em Windows Server 2026, complementar ao Script 14
- kbase.pt/zero-trust-pme-2026/ — Implementação de Zero Trust em PME, inclui configuração de firewall e certificados
- kbase.pt/bitlocker-entra-id-join-2026/ — Configuração BitLocker com Entra ID, usa cmdlets PowerShell cobertos neste artigo
- kbase.pt/ransomware-protecao-2026/ — protecção contra ransomware, inclui auditoria de patches e logs de eventos
- kbase.pt/mfa-fido2-microsoft-365/ — MFA com FIDO2 no Microsoft 365, automatizável via PowerShell 7
Fontes
- Microsoft Learn — Installing PowerShell on Windows
- Microsoft Learn — What’s New in PowerShell 7.4
- Microsoft Learn — What’s New in PowerShell 7.5
- Microsoft Learn — Migrating from Windows PowerShell 5.1 to PowerShell 7
- Microsoft Learn — winget Package Manager
- Microsoft Learn — Installing PowerShell on Linux
- Microsoft Learn — Writing Portable Modules
- Microsoft Learn — Get-ComputerInfo
- Microsoft Learn — Get-LocalUser
- Microsoft Learn — Get-Volume
- Microsoft Learn — Get-Service
- Microsoft Learn — Get-HotFix
- Microsoft Learn — Get-EventLog
- Microsoft Learn — Get-CimInstance
- Microsoft Learn — Get-NetFirewallRule
- Microsoft Learn — New-NetFirewallRule
- Microsoft Learn — Get-Certificate (PKI)
- Microsoft Learn — Backup-Gpo
- Microsoft Learn — Get-ADGroup
- Microsoft Learn — Get-ADGroupMember
- Microsoft Learn — ForEach-Object
- Microsoft Learn — ConvertTo-Html
- Microsoft Learn — about_Try_Catch_Finally
- Microsoft Learn — Running Remote Commands
- Microsoft Learn — Using VS Code with PowerShell
- Microsoft Learn — Remote Desktop Services Overview
- GitHub — PowerShell/PowerShell
- GitHub — PowerShell Releases
