.NET w klasach programu PowerShell

Oct 23 2020

Próbuję zapoznać się z najlepszymi praktykami klasy PowerShell i wpadam w pewne zamieszanie z prostą klasą do obsługi wskazówek dotyczących balonów.

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

Mam dwa pytania:

1: Dlaczego [PxMessage]::balloon = New-Object Windows.Forms.NotifyIcondziała, ale [PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new()nie ( Unable to find typebłąd)? I czy to sugeruje, że używanie [Type]::new()nie jest jeszcze w pełni obsługiwane, a ze względu na spójność lepiej jest wszędzie używać New-Object? A przynajmniej wszędzie w moich własnych klasach?

2: Chciałbym wpisać moje właściwości i parametry, ale pojawiają się również Unable to find typebłędy, gdy wpisuję $balloon& $defaultIconproperties, lub gdy wpisuję $processIconparametr w GetInstancemetodzie.

Oczywiście mogę wpisać Właściwości, nawet gdy mój typ jest zdefiniowany. Więc czym różni się te dwa [System.Drawing]& [System.Windows.Forms]i czy jest to błąd, czy funkcja? Czy są inne typy, które zachowują się podobnie?

Odpowiedzi

4 MathiasR.Jessen Oct 22 2020 at 22:44

Zasadniczo jest to stan wyścigu!

Gdy PowerShell rozpoczyna wykonywanie pliku skryptu, przechodzi przez 3 fazy:

  • Rozbiór gramatyczny zdania
  • Kompilacja
  • Wykonanie

Pierwszą rzeczą , która jest przetwarzana w kompilacji fazy (tak przed egzekucja zaczyna nawet) to:

  • Wszystkie usinginstrukcje u góry skryptu
  • Wszelkie definicje typów - dowolne classlub enumsłowa kluczowe - są kompilowane osobno

Więc błędy związane z rozwiązywaniem [Windows.Forms.NotifyIcon]literału typu wewnątrz definicji klasy są w rzeczywistości wyrzucane, zanim będą Add-Type -AssemblyName:System.Windows.Forms miały szansę kiedykolwiek działać!

Kilka opcji:

Zagnieżdżone skrypty

Napisz osobny skrypt modułu ładującego, który ładuje zależność:

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

Z modułami

W przypadku modułów nieco prostsze jest zarządzanie zależnościami przed kompilacją niestandardowych definicji typów - wystarczy dodać nazwy zestawów zgodnie RequiredAssembliesz manifestem modułu:

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

Załaduj z dysku za pomocą using assembly ...

Jeśli ścieżka wymaganego zestawu jest znana, możesz ją załadować w czasie analizy za pomocą using assemblyinstrukcji:

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

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

W twoim przypadku ten ostatni nie jest zbyt atrakcyjny, ponieważ musiałbyś zakodować na stałe zestaw zamiast po prostu ładować go z GAC po nazwie.


Dlaczego tak się dzieje w pierwszej kolejności?

To zachowanie może być nieco zagmatwane, ponieważ wszystko inne w programie PowerShell jest po prostu prostą interpretacją „jednej instrukcji na raz”.

Powodem tego wyjątku jest zezwolenie skryptom i funkcjom na hermetyzację niestandardowych typów parametrów:

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

Bez tej wywłaszczeniowej kompilacji CustomEnumTypew czasie analizy program PowerShell nie byłby w stanie zaoferować autouzupełniania i walidacji danych wejściowych dla -Optionargumentu parametru - ponieważ jego typ nie istniałby