Tipos de .NET en clases de PowerShell
Estoy tratando de entender las mejores prácticas de la clase PowerShell y me encuentro con cierta confusión con una clase simple para manejar los consejos de globo.
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')
Tengo dos preguntas:
1: ¿Por qué [PxMessage]::balloon = New-Object Windows.Forms.NotifyIconfunciona, pero [PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new()no ( Unable to find typeerror)? ¿Y esto sugiere que el uso [Type]::new()aún no es totalmente compatible y, en aras de la coherencia, es mejor usar New-Object en todas partes? ¿O al menos en todas partes en mis propias clases?
2: Me gustaría escribir mis propiedades y parámetros, pero también obtengo Unable to find typeerrores cuando escribo las propiedades $balloon& $defaultIcon, o si escribo el $processIconparámetro en el GetInstancemétodo.
Obviamente puedo escribir Propiedades, incluso con mi tipo definido. Entonces, ¿qué hay de diferente en los dos [System.Drawing]&? ¿ [System.Windows.Forms]Es esto un error o una característica? ¿Y hay otros tipos que se comportan de manera similar?
Respuestas
¡Esta es esencialmente una condición de carrera!
Cuando PowerShell comienza a ejecutar un archivo de script, pasa por 3 fases:
- Analizando
- Compilacion
- Ejecución
Lo primero que se procesa en la fase de compilación (es decir, incluso antes de que comience la ejecución) es:
- Todas las
usingdeclaraciones en la parte superior del guión - Cualquier definición de tipo (cualquiera
classoenumpalabras clave) se compila por separado
¡Así que los errores relacionados con la resolución del [Windows.Forms.NotifyIcon]tipo literal dentro de la definición de clase se lanzan antes de que Add-Type -AssemblyName:System.Windows.Forms tenga la oportunidad de ejecutarse!
Un par de opciones:
Guiones anidados
Escriba un script de cargador separado que cargue la dependencia:
# loader.ps1
Add-Type -AssemblyName System.Windows.Forms,System.Drawing
. .\scriptWithTypes.ps1
# scriptWithTypes.ps1
class ClassDependentOnForms
{
[Windows.Forms.NotifyIcon]$BalloonTipIcon
}
Con módulos
Con los módulos, es un poco más sencillo administrar las dependencias antes de compilar las definiciones de tipo personalizadas; solo agregue los nombres de ensamblado RequiredAssembliesen el manifiesto del módulo:
New-ModuleManifest ... -RootModule moduleWithTypes.psm1 -RequiredAssemblies System.Windows.Forms,System.Drawing
Cargar desde disco con using assembly ...
Si se conoce la ruta del ensamblado requerido, puede cargarlo en el momento del análisis con una using assemblydeclaración:
using assembly '.\path\to\System.Drawing.dll'
using assembly '.\path\to\System.Windows.Forms.dll'
class FormsDependentClass
{
[Windows.Forms.NotifyIcon]$BallonTipIcon
}
Para su caso de uso, este último no es muy atractivo porque necesitaría codificar el ensamblado en lugar de simplemente cargarlo desde el GAC por su nombre.
¿Por qué ocurre esto en primer lugar?
Este comportamiento puede ser un poco confuso porque todo lo demás en PowerShell es simplemente una interpretación sencilla de "una declaración a la vez".
El motivo de esta excepción es permitir que los scripts y funciones encapsulen tipos de parámetros personalizados:
param(
[CustomEnumType]$Option ) begin { enum CustomEnumType { None Option1 Option2 } } end { # do something based on $Option
}
Sin esta compilación preventiva de CustomEnumTypeen tiempo de análisis, PowerShell no podría ofrecer autocompletado y validación de entrada para el -Optionargumento del parámetro, porque su tipo no existiría