O PowerShell é construído sobre o .NET, o que significa que você tem acesso a todas as classes, métodos e propriedades da plataforma .NET diretamente em seus scripts. Nesta aula, vamos explorar técnicas avançadas de interação com o .NET, como carregar assemblies externos, trabalhar com tipos genéricos, lidar com eventos e aplicar esses conceitos em casos de uso reais.

Dominar essas técnicas permite que você estenda o PowerShell muito além dos cmdlets nativos, criando soluções robustas e integradas com bibliotecas .NET existentes, seja para automação, processamento de dados ou administração de sistemas.

Carregando assemblies

Assemblies são os blocos de construção do .NET, contendo tipos compilados (classes, interfaces, etc.) que podem ser usados pelo seu script. Por padrão, o PowerShell carrega alguns assemblies principais, mas para usar bibliotecas externas ou tipos específicos, é necessário carregá-los explicitamente.

O cmdlet Add-Type é a forma mais comum de carregar assemblies no PowerShell. Ele pode carregar um assembly a partir de um caminho de arquivo, de um nome de assembly, ou até mesmo compilar código C# diretamente no script.

# Carregar um assembly a partir de um arquivo DLL
Add-Type -Path "C:\MeusAssemblies\MinhaBiblioteca.dll"

# Carregar um assembly do GAC (Global Assembly Cache)
Add-Type -AssemblyName "System.Windows.Forms"

# Compilar e carregar código C# inline
Add-Type -TypeDefinition @"
public class Calculadora {
    public int Soma(int a, int b) { return a + b; }
}
"@

Após carregar, você pode usar os tipos do assembly normalmente, criando objetos com New-Object ou usando a sintaxe de tipo estático [Namespace.Tipo].

Também é possível verificar se uma classe está disponível usando [Type]::GetType() ou listar os tipos carregados com [AppDomain]::CurrentDomain.GetAssemblies().

Tipos genéricos

Tipos genéricos permitem criar classes e métodos que funcionam com qualquer tipo de dado, aumentando a reutilização de código. No PowerShell, você pode instanciar e usar tipos genéricos como List<T>, Dictionary<TKey, TValue> e outros.

Para criar um objeto genérico, use a sintaxe [TipoGenérico[Tipo]] ou New-Object com o nome do tipo e o tipo de parâmetro. Por exemplo:

# Criar uma lista de strings
$lista = New-Object 'System.Collections.Generic.List[string]'
$lista.Add("PowerShell")
$lista.Add(".NET")

# Criar um dicionário de inteiros para strings
$dicionario = New-Object 'System.Collections.Generic.Dictionary[int, string]'
$dicionario[1] = "Um"
$dicionario[2] = "Dois"

# Usar sintaxe de tipo estático
[System.Collections.Generic.List[int]]$numeros = @(1,2,3)
$numeros.Add(4)

É importante notar que o PowerShell não tem suporte nativo para inferência de tipos genéricos, então você deve especificar o tipo explicitamente. Além disso, alguns métodos genéricos exigem que você passe o tipo como parâmetro, como [System.Linq.Enumerable]::ToList().

Eventos (visão geral)

Eventos são mecanismos que permitem que objetos notifiquem quando algo acontece, como um clique de botão ou a conclusão de uma operação. No PowerShell, você pode assinar eventos usando o cmdlet Register-ObjectEvent, que permite executar uma ação quando o evento for acionado.

Eventos são comuns em .NET para criar aplicações interativas e reativas. No PowerShell, eles são úteis para monitorar processos, arquivos ou até mesmo para criar interfaces gráficas simples.

# Exemplo: monitorar a criação de um arquivo
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = "C:\Temp"
$watcher.EnableRaisingEvents = $true

$action = {
    Write-Host "Arquivo alterado: $($event.SourceEventArgs.FullPath)"
}

Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action

# Para parar o monitoramento, use Unregister-Event

Neste exemplo, o evento Created é disparado quando um arquivo é criado na pasta monitorada. A ação é executada de forma assíncrona, permitindo que o script continue executando outras tarefas.

É importante entender como gerenciar eventos: você pode listar eventos com Get-Event, aguardar eventos com Wait-Event e removê-los com Remove-Event. O uso de eventos pode tornar seus scripts mais eficientes, mas também requer cuidado com o ciclo de vida e a limpeza dos registros.

Casos de uso

As técnicas avançadas de interação com .NET são aplicáveis em diversas situações do dia a dia. Abaixo estão alguns casos práticos:

  • Automação de aplicações Windows: Usar assemblies como System.Windows.Forms para criar caixas de diálogo ou manipular janelas.
  • Processamento de dados: Utilizar coleções genéricas e LINQ para manipular grandes conjuntos de dados de forma eficiente.
  • Monitoramento de sistema: Usar FileSystemWatcher para vigiar pastas e reagir a alterações, ou System.Diagnostics.Process para monitorar processos.
  • Integração com APIs: Carregar assemblies de clientes HTTP, como System.Net.Http, para fazer requisições avançadas.
  • Compilação dinâmica: Usar Add-Type para criar classes personalizadas em C# e usá-las diretamente no PowerShell.

Vamos ver um exemplo completo que combina vários conceitos: criar uma lista genérica de processos e filtrá-la usando LINQ.

# Carregar LINQ
Add-Type -AssemblyName System.Core

# Obter todos os processos e criar uma lista genérica
$processos = [System.Diagnostics.Process]::GetProcesses()
$lista = New-Object 'System.Collections.Generic.List[System.Diagnostics.Process]'
$lista.AddRange($processos)

# Usar LINQ para filtrar processos que usam mais de 100 MB de memória
$filtrados = [System.Linq.Enumerable]::Where($lista, [Func[System.Diagnostics.Process, bool]] { param($p) $p.WorkingSet64 -gt 100MB })

# Exibir os processos filtrados
$filtrados | Select-Object ProcessName, WorkingSet64

Esse exemplo mostra como carregar assemblies, usar tipos genéricos e delegados para filtrar dados — tudo dentro do PowerShell.

Boas práticas

Ao trabalhar com .NET no PowerShell, considere as seguintes recomendações:

  • Use Add-Type com moderação: Carregar assemblies desnecessariamente pode aumentar o consumo de memória e o tempo de carregamento. Carregue apenas quando necessário.
  • Prefira cmdlets nativos quando possível: Muitas vezes, há um cmdlet que já faz o que você precisa, como Get-Process em vez de acessar a classe Process diretamente.
  • Gerencie eventos adequadamente: Sempre remova registros de eventos quando não forem mais necessários para evitar vazamentos de memória.
  • Teste a compatibilidade: Algumas classes .NET podem não estar disponíveis em versões diferentes do PowerShell ou do .NET Framework. Verifique a documentação.
  • Escape corretamente: Ao usar tipos genéricos com colchetes, tome cuidado com a sintaxe, especialmente em strings.

Referências

Exercícios

  1. Escreva um script que carregue o assembly System.Windows.Forms e exiba uma caixa de mensagem com o texto "Olá, .NET!".

    ✓ Resposta:
    Add-Type -AssemblyName System.Windows.Forms
    [System.Windows.Forms.MessageBox]::Show("Olá, .NET!")
  2. Crie uma lista genérica de inteiros, adicione os números de 1 a 10 e calcule a soma usando o método Sum do LINQ.

    ✓ Resposta:
    $lista = New-Object 'System.Collections.Generic.List[int]'
    1..10 | ForEach-Object { $lista.Add($_) }
    $soma = [System.Linq.Enumerable]::Sum($lista)
    Write-Host "Soma: $soma"
  3. Explique, com um exemplo, como registrar um evento para ser notificado quando um processo específico for iniciado.

    ✓ Resposta: Você pode usar o System.Diagnostics.Process para monitorar eventos, mas o PowerShell não tem um evento direto para início de processo. Uma abordagem é usar ManagementEventWatcher com WMI. Exemplo:
    $query = "SELECT * FROM Win32_ProcessStartTrace"
    $watcher = New-Object System.Management.ManagementEventWatcher $query
    $action = { Write-Host "Novo processo: $($event.SourceEventArgs.NewEvent.ProcessName)" }
    Register-ObjectEvent -InputObject $watcher -EventName EventArrived -Action $action
  4. Escreva um script que use Add-Type para compilar uma classe C# com um método que calcula o fatorial de um número, e chame esse método.

    ✓ Resposta:
    Add-Type -TypeDefinition @"
    public class Matematica {
        public static long Fatorial(int n) {
            if (n <= 1) return 1;
            return n * Fatorial(n - 1);
        }
    }
    "@
    [Matematica]::Fatorial(5)
  5. Cite três casos de uso prático para carregar assemblies externos em scripts do PowerShell.

    ✓ Resposta: 1) Usar bibliotecas de terceiros para funcionalidades específicas, como manipulação de PDFs. 2) Integrar com APIs de sistemas legados que fornecem DLLs. 3) Criar interfaces gráficas com Windows Forms ou WPF sem precisar de um compilador externo.