I have working code which will change tray icon depending on notepad process existence.
Set-StrictMode -Version Latest
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
function Test-Notepad {
[bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}
function menu1clickEvent ($label) {
# Build Form object
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
$Form.Controls.Add($Label) # Add label to form
$form.ShowDialog()| Out-Null # Show the Form
}
function menu4clickEvent ($icon) {
$icon.Visible = $false
[System.Windows.Forms.Application]::Exit()
}
function create_taskbar_menu($label){
# Create menu item
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
# Create menu item
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
$Main_Tool_Icon.ContextMenu = $contextmenu
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
$Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)
# Add click events
$MenuItem1.add_Click({menu1clickEvent $label})
$MenuItem2.add_Click({menu4clickEvent $Main_Tool_Icon})
}
# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing') | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null
# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:/WINDOWS/system32/notepad.exe")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:/WINDOWS/system32/resmon.exe")
# create tray icon
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true
# Build Label object
$Label = New-Object System.Windows.Forms.Label
$Label.Name = "labelName"
$Label.AutoSize = $True
# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
if ($Label){
$Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
"Notepad is running", $onlineIcon
} else {
"Notepad is NOT running", $offlineIcon
}
}
})
$timer.Start()
create_taskbar_menu $label
# ERROR:
# create_taskbar_menu2 $label $Main_Tool_Icon
# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)
# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://learn.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()
# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
[System.Windows.Forms.Application]::Run($appContext)
}
finally
{
foreach ($component in $timer, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
{
# The following test returns $false if $component is
# $null, which is really what we're concerned about
if ($component -is [System.IDisposable])
{
$component.Dispose()
}
}
Stop-Process -Id $PID
}
My goal is to refactor create_taskbar_menu method to use passed variables and not the global one, here is my code:
function create_taskbar_menu2($label, $trayIcon){
# Create menu item
$MenuItem1 = New-Object System.Windows.Forms.MenuItem
$MenuItem1.Text = "Menu Item 1"
# Create menu item
$MenuItem2 = New-Object System.Windows.Forms.MenuItem
$MenuItem2.Text = "Exit"
# Add menu items to context menu
$contextmenu = New-Object System.Windows.Forms.ContextMenu
# Set icton properties
$trayIcon.ContextMenu = $contextmenu
$trayIcon.contextMenu.MenuItems.AddRange($MenuItem1)
$trayIcon.contextMenu.MenuItems.AddRange($MenuItem2)
# Add click events
$MenuItem1.add_Click({menu1clickEvent $label})
$MenuItem2.add_Click({menu4clickEvent $trayIcon})
}
I call this method as create_taskbar_menu2 $label $Main_Tool_Icon instead of create_taskbar_menu $label. Problem is that after thisrefactor my application crashes, when I click on taskbar exit button. Following message appears:
Unhandled exception has occurred in your application. If you click Continue, the application will ignore this error and attempt to continue. If you click Quit, the application will close immediately.
The variable '$trayIcon' cannot be retrieved because it has not been set.
I run my code from cmd.exe as powershell -f script.ps1 And I get nothing to console. What I'm, doing wrong and how can I debug such .NET Framework exceptions?
PS: here is log which I can copy from dialog error message box (it is a bit longer so I've pasted it on pastebin)
$Main_Tool_Icontocreate_taskbar_menu2. There is no reason why$Main_Tool_Iconshouldn't be visible forcreate_taskbar_menu2or is there?