Tipos .NET em classes PowerShell
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
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
classouenumpalavra-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