Tray Icon Context Menu without hidden Form - powershell

I've been experimenting with tray icons & context menus in PowerShell for some time. However, i can only get the context menu to work correctly when a Form is called in the same script.
Here is a small example:
Add-Type -AssemblyName "System.Windows.Forms"
$objForm = New-Object System.Windows.Forms.Form
$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$objContextMenu = New-Object System.Windows.Forms.ContextMenu
$objExitMenuItem = New-Object System.Windows.Forms.MenuItem
$objExitMenuItem.Index = 1
$objExitMenuItem.Text = "Exit"
$objExitMenuItem.add_Click({
$objForm.Close()
$objNotifyIcon.visible = $false
})
$objContextMenu.MenuItems.Add($objExitMenuItem) | Out-Null
$objNotifyIcon.Icon = "$PSScriptRoot\win.ico"
$objNotifyIcon.Text = "Context Menu"
$objNotifyIcon.ContextMenu = $objContextMenu
$objForm.ContextMenu = $objContextMenu
#Enabling Icon in Taskbar
$objNotifyIcon.Visible = $true
#Hiding Form as best as possible
$objForm.Visible = $false
$objForm.WindowState = "minimized"
$objForm.ShowInTaskbar = $false
$objForm.add_Closing({ $objForm.ShowInTaskBar = $False })
$objForm.ShowDialog()
As soon as the Form componets are removed, the Context menu wont work correctly.
Does anyone know why you need this Form to be loaded and is there a way around it?

System.Windows.Forms.ApplicationContext is what you need to use to acheive that.

Related

Text parameter not working with Powershell Progress Bar

I've been looking around for the past half hour on how to show the text inside of a GUI ProgressBar in Powershell, and everything I've tried has failed. I've even been referencing MSoft docs on it.
Am I doing something wrong? How do I add in the text?
This isn't my full script or exactly how I'll be using it - I just made an example so I could try to get it working.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Size = '500,300'
$form.StartPosition = 'CenterScreen'
$form.Topmost = $true
$computerList = 'server01', 'server02', 'server03', 'server04', 'server05', 'server06', 'server07', 'server08', 'server09', 'server10'
$progressbar1 = New-Object System.Windows.Forms.ProgressBar
$progressbar1.Size = '300, 20'
$progressbar1.Location = '20,60'
$progressbar1.Text = "Processing..."
$progressbar1.Maximum = $computerList.Count
$progressbar1.Step = 1
$progressbar1.Value = 0
foreach ($computer in $computerList){
$progressbar1.PerformStep()
}
$form.Controls.Add($progressbar1)
$form.ShowDialog()
I guess the easiest way is to have a Label control above the progressbar and update the text in there:
$progressLabel = New-Object System.Windows.Forms.Label
$progressLabel.Size = '300, 20'
$progressLabel.Location = '20,40'
$progressLabel.Text = "Processing..."
$form.Controls.Add($progressLabel)
foreach ($computer in $computerList){
$progressLabel.Text = "Doing stuff on computer '$computer'.."
$progressbar1.PerformStep()
# perform your action on $computer here
}

Ms Powershell 5: Displaying highlighted item from a Listbox

I want to be able to display the currently highlighted item from a scrolling list box, without having to click it.
I have set up the list box so that only one item is displayed. The selection changes via the scroll buttons. I want to be able to read the item displayed without having to click it - so .Add_Click is not suitable.
Example code below:
Many thanks
# Listbox test
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# set up form
$form = New-Object System.Windows.Forms.Form
$form.Text = "List Box Test"
$form.Size = New-Object System.Drawing.Size(500,200)
$form.StartPosition = "Manual"
$form.Location = New-Object System.Drawing.Point(10, 10)
# Set up list
$List1 = New-Object System.Windows.Forms.ListBox
$List1.Location = New-Object System.Drawing.Point(10, 10)
$List1.Height = 30
$List1.Width = 150
$List1.font = 'arial, 16pt'
[void] $List1.Items.Add('Clubs')
[void] $List1.Items.Add('Diamonds')
[void] $List1.Items.Add('Hearts')
[void] $List1.Items.Add('Spades')
$form.Controls.Add($List1)
# Question: how do I get the highlighted item from the list box to show here, before it is clicked?
# ----------------------------------------------------------------------------------------
# display selection
$TextBox1 = New-Object System.Windows.Forms.TextBox
$TextBox1.Location = New-Object System.Drawing.Point(10,50)
$TextBox1.Size = New-Object System.Drawing.Size(300,30)
$TextBox1.Font = 'arial, 12pt'
$TextBox1.text = "Highlighted item to show here ..."
$form.Controls.Add($TextBox1)
# ----------------------------------------------------------------------------------------
# set up button
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Location = New-Object System.Drawing.Point(10,100)
$Button1.Size = New-Object System.Drawing.Size(50,20)
$Button1.Text = "OK"
$Button1.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Controls.Add($Button1)
$form.Topmost = $true
$null = $form.ShowDialog()
Just add a SelectedIndexChanged event handler to the listbox:
$List1.Add_SelectedIndexChanged({
$TextBox1.Text = $this.SelectedItem.ToString() # or do: $this.items[$this.SelectedIndex].ToString()
})
Since a Listbox only contains text, you could leave out the .ToString().
P.S. Don't forget to remove the form from memory when all done with
$form.Dispose()

How to ensure Forms are the top window

I have a PS script that implements System.Windows.Forms in order to query technicians for some data.
I create the forms and set both .Topmost and .TopLevel to true in an attempt to have them show up over the Powershell window, but they continue to (for some reason inconsistently) appear behind the Powershell window. This slows down the process and is confusing in its inconsistency.
If anyone knows how to ensure these windows stay top without a mountain of code larger than the script itself that would be incredibly useful. I'll include the code I use to build one of the basic forms below.
Any simple solution that will allow these Forms to appear over the Powershell window is appreciated. It could even just minimize the PS window, but I don't want to launch without the window as we need it open. Thanks.
$form.Text = 'Computer Name Entry'
$form.Size = New-Object System.Drawing.Size(550,400)
$form.StartPosition = 'CenterScreen'
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Point(75,300)
$okButton.Size = New-Object System.Drawing.Size(75,23)
$okButton.Text = 'OK'
$okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $okButton
$form.Controls.Add($okButton)
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(400,40)
$label.Text = 'Text is here:'
$form.Controls.Add($label)
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Point(10,70)
$textBox.Size = New-Object System.Drawing.Size(400,20)
$form.Controls.Add($textBox)
$form.Topmost = $true
$form.TopLevel = $true
$form.Add_Shown({$textBox.Select()})
$result = $form.ShowDialog()
if ($result -eq [System.Windows.Forms.DialogResult]::OK)
{
Do-Stuff
}
The easiest way I know of forcing the form to be topmost is to open it with a new temporary form that is TopMost as parameter for ShowDialog().
First, from your code remove the lines $form.Topmost = $true and $form.TopLevel = $true
Next, show your form like this:
# force the dialog TopMost by creating a temporary parent window for this form
$result = $form.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true }))
Another way of doing this is to use a piece of C# to return a windowhandle which implements the IWin32Window interface.
Then use this handle as the owner window for this form in the .ShowDialog() method of the form.
For this method, also remove the lines $form.Topmost = $true and $form.TopLevel = $true from your original code.
$iWin32Code = #"
using System;
using System.Windows.Forms;
public class Win32Window : IWin32Window {
public Win32Window(IntPtr handle) {
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
"#
if (-not ([System.Management.Automation.PSTypeName]'Win32Window').Type) {
Add-Type -TypeDefinition $iWin32Code -ReferencedAssemblies System.Windows.Forms.dll
}
Now, using that code, create a handle for the currently running PowerShell process
# get the owner handle from this PowerShell process
$ownerHandle = New-Object Win32Window -ArgumentList ([System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle)
# and use that in the ShowDialog method as argument
$result = $form.ShowDialog($ownerHandle)
P.S. do not forget to clear your form from memory after you are done with it by calling
$form.Dispose()

Powershell multiple dynamic buttons

I built a dynamic Powershell GUI but i have trouble getting my buttons to work correctly.
I created a function to add texboxes and buttons. However they do not correspond correctly at this point. because i'm not sure how to bind the add_click to a specific textbox.
Here is the example code:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$Form = New-Object System.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size(300,300)
$ButtonAdd = New-Object System.Windows.Forms.Button
$ButtonAdd.Location = New-Object System.Drawing.Point(20,20)
$ButtonAdd.Size = New-Object System.Drawing.Size(50,30)
$ButtonAdd.Text = 'Add'
$Form.Controls.Add($ButtonAdd)
$global:Counter = 0
$global:ButtonPosY = 20
$global:DefaultTextValue = ""
$ButtonAdd.Add_Click{Add-Fields}
Function Create-Button {
param ( $ButtonPosY, $TextBoxPosY)
$TextBoxField = New-Object System.Windows.Forms.TextBox
$TextBoxField.Location = New-Object System.Drawing.Point(181,$TextBoxPosY)
$TextBoxField.Size = New-Object System.Drawing.Size(50,30)
$TextBoxField.Text = $global:DefaultTextValue
New-Variable -name TextBoxField$global:Counter -Value $TextBoxField -Scope Global -Force
$Form.controls.Add($((Get-Variable -name TextBoxField$global:Counter).value))
$ButtonClick = New-Object System.Windows.Forms.Button
$ButtonClick.Location = New-Object System.Drawing.Point(100,$global:ButtonPosY)
$ButtonClick.Size = New-Object System.Drawing.Size(50,30)
$ButtonClick.Text = 'Click'
New-Variable -name ButtonClick$global:Counter -Value $ButtonClick -Scope Global -Force
$Form.controls.Add($((Get-Variable -name ButtonClick$global:Counter).value))
$ButtonClick.Add_Click({
$((Get-Variable -name TextBoxField$global:Counter -Scope Global).value).Text = 'hello'
})
}
Function Add-Fields {
$global:Counter = $global:Counter + 1
$global:ButtonPosY = $global:ButtonPosY + 40
Create-Button -TextBoxPosY $global:ButtonPosY -ButtonPosY $global:ButtonPosY
}
Create-Button -TextBoxPosY 21 -ButtonPosY 20
$Form.ShowDialog()
If the click button is pressed each time after new input is added everything works fine. however if multiple input fields are added first and then the click button is pressed the code breaks.
The Problem is here:
$ButtonClick.Add_Click({...})
I don't know how to add the counter (in my case $global:Counter) to the $ButtonClick variable as in $ButtonClick0, $ButtonClick1, ... etc. So right now when i add more buttons by calling the function the input will always be applied to the last added textbox since the add_click is not linked to a individual $ButtonClick0 variable.
How would this be done right?
After some serious reading i figured it out.
A name needs to be added to $ButtonClick
$ButtonClick.Name = $global:Counter
And the Add_Click event should look the following:
$ButtonClick.Add_Click({
[System.Object]$Sender = $args[0]
$((Get-Variable -name ('TextBoxField' + [int]$Sender.Name)).value).Text = 'hello'
})
However i don't know if this is how it should be done. Please post your solution if it looks different.

How to create a popup message in Powershell without buttons

I'm trying to create a message dialogue in Powershell where the user has no option to action on the message as that is the intention. So the message will have the X button grayed along with the buttons (not showing buttons are even better).
The closest I could reach was disabling the X via below code:
$wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop
$wshell.Popup("Aborted",0,"ERROR!",48+4)
But cannot figure out disabling button part. Below MS articles were of little help as well:
http://blogs.technet.com/b/heyscriptingguy/archive/2006/07/27/how-can-i-display-a-message-box-that-has-no-buttons-and-that-disappears-after-a-specified-period-of-time.aspx
https://msdn.microsoft.com/en-us/library/x83z1d9f(v=vs.84).aspx
Referred to few other articles over net some even suggesting custom made buttons using HTML, or VB library. But not what I was looking for.
Any help/hint/suggestion would be deeply appreciated.
Regards,
Shakti
Dig into the .NET Windows.Forms namespace, you can make pretty much any kind of window you want with that:
https://msdn.microsoft.com/en-us/library/system.windows.forms.aspx
Here's a quick sample window w/ no buttons that can't be moved/closed by the user, but closes itself after 5 seconds:
Function Generate-Form {
Add-Type -AssemblyName System.Windows.Forms
# Build Form
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Test"
$objForm.Size = New-Object System.Drawing.Size(220,100)
# Add Label
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(80,20)
$objLabel.Size = New-Object System.Drawing.Size(100,20)
$objLabel.Text = "Hi there!"
$objForm.Controls.Add($objLabel)
# Show the form
$objForm.Show()| Out-Null
# wait 5 seconds
Start-Sleep -Seconds 5
# destroy form
$objForm.Close() | Out-Null
}
generate-form
Using the script above as a launching point I'm attempting to make a function that will allow me to popup a please wait message run some more script then close the popup
Function Popup-Message {
param ([switch]$show,[switch]$close)
Add-Type -AssemblyName System.Windows.Forms
# Build Form
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Test"
$objForm.Size = New-Object System.Drawing.Size(220,100)
# Add Label
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(80,20)
$objLabel.Size = New-Object System.Drawing.Size(100,20)
$objLabel.Text = "Hi there!"
$objForm.Controls.Add($objLabel)
If ($show)
{
$objForm.Show() | Out-Null
$global:test = "Show"
}
If ($close)
{
# destroy form
$objForm.Close() | Out-Null
$global:test = "Close"
}
}
I can then get the popup to display by:
Popup-Message -show
At this point I can see the $test variable as Show
But when I try to close the window with:
Popup-Message -close
But the popup window will not close
If I look at $test again it will show as Close
I'm assuming this has something to do with keeping the function in the Global Scope but I can't figure out how to do this with the form