merlinuwe
merlinuwe

Reputation: 311

How to exit a powershell script that runs in the systray?

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
}


Upvotes: 4

Views: 1285

Answers (1)

mklement0
mklement0

Reputation: 439872

The crucial changes required are:

  • Do not call exit directly 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 a finally block of a try statement that wraps the while loop, and notifies the loop of the desire to quit via a script-level $done variable, which is set via $script:done = $true from 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 while loop, 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.

# Load the WinForms assembly.
Add-Type -AssemblyName System.Windows.Forms

# Use PowerShell's icon in this example; be sure to use a full path.
$iconPath = (Get-Process -Id $PID).Path
$tooltip = "This is a text."

# Construct the NotifyIcon object.
$notifyIcon = [System.Windows.Forms.NotifyIcon]::new()
$notifyIcon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($iconPath)
$notifyIcon.Text = $tooltip

# Define a script-level variable that indicates whether the
# script should be exited, to be set to $true from the .add_Click() event handler.
$done = $false

$contextMenu = [System.Windows.Forms.ContextMenuStrip]::new()
$menuItemExit = [System.Windows.Forms.ToolStripMenuItem]::new()
$menuItemExit.Text = "Quit."
$null = $contextMenu.Items.Add($menuItemExit)
$notifyIcon.ContextMenuStrip = $contextMenu
# Set the script-level $done variable to $true when the menu item is clicked.
$menuItemExit.add_Click({ $script:done = $true })

# Show icon in systray (notification area)
$notifyIcon.Visible = $true

Write-Verbose -Verbose @"
Adding a PowerShell icon to notification area (system tray).
Use the icon's context menu to quit this script, 
or press Ctrl-C in the console window.
"@

# Loop
try {
  while (-not $done) {
      # IMPORTANT: Make WinForms process its events.
      [System.Windows.Forms.Application]::DoEvents()
      # Sleep just a little, to keep the UI responsive.
      # Note:
      #   In theory, you could perform other tasks here,
      #   as long as they complete quickly so as to still
      #   allow frequent enough ::DoEvents() calls.
      Start-Sleep -MilliSeconds 100
  }
}
finally {
  # Dispose of the notify icon, which also removes it from display.
  $notifyIcon.Dispose()
  Write-Verbose -Verbose 'Exiting.'
}

Upvotes: 7

Related Questions