Tipi .NET nelle classi PowerShell

Oct 23 2020

Sto cercando di capire le migliori pratiche della classe PowerShell e mi imbatto in una certa confusione con una semplice classe per gestire i suggerimenti per i palloncini.

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')

Ho due domande:

1: Perché [PxMessage]::balloon = New-Object Windows.Forms.NotifyIconfunziona, ma [PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new()non ( Unable to find typeerrore)? E questo suggerisce che l'utilizzo [Type]::new()non è ancora completamente supportato, e per motivi di coerenza è meglio usare New-Object ovunque? O almeno ovunque nelle mie classi?

2: Vorrei digitare le mie proprietà e parametri, ma ricevo anche Unable to find typeerrori quando digito le proprietà $balloon& $defaultIcono se digito il $processIconparametro nel GetInstancemetodo.

Ovviamente posso digitare Proprietà, anche se il mio tipo è definito. Quindi cosa c'è di diverso tra i due [System.Drawing]& [System.Windows.Forms], e questo è un bug o una caratteristica? E ci sono altri tipi che si comportano in modo simile?

Risposte

4 MathiasR.Jessen Oct 22 2020 at 22:44

Questa è essenzialmente una condizione di gara!

Quando PowerShell inizia a eseguire un file di script, passa attraverso 3 fasi:

  • Analisi
  • Compilazione
  • Esecuzione

La primissima cosa che viene elaborata nella fase di compilazione , (quindi prima ancora che inizi l'esecuzione) è:

  • Tutte le usingdichiarazioni in cima allo script
  • Qualsiasi definizione di tipo, qualsiasi classo enumparola chiave, viene compilata separatamente

Quindi gli errori relativi alla risoluzione del [Windows.Forms.NotifyIcon]tipo letterale all'interno della definizione della classe vengono effettivamente lanciati prima che Add-Type -AssemblyName:System.Windows.Forms abbia la possibilità di essere eseguito!

Coppia di opzioni:

Script annidati

Scrivi uno script di caricamento separato che carichi la dipendenza:

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

Con moduli

Con i moduli è un po 'più semplice gestire le dipendenze prima di compilare le definizioni di tipo personalizzate: basta aggiungere i nomi degli assembly come RequiredAssembliesal manifest del modulo:

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

Carica da disco con using assembly ...

Se il percorso dell'assembly richiesto è noto, è possibile caricarlo in fase di analisi con using assemblyun'istruzione:

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

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

Per il tuo caso d'uso, quest'ultimo non è molto attraente perché avresti bisogno di codificare l'assembly invece di caricarlo semplicemente dal GAC per nome.


Perché questo accade in primo luogo?

Questo comportamento potrebbe creare confusione perché tutto il resto in PowerShell è solo una semplice interpretazione "un'istruzione alla volta".

Il motivo di questa eccezione è consentire a script e funzioni di incapsulare tipi di parametri personalizzati:

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

Senza questa compilazione preventiva di CustomEnumTypein fase di analisi, PowerShell non sarebbe in grado di offrire il completamento automatico e la convalida dell'input per l' -Optionargomento del parametro, perché il suo tipo non esisterebbe