Reputation: 6863
I am trying to get my head around PowerShell Class best practices, and running into some confusion with a simple class to handle Balloon Tips.
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')
I have two questions:
1: Why does [PxMessage]::balloon = New-Object Windows.Forms.NotifyIcon
work, but [PxMessage]::balloon = [Windows.Forms.NotifyIcon]::new()
does not (Unable to find type
error)? And does this suggest that using [Type]::new()
is not yet fully supported, and for consistency sake I am better off using New-Object everywhere? Or at least everywhere in my own Classes?
2: I would like to type my properties & parameters, but I also get Unable to find type
errors when I type the $balloon
& $defaultIcon
properties, or if I type the $processIcon
parameter in the GetInstance
method.
Obviously I can type Properties, even with my type being defined. So what is different about the two [System.Drawing]
& [System.Windows.Forms]
, and is this a bug, or a feature? And are there other types that behave similarly?
Upvotes: 4
Views: 698
Reputation: 174435
This is essentially a race condition!
When PowerShell starts executing a script file, it goes through 3 phases:
The very first thing that is processed in the compilation phase, (so before execution even begins) is:
using
statements at top of the scriptclass
or enum
keywords - are compiled separatelySo the errors related to resolving the [Windows.Forms.NotifyIcon]
type literal inside the class definition are actually thrown before Add-Type -AssemblyName:System.Windows.Forms
gets a chance to ever run!
Couple of options:
Write a separate loader script that loads the dependency:
# loader.ps1
Add-Type -AssemblyName System.Windows.Forms,System.Drawing
. .\scriptWithTypes.ps1
# scriptWithTypes.ps1
class ClassDependentOnForms
{
[Windows.Forms.NotifyIcon]$BalloonTipIcon
}
With modules it's a bit simpler to manage dependencies ahead of compiling the custom type definitions - just add the assembly names as RequiredAssemblies
to the module manifest:
New-ModuleManifest ... -RootModule moduleWithTypes.psm1 -RequiredAssemblies System.Windows.Forms,System.Drawing
using assembly ...
If the required assembly's path is known, you can load it at parse time with a using assembly
statement:
using assembly '.\path\to\System.Drawing.dll'
using assembly '.\path\to\System.Windows.Forms.dll'
class FormsDependentClass
{
[Windows.Forms.NotifyIcon]$BallonTipIcon
}
For your use case, this last one is not very attractive because you'd need to hardcode the assembly instead of just loading it from the GAC by name.
This behavior might be slightly confusing because everything else in PowerShell is just straightforward "one statement at a time"-interpretation.
The reason for this exception is to allow scripts and functions to encapsulate custom parameter types:
param(
[CustomEnumType]$Option
)
begin {
enum CustomEnumType {
None
Option1
Option2
}
}
end {
# do something based on $Option
}
Without this preemptive compilation of CustomEnumType
at parse time, PowerShell would be unable to offer autocompletion and input validation for the -Option
parameter argument - because it's type would not exist
Upvotes: 7