Criando relatórios de sistema com PowerShell e exportando para CSV

1. Introdução aos relatórios de sistema com PowerShell

O PowerShell é uma ferramenta poderosa para administradores de sistemas que precisam gerar relatórios detalhados sobre o estado de servidores e estações de trabalho. Diferente de interfaces gráficas que exigem navegação manual, o PowerShell permite coletar, filtrar e exportar informações de forma programática e repetível.

A exportação para CSV (Comma-Separated Values) é particularmente vantajosa por três motivos principais: compatibilidade universal com ferramentas como Microsoft Excel e Google Sheets, facilidade de importação em bancos de dados e sistemas de monitoramento, e o formato leve que pode ser processado até mesmo em arquivos de texto simples.

Pré-requisitos básicos:
- PowerShell 5.1 ou superior (incluído no Windows 10/11 e Windows Server 2016+)
- Permissões de administrador local para acessar informações do sistema (WMI/CIM)
- Para relatórios remotos, permissões de administrador nos computadores alvo e WinRM habilitado

2. Coletando informações do hardware do sistema

O cmdlet Get-CimInstance é a forma moderna e recomendada para acessar informações do sistema via WMI (Windows Management Instrumentation). Vamos começar com um exemplo prático que coleta dados essenciais de hardware.

# Script: Inventário de Hardware Básico
$hardwareReport = @()

# CPU
$cpu = Get-CimInstance -ClassName Win32_Processor
$hardwareReport += [PSCustomObject]@{
    Categoria = "CPU"
    Nome = $cpu.Name
    Núcleos = $cpu.NumberOfCores
    Threads = $cpu.NumberOfLogicalProcessors
    FrequênciaMHz = $cpu.MaxClockSpeed
}

# Memória RAM
$ram = Get-CimInstance -ClassName Win32_PhysicalMemory
$totalRAM = ($ram | Measure-Object -Property Capacity -Sum).Sum / 1GB
$hardwareReport += [PSCustomObject]@{
    Categoria = "Memória RAM"
    TotalGB = [math]::Round($totalRAM, 2)
    Pentes = $ram.Count
}

# Discos
$disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3"
foreach ($disk in $disks) {
    $hardwareReport += [PSCustomObject]@{
        Categoria = "Disco"
        Unidade = $disk.DeviceID
        TamanhoGB = [math]::Round($disk.Size / 1GB, 2)
        LivreGB = [math]::Round($disk.FreeSpace / 1GB, 2)
        PercentualLivre = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
    }
}

# Placa-mãe e BIOS
$bios = Get-CimInstance -ClassName Win32_BIOS
$baseboard = Get-CimInstance -ClassName Win32_BaseBoard
$hardwareReport += [PSCustomObject]@{
    Categoria = "BIOS"
    Fabricante = $bios.Manufacturer
    Versão = $bios.SMBIOSBIOSVersion
    PlacaMãe = "$($baseboard.Manufacturer) $($baseboard.Product)"
}

$hardwareReport | Export-Csv -Path "C:\Relatorios\Hardware_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8

3. Obtendo dados de software e processos

Para listar programas instalados, o namespace Win32_Product deve ser evitado por ser lento e causar reconhecimento de pacotes. A abordagem recomendada é consultar o registro do Windows.

# Script: Relatório de Software Instalado
$software = Get-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*",
    "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$softwareReport = $software | Where-Object {
    $_.DisplayName -and $_.DisplayName -notlike "*Update for*" -and $_.DisplayName -notlike "*Security Update*"
} | Select-Object @{N="Programa";E={$_.DisplayName}},
    @{N="Versão";E={$_.DisplayVersion}},
    @{N="Fabricante";E={$_.Publisher}},
    @{N="DataInstalação";E={$_.InstallDate}}

# Processos ativos com consumo de recursos
$processReport = Get-Process | Select-Object Name, Id,
    @{N="CPU(s)";E={[math]::Round($_.CPU, 2)}},
    @{N="MemóriaMB";E={[math]::Round($_.WorkingSet64 / 1MB, 2)}},
    @{N="Threads";E={$_.Threads.Count}}

# Serviços do Windows
$serviceReport = Get-Service | Select-Object Name, DisplayName, Status, StartType

# Exportação combinada
$softwareReport | Export-Csv "C:\Relatorios\Software_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
$processReport | Export-Csv "C:\Relatorios\Processos_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
$serviceReport | Export-Csv "C:\Relatorios\Servicos_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation

4. Coletando informações de rede e usuários

Informações de rede e sessões de usuários são cruciais para auditoria e troubleshooting.

# Script: Relatório de Rede e Usuários
$networkReport = Get-NetAdapter | Where-Object {$_.Status -eq "Up"} | ForEach-Object {
    $adapter = $_
    $ipConfig = Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4
    $dns = Get-DnsClientServerAddress -InterfaceIndex $adapter.ifIndex
    [PSCustomObject]@{
        Adaptador = $adapter.Name
        IP = $ipConfig.IPAddress
        Máscara = $ipConfig.PrefixLength
        Gateway = (Get-NetRoute -InterfaceIndex $adapter.ifIndex -DestinationPrefix "0.0.0.0/0").NextHop
        DNS1 = $dns.ServerAddresses[0]
        DNS2 = $dns.ServerAddresses[1]
        MAC = $adapter.MacAddress
    }
}

# Usuários logados
$userSessions = Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object UserName
$loggedUsers = @()
$sessions = quser 2>$null
if ($sessions) {
    $loggedUsers = $sessions | Select-Object -Skip 1 | ForEach-Object {
        $parts = $_ -split '\s+'
        [PSCustomObject]@{
            Usuário = $parts[0]
            Sessão = $parts[1]
            ID = $parts[2]
            Estado = $parts[3]
        }
    }
}

# Compartilhamentos de rede
$shares = Get-SmbShare | Select-Object Name, Path, Description, ShareType

$networkReport | Export-Csv "C:\Relatorios\Rede_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
$loggedUsers | Export-Csv "C:\Relatorios\Usuarios_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
$shares | Export-Csv "C:\Relatorios\Compartilhamentos_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation

5. Estruturando os dados e exportando para CSV

A qualidade do relatório depende de como os dados são estruturados antes da exportação. Use Select-Object para escolher apenas as propriedades relevantes, Sort-Object para ordenar e Group-Object para agrupar.

# Exemplo de estruturação avançada
$allProcesses = Get-Process | Group-Object -Property Company | ForEach-Object {
    [PSCustomObject]@{
        Fabricante = $_.Name
        TotalProcessos = $_.Count
        MemóriaTotalMB = [math]::Round(($_.Group | Measure-Object -Property WorkingSet64 -Sum).Sum / 1MB, 2)
        Processos = ($_.Group.Name -join "; ")
    }
} | Sort-Object -Property TotalProcessos -Descending

# Exportação com opções
$allProcesses | Export-Csv -Path "C:\Relatorios\ProcessosAgrupados.csv" `
    -NoTypeInformation `
    -Encoding UTF8 `
    -Delimiter ";"

# Verificando a exportação
Import-Csv "C:\Relatorios\ProcessosAgrupados.csv" -Delimiter ";" | Format-Table -AutoSize

6. Automatizando e agendando relatórios periódicos

Crie funções reutilizáveis para gerar relatórios completos e agende-os no Windows Task Scheduler.

# Função principal de relatório
function New-SystemReport {
    param(
        [string]$OutputPath = "C:\Relatorios",
        [string[]]$Computers = @($env:COMPUTERNAME)
    )

    foreach ($computer in $Computers) {
        try {
            $report = @()
            # Coleta CPU
            $cpu = Get-CimInstance -ComputerName $computer -ClassName Win32_Processor
            $report += [PSCustomObject]@{
                Computador = $computer
                Categoria = "CPU"
                Valor = $cpu.Name
                Detalhe = "$($cpu.NumberOfCores) núcleos / $($cpu.NumberOfLogicalProcessors) threads"
            }

            # Coleta memória
            $ram = Get-CimInstance -ComputerName $computer -ClassName Win32_PhysicalMemory
            $totalRAM = [math]::Round(($ram | Measure-Object -Property Capacity -Sum).Sum / 1GB, 2)
            $report += [PSCustomObject]@{
                Computador = $computer
                Categoria = "RAM"
                Valor = "$totalRAM GB"
                Detalhe = "$($ram.Count) pentes"
            }

            # Coleta disco C:
            $disk = Get-CimInstance -ComputerName $computer -ClassName Win32_LogicalDisk -Filter "DeviceID='C:'"
            $pctLivre = [math]::Round(($disk.FreeSpace / $disk.Size) * 100, 2)
            $report += [PSCustomObject]@{
                Computador = $computer
                Categoria = "Disco C:"
                Valor = "$([math]::Round($disk.Size/1GB,2)) GB"
                Detalhe = "$([math]::Round($disk.FreeSpace/1GB,2)) GB livres ($pctLivre%)"
            }

            # Exporta
            $filename = "$OutputPath\RelatorioSistema_$computer_$(Get-Date -Format 'yyyyMMdd').csv"
            $report | Export-Csv -Path $filename -NoTypeInformation -Encoding UTF8
            Write-Host "Relatório gerado: $filename" -ForegroundColor Green
        }
        catch {
            Write-Warning "Erro ao processar $computer : $_"
        }
    }
}

# Uso: relatório semanal de desempenho
New-SystemReport -Computers @("SERVIDOR01", "SERVIDOR02", "WORKSTATION01")

Para agendar no Task Scheduler, crie uma tarefa básica que execute:

powershell.exe -ExecutionPolicy Bypass -File "C:\Scripts\SystemReport.ps1"

7. Boas práticas e tratamento de erros

Sempre valide dados ausentes e trate exceções para evitar relatórios corrompidos.

# Função segura para coleta de dados
function Get-SafeCimInstance {
    param(
        [string]$ClassName,
        [string]$ComputerName = $env:COMPUTERNAME,
        [string]$Filter = $null
    )

    try {
        $params = @{
            ClassName = $ClassName
            ComputerName = $ComputerName
            ErrorAction = "Stop"
        }
        if ($Filter) { $params.Filter = $Filter }

        $result = Get-CimInstance @params
        return $result
    }
    catch {
        Write-Warning "Falha ao obter $ClassName de $ComputerName : $($_.Exception.Message)"
        return $null
    }
}

# Tratamento de valores nulos na exportação
$data = @()
$cpuInfo = Get-SafeCimInstance -ClassName "Win32_Processor"
if ($cpuInfo) {
    $data += [PSCustomObject]@{
        Propriedade = "CPU"
        Valor = $cpuInfo.Name ?? "Não disponível"
        Detalhe = "$($cpuInfo.NumberOfCores ?? 0) núcleos"
    }
} else {
    $data += [PSCustomObject]@{
        Propriedade = "CPU"
        Valor = "Erro na coleta"
        Detalhe = "Verifique permissões e conectividade"
    }
}

# Limpeza do CSV: remover linhas vazias e normalizar
$csvPath = "C:\Relatorios\RelatorioLimpo.csv"
$data | Export-Csv $csvPath -NoTypeInformation -Encoding UTF8

# Pós-processamento: remover aspas desnecessárias
(Get-Content $csvPath) -replace '"', '' | Set-Content $csvPath

Dicas finais:
- Use -Encoding UTF8 para suportar caracteres especiais
- Defina -NoTypeInformation para evitar a linha "#TYPE" no CSV
- Para relatórios remotos, configure corretamente o WinRM e o firewall
- Teste scripts em ambiente controlado antes de agendar em produção

Referências