Tipos .NET em classes PowerShell

Oct 23 2020

Estou tentando entender as práticas recomendadas da classe do PowerShell e me confundindo com uma classe simples para lidar com dicas de balão.

Add-Type -assemblyName:System.Drawing
Add-Type -assemblyName:System.Windows.Forms

class PxMessage {
    static [PxMessage] $instance static $balloon
    static $defaultIcon static [PxMessage] GetInstance($processIcon) {
        if ([PxMessage]::instance -eq $null) { [PxMessage]::instance = [PxMessage]::new() #[PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new() [PxMessage]::balloon = New-Object Windows.Forms.NotifyIcon [PxMessage]::defaultIcon = $processIcon
        }

        return [PxMessage]::instance
    }

    [Void] SendMessage ([String]$title, [String]$message, [String]$messageIcon) { [PxMessage]::balloon.icon = [PxMessage]::defaultIcon [PxMessage]::balloon.balloonTipTitle = $title
        [PxMessage]::balloon.balloonTipText = $message [PxMessage]::balloon.balloonTipIcon = $messageIcon
        [PxMessage]::balloon.visible = $true [PxMessage]::balloon.ShowBalloonTip(0) [PxMessage]::balloon.Dispose } } $processIcon = [System.Drawing.Icon]::ExtractAssociatedIcon($(Get-Process -id:$PID | Select-Object -expandProperty:path))
$message = [PxMessage]::GetInstance($processIcon)

$message.SendMessage('Title', "$(Get-Date)", 'Info')

Eu tenho duas perguntas:

1: Por que [PxMessage]::balloon = New-Object Windows.Forms.NotifyIconfunciona, mas [PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new()não ( Unable to find typeerro)? E isso sugere que o uso [Type]::new()ainda não é totalmente suportado, e por uma questão de consistência, é melhor usar New-Object em todos os lugares? Ou pelo menos em todas as minhas aulas?

2: Eu gostaria de digitar minhas propriedades e parâmetros, mas também recebo Unable to find typeerros quando digito as propriedades $balloone $defaultIcon, ou se digito o $processIconparâmetro no GetInstancemétodo.

Obviamente, posso digitar Propriedades, mesmo com meu tipo definido. Então, o que há de diferente nos dois [System.Drawing]& [System.Windows.Forms], e isso é um bug ou um recurso? E existem outros tipos que se comportam de forma semelhante?

Respostas

4 MathiasR.Jessen Oct 22 2020 at 22:44

Esta é essencialmente uma condição de corrida!

Quando o PowerShell começa a executar um arquivo de script, ele passa por 3 fases:

  • Análise
  • Compilação
  • Execução

A primeira coisa que é processada na fase de compilação (portanto, antes mesmo de começar a execução) é:

  • Todas as usingdeclarações no início do script
  • Quaisquer definições de tipo - qualquer classou enumpalavra-chave - são compiladas separadamente

Portanto, os erros relacionados à resolução do [Windows.Forms.NotifyIcon]tipo literal dentro da definição de classe são realmente lançados antes de Add-Type -AssemblyName:System.Windows.Forms ter a chance de executar!

Algumas opções:

Scripts aninhados

Escreva um script de carregador separado que carregue a dependência:

# loader.ps1
Add-Type -AssemblyName System.Windows.Forms,System.Drawing
. .\scriptWithTypes.ps1
# scriptWithTypes.ps1
class ClassDependentOnForms
{
  [Windows.Forms.NotifyIcon]$BalloonTipIcon
}

Com módulos

Com os módulos, é um pouco mais simples gerenciar dependências antes de compilar as definições de tipo personalizado - basta adicionar os nomes dos conjuntos RequiredAssembliesao manifesto do módulo:

New-ModuleManifest ... -RootModule moduleWithTypes.psm1 -RequiredAssemblies System.Windows.Forms,System.Drawing

Carregar do disco com using assembly ...

Se o caminho do assembly necessário for conhecido, você pode carregá-lo em tempo de análise com uma using assemblyinstrução:

using assembly '.\path\to\System.Drawing.dll'
using assembly '.\path\to\System.Windows.Forms.dll'

class FormsDependentClass
{
  [Windows.Forms.NotifyIcon]$BallonTipIcon
}

Para o seu caso de uso, este último não é muito atraente porque você precisaria codificar o assembly em vez de apenas carregá-lo do GAC pelo nome.


Por que isso ocorre em primeiro lugar?

Esse comportamento pode ser um pouco confuso porque tudo o mais no PowerShell é apenas uma interpretação direta "uma instrução por vez".

O motivo dessa exceção é permitir que scripts e funções encapsulem tipos de parâmetros personalizados:

param(
  [CustomEnumType]$Option ) begin { enum CustomEnumType { None Option1 Option2 } } end { # do something based on $Option
}

Sem esta compilação preemptiva de CustomEnumTypeno tempo de análise, o PowerShell seria incapaz de oferecer autocompletar e validação de entrada para o -Optionargumento de parâmetro - porque seu tipo não existiria