Типы .NET в классах PowerShell

Oct 23 2020

Я пытаюсь разобраться в передовых методах работы с классом PowerShell и натолкнулся на некоторую путаницу с простым классом для обработки всплывающих подсказок.

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

У меня два вопроса:

1: Почему [PxMessage]::balloon = New-Object Windows.Forms.NotifyIconработает, а [PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new()нет ( Unable to find typeошибка)? И означает ли это, что использование [Type]::new()еще не полностью поддерживается, и для единообразия мне лучше везде использовать New-Object? Или, по крайней мере, везде в моих классах?

2: Я хотел бы ввести свои свойства и параметры, но я также получаю Unable to find typeошибки при вводе свойств $balloon& $defaultIconили при вводе $processIconпараметра в GetInstanceметоде.

Очевидно, я могу ввести Properties, даже если мой тип определен. Так чем же отличается два [System.Drawing]& [System.Windows.Forms], и это ошибка или особенность? И есть ли другие типы, которые ведут себя аналогичным образом?

Ответы

4 MathiasR.Jessen Oct 22 2020 at 22:44

По сути, это состояние гонки!

Когда PowerShell начинает выполнение файла сценария, он проходит 3 фазы:

  • Парсинг
  • Компиляция
  • Исполнение

Самое первое, что обрабатывается на этапе компиляции (то есть еще до начала выполнения):

  • Все usingутверждения в верхней части скрипта
  • Определения любых типов - любые classили enumключевые слова - составляются отдельно.

Таким образом, ошибки, связанные с разрешением [Windows.Forms.NotifyIcon]литерала типа внутри определения класса, на самом деле возникают до того, как Add-Type -AssemblyName:System.Windows.Forms появится шанс когда-либо запустить!

Пара вариантов:

Вложенные скрипты

Напишите отдельный сценарий загрузчика, который загружает зависимость:

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

С модулями

С модулями немного проще управлять зависимостями перед компиляцией определений пользовательского типа - просто добавьте имена сборок RequiredAssembliesв манифест модуля:

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

Загрузить с диска с помощью using assembly ...

Если путь к требуемой сборке известен, вы можете загрузить его во время синтаксического анализа с помощью using assemblyоператора:

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

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

Для вашего варианта использования последний не очень привлекателен, потому что вам нужно жестко закодировать сборку, а не просто загружать ее из GAC по имени.


Почему это вообще происходит?

Такое поведение может немного сбивать с толку, потому что все остальное в PowerShell является простой интерпретацией «по одному утверждению за раз».

Причина этого исключения состоит в том, чтобы разрешить скриптам и функциям инкапсулировать типы настраиваемых параметров:

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

Без этой превентивной компиляции CustomEnumTypeво время синтаксического анализа PowerShell не смог бы предложить автозаполнение и проверку ввода для -Optionаргумента параметра - потому что его тип не существовал бы