O formato CSV (Comma-Separated Values) é um dos formatos de dados mais comuns e simples para troca de informações entre sistemas. No PowerShell, trabalhar com CSV é uma tarefa cotidiana, seja para exportar resultados de comandos, importar dados de fontes externas ou configurar scripts. Dominar os cmdlets Import-Csv e Export-Csv é essencial para qualquer profissional que deseja automatizar tarefas administrativas ou processar dados de forma eficiente.

Nesta aula, vamos explorar profundamente como o PowerShell lida com CSV, desde a importação e exportação até a manipulação de objetos criados a partir desses arquivos. Também veremos como lidar com delimitadores diferentes e discutiremos casos de uso comuns, com exemplos práticos que você pode aplicar imediatamente em seus scripts.

Import-Csv e Export-Csv

Os cmdlets Import-Csv e Export-Csv são as ferramentas primárias para trabalhar com arquivos CSV no PowerShell. Eles convertem entre arquivos de texto estruturado e objetos do PowerShell, permitindo que você use a pipeline para processar dados de forma consistente.

O Export-Csv converte objetos em linhas de um arquivo CSV, enquanto o Import-Csv faz o caminho inverso, criando objetos a partir das linhas do arquivo. Esses cmdlets são simétricos e poderosos, mas é importante entender suas nuances para evitar armadilhas comuns, como a perda de tipos de dados (tudo vira string).

Exemplo básico de exportação:

$servidores = @(
    [PSCustomObject]@{ Nome = 'WEB01'; IP = '192.168.1.10'; Sistema = 'Windows Server 2022' },
    [PSCustomObject]@{ Nome = 'DB01'; IP = '192.168.1.11'; Sistema = 'Windows Server 2022' },
    [PSCustomObject]@{ Nome = 'FW01'; IP = '192.168.1.1'; Sistema = 'PFSense' }
)

$servidores | Export-Csv -Path .\servidores.csv -NoTypeInformation
Get-Content .\servidores.csv

Nesse exemplo, criamos uma lista de objetos com propriedades Nome, IP e Sistema, exportamos para servidores.csv e exibimos o conteúdo do arquivo. O parâmetro -NoTypeInformation evita que a linha com o tipo de objeto seja incluída, deixando o CSV mais limpo.

Agora, importando de volta:

$dadosImportados = Import-Csv .\servidores.csv
$dadosImportados | Format-Table -AutoSize

O resultado é uma coleção de objetos PSCustomObject, cada um com as propriedades Nome, IP e Sistema, todas como strings. Isso permite filtrar, ordenar e manipular os dados facilmente.

Objetos a partir de CSV

Quando você importa um CSV, o PowerShell cria objetos do tipo PSCustomObject (ou PSObject) para cada linha. As colunas viram propriedades e os valores são armazenados como strings. Isso é fundamental: qualquer operação que dependa de tipos (como números ou datas) exigirá conversão explícita.

Exemplo de manipulação de objetos importados:

$csv = Import-Csv .\servidores.csv
# Filtrar servidores Windows
$windows = $csv | Where-Object { $_.Sistema -like '*Windows*' }
$windows | Select-Object Nome, IP

# Converter IP para [System.Net.IPAddress] (apenas demonstração)
$primeiroIP = $csv[0].IP
$ipConvertido = [System.Net.IPAddress]::Parse($primeiroIP)
$ipConvertido.GetType().FullName

Repare que, ao importar, o valor de IP é uma string, e se precisarmos tratá-lo como um endereço IP, devemos convertê-lo manualmente. Isso é comum quando o CSV contém números, datas ou outros tipos não textuais.

Uma técnica útil é usar o parâmetro -Header no Import-Csv quando o arquivo não possui cabeçalho, permitindo que você defina nomes de propriedades personalizados. Veja:

# Arquivo sem cabeçalho: "WEB01,192.168.1.10,Windows"
$dados = Import-Csv .\servidores_sem_header.csv -Header Nome, IP, Sistema
$dados | Format-Table -AutoSize

Isso é muito útil ao lidar com arquivos de sistemas legados que não incluem cabeçalho.

Delimitadores

O padrão do CSV é usar vírgula como delimitador, mas muitos sistemas usam ponto e vírgula (principalmente em regiões onde a vírgula é usada como separador decimal) ou tabulações. O PowerShell permite especificar qualquer delimitador com o parâmetro -Delimiter.

Exemplo com ponto e vírgula:

# Criando um CSV com ponto e vírgula
$dados = @([PSCustomObject]@{ Nome = 'A'; Valor = '10,5' })
$dados | Export-Csv -Path .\dados.csv -Delimiter ';' -NoTypeInformation

# Ver conteúdo
Get-Content .\dados.csv

# Importar corretamente
$importado = Import-Csv .\dados.csv -Delimiter ';'
$importado.Valor  # Retorna "10,5" como string

Se você tentar importar um arquivo com ponto e vírgula sem especificar o delimitador, o PowerShell interpretará cada linha como uma única coluna, o que pode causar erros. Portanto, sempre verifique o delimitador do arquivo que está manipulando.

Também é possível usar tabulações como delimitador, útil ao copiar dados de planilhas. O caractere de tabulação pode ser passado usando "`t" (tique invertido + t).

$dados | Export-Csv -Path .\dados_tab.txt -Delimiter "`t" -NoTypeInformation
$importadoTab = Import-Csv .\dados_tab.txt -Delimiter "`t"

Casos de uso

Os casos de uso para CSV no PowerShell são vastos. Vamos explorar alguns cenários práticos que você encontrará no dia a dia.

1. Exportação de relatórios: você pode exportar o resultado de qualquer comando que produza objetos para CSV, gerando relatórios que podem ser abertos no Excel ou processados por outras ferramentas.

Get-Process | Export-Csv -Path .\processos.csv -NoTypeInformation

2. Importação de listas para automação: se você tem uma lista de usuários, computadores ou qualquer recurso em CSV, pode importá-la e usá-la em loop para executar ações em massa.

$usuarios = Import-Csv .\usuarios.csv
foreach ($usuario in $usuarios) {
    # Supondo que o CSV tenha colunas: Nome, Email, Departamento
    New-ADUser -Name $usuario.Nome -EmailAddress $usuario.Email -Department $usuario.Departamento
}

3. Configuração de servidores: arquivos CSV podem armazenar configurações de máquinas, como IPs, hostnames e funções, permitindo que scripts provisionem infraestrutura de forma declarativa.

$configs = Import-Csv .\config_servidores.csv
foreach ($config in $configs) {
    # Chamar função de provisionamento
    Provisionar-Servidor -Nome $config.Nome -IP $config.IP -Role $config.Role
}

4. Análise de dados: depois de importar, você pode usar cmdlets como Group-Object, Measure-Object e Sort-Object para analisar dados.

$vendas = Import-Csv .\vendas.csv
$vendas | Group-Object Produto | Select-Object Name, Count, @{N='Total';E={($_.Group | Measure-Object -Property Valor -Sum).Sum}}

5. Intercâmbio com APIs: muitos serviços web exportam dados em CSV; você pode baixar e processar esses arquivos diretamente no PowerShell.

Invoke-WebRequest -Uri 'https://exemplo.com/dados.csv' -OutFile .\dados.csv
$dados = Import-Csv .\dados.csv

Boas práticas e observações finais

Ao trabalhar com CSV no PowerShell, lembre-se de:

  • Sempre usar -NoTypeInformation no Export-Csv para evitar a linha extra que pode atrapalhar outros sistemas.
  • Verificar o delimitador antes de importar; use Get-Content para inspecionar o arquivo.
  • Lembre-se de que tudo é string após a importação; converta tipos quando necessário.
  • Para arquivos grandes, considere usar Import-Csv com -Delimiter e processamento em pipeline para não sobrecarregar a memória.
  • Utilize o parâmetro -Encoding para garantir a codificação correta (UTF-8, por exemplo) ao exportar/importar com caracteres especiais.

Com essas técnicas, você pode integrar CSV em seus scripts de forma robusta e eficiente, tornando seu trabalho mais produtivo.

Referências

Exercícios

  1. Crie um script que exporte a lista de processos em execução no seu sistema para um arquivo chamado processos.csv usando Export-Csv e depois importe-o novamente, exibindo os 5 primeiros processos com maior uso de memória (WorkingSet).

    ✓ Resposta:
    # Exportar processos
    Get-Process | Export-Csv -Path .\processos.csv -NoTypeInformation
    
    # Importar e ordenar por WorkingSet (memória em bytes)
    $processos = Import-Csv .\processos.csv
    $processos | Sort-Object {[int64]$_.WorkingSet} -Descending | Select-Object -First 5 | Format-Table Name, WorkingSet
  2. Dado um arquivo CSV com delimitador ponto e vírgula e sem cabeçalho, com colunas: Nome, Idade, Cidade. Escreva um script que importe esse arquivo usando cabeçalho personalizado e exiba apenas as pessoas com idade maior que 30 anos.

    ✓ Resposta:
    $pessoas = Import-Csv .\pessoas.csv -Delimiter ';' -Header Nome, Idade, Cidade
    $pessoas | Where-Object { [int]$_.Idade -gt 30 } | Format-Table Nome, Idade, Cidade
  3. Explique por que, ao importar um CSV, todos os valores são tratados como strings e como você pode converter uma coluna para número inteiro em uma expressão de pipeline. Dê um exemplo prático.

    ✓ Resposta: O PowerShell não tem como inferir o tipo dos dados em um CSV, pois o formato é textual. Por isso, cada valor é lido como string. Para converter, você pode usar o operador de cast ou métodos como [int]. Exemplo: $csv | ForEach-Object { $_.Idade = [int]$_.Idade } ou usar uma propriedade calculada em Select-Object: $csv | Select-Object Nome, @{N='Idade';E={[int]$_.Idade}}.
  4. Crie um script que leia um arquivo CSV de vendas com colunas Produto, Quantidade e PrecoUnitario (separados por vírgula), calcule o total de cada produto (Quantidade * PrecoUnitario) e exporte um novo CSV com as colunas Produto e Total.

    ✓ Resposta:
    $vendas = Import-Csv .\vendas.csv
    $resultado = $vendas | ForEach-Object {
        [PSCustomObject]@{
            Produto = $_.Produto
            Total = [double]$_.Quantidade * [double]$_.PrecoUnitario
        }
    }
    $resultado | Export-Csv -Path .\totais.csv -NoTypeInformation
  5. Escreva um script que importe um CSV com colunas Nome e Email e, para cada linha, envie um e-mail usando o cmdlet Send-MailMessage (ou Send-MailkitMessage se disponível). Assuma que você tem um servidor SMTP fictício. Não execute de fato; apenas mostre a estrutura.

    ✓ Resposta:
    $destinatarios = Import-Csv .\contatos.csv
    foreach ($dest in $destinatarios) {
        $params = @{
            From = 'remetente@exemplo.com'
            To = $dest.Email
            Subject = 'Assunto do e-mail'
            Body = "Olá $($dest.Nome), este é um teste."
            SmtpServer = 'smtp.exemplo.com'
        }
        Send-MailMessage @params
    }