I would like to exit this systray program by clicking with the left mouse on the text "Quit.". When I hover, the mouse shows a rotating blue icon and clicking does nothing. What's the problem with the script?
# a systray program, that should be exited (but it doesn't)
# 2023-03-18
$iconPath = "H:\Dropbox\400 - Scriptprogrammierung\Powershell\Taskleiste mit Wochentag\icons\ico\Logo.ico" # icon path
Write-Host -ForegroundColor Yellow $iconPath
$tooltip = "This is a text."
# NotifyIcon-object
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
$notifyIcon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($iconPath)
$notifyIcon.Text = $tooltip
########################
# Here seems to be the problem...
$contextMenu = New-Object System.Windows.Forms.ContextMenuStrip
$menuItemExit = New-Object System.Windows.Forms.ToolStripMenuItem
$menuItemExit.Text = "Quit."
$contextMenu.Items.Add($menuItemExit)
$notifyIcon.ContextMenuStrip = $contextMenu
$menuItemExit.add_Click({ $notifyIcon.Dispose(); exit })
########################
# Show icon in systray
$notifyIcon.Visible = $true
# Loop
while ($true) {
$notifyIcon.Text = $tooltip
Start-Sleep -Seconds 60 # wait 60 seconds
}
The crucial changes required are:
Do not call
exitdirectly from the.add_Click()event-handler script block: It will crash your script.The code below also moves
$notifyIcon.Dispose()out of this script block and instead moves it into afinallyblock of atrystatement that wraps thewhileloop, and notifies the loop of the desire to quit via a script-level$donevariable, which is set via$script:done = $truefrom the event handler (which runs in a child scope of the script).This ensures that using Ctrl-C to terminate the script also properly disposes of the icon and removes it from the notification area.
In your
whileloop, you must periodically call[System.Windows.Forms.Application]::DoEvents()in order to allow WinForms to process its UI events. Sleep only a short while between these calls, so as to keep the UI responsive - a long sleep would block event processing for the duration of that sleep.ApplicationContext) and use it in a single, blocking[System.Windows.Forms.Application]::Run()call - see this answer.