kpogue's helpful answer explains your problem well and offers a solution that is effective, but suboptimal due to relying on global variables.
Let me suggest a different approach, where you use a function to simply define the form and return that definition, on which you can call the .Show()
and .Close()
methods as needed, but note that the .Show()
method is overridden via Add-Member
to include a call to [System.Windows.Forms.Application]::DoEvents()
, so as to ensure that the form is properly drawn.
function New-PopUpForm {
Add-Type -AssemblyName System.Windows.Forms
# Create the form.
$objForm = New-Object System.Windows.Forms.Form -Property @{
Text = "Test"
Size = New-Object System.Drawing.Size 220, 100
StartPosition = 'CenterScreen' # Center on screen.
FormBorderStyle = 'FixedSingle' # fixed-size form
# Remove interaction elements (close button, system menu).
ControlBox = $false
}
# Create a label...
$objLabel = New-Object System.Windows.Forms.Label -Property @{
Location = New-Object System.Drawing.Size 80, 20
Size = New-Object System.Drawing.Size 100, 20
Text = "Hi there!"
}
# ... and add it to the form.
$objForm.Controls.Add($objLabel)
# Override the `.Show()` method to include
# a [System.Windows.Forms.Application]::DoEvents(), so as
# to ensure that the form is properly drawn.
$objForm | Add-Member -Force -Name Show -MemberType ScriptMethod -Value {
$this.psbase.Show() # Call the original .Show() method.
# Activate the form (focus it).
$this.Activate()
[System.Windows.Forms.Application]::DoEvents() # Ensure proper drawing.
}
# Since this form is meant to be called with .Show() but without
# a [System.Windows.Forms.Application]::DoEvents() *loop*, it
# it is best to simply *hide* the cursor (mouse pointer), so as not
# to falsely suggest that interaction with the form is possible
# and so as not to end up with a stuck "wait" cursor (mouse pointer) on
# the first instantiation in a session.
[System.Windows.Forms.Cursor]::Hide()
# Return the form.
return $objForm
}
You can then use the function as follows:
$form = New-PopupForm
$form.Show()
# ...
$form.Close()
Note:
Once you call .Close()
, the form instance stored in $form
is disposed of and cannot be reused - simply call New-PopupForm
again to create a new instance.
If the PowerShell session running your script exits, any pop-up windows created in the session close automatically.
Caveats:
Note that, due to use of the .Show()
method (without additional effort), the user won't be able to interact with the pop-up window, notably not even in order to move the window or close it manually.
- If, such as in your case, the window isn't meant to be interacted with, this isn't a problem, however.
- Setting
ControlBox = $false
above removes the window's close button and system menu so as to make it obvious that no interaction is possible.
- Hiding the cursor (mouse pointer) with
[System.Windows.Forms.Cursor]::Hide()
serves the same purpose.
- Note: Not hiding the cursor causes the "wait" cursor to be shown (indicating that the form is busy processing something) indefinitely while mousing over the form, but curiously only for the very first instance created in a session. The only way to avoid it that I know of is to enter an event loop after calling
.Show()
, as discussed in the last bullet point.
By contrast, .ShowDialog()
would allow interaction, but blocks further execution of your script until the window is closed by the user.
If you need to combine the two approaches - allowing the user to interact with the window while continuing to do processing in your PowerShell script - you need to call [System.Windows.Forms.Application]::DoEvents()
in a loop, as shown in this answer.
- If you use this approach, you should remove the
[System.Windows.Forms.Cursor]::Hide()
call from the function.