Is there a way to bring a PoweR Shell popup to the front of the screen?
i use this command to show the popup
$wshell = New-Object -ComObject Wscript.Shell
$Output = $wshell.Popup("text" ,0,"header",0+64)
but when i use form and a button in the form bis supposed to bring up the popup it shows in the back behind the form
the form itself opens in the center and not bringing to front as shows here
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '1370,720'
$Form.text = "Chame Wizard"
$Form.TopMost = $true
$Form.icon = "c:\script\chame.ico"
$FormImage = [system.drawing.image]::FromFile("c:\script\back2.jpg")
$Form.BackgroundImage = $FormImage
$Form.StartPosition = "CenterScreen"
i know i can use balloon popup but i want the user to press OK before the script continues.
Thanks :-)
You can also use one of the overloaded methods of [System.Windows.Forms.MessageBox]::Show() which allows you to add the owner window in order to have the messagebox be topmost to that.
By using $null there, your messagebox will be topmost to all opened windows:
function Show-MessageBox {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false)]
[string]$Title = 'MessageBox in PowerShell',
[Parameter(Mandatory = $true)]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet('OK', 'OKCancel', 'AbortRetryIgnore', 'YesNoCancel', 'YesNo', 'RetryCancel')]
[string]$Buttons = 'OKCancel',
[Parameter(Mandatory = $false)]
[ValidateSet('Error', 'Warning', 'Information', 'None', 'Question')]
[string]$Icon = 'Information',
[Parameter(Mandatory = $false)]
[ValidateRange(1,3)]
[int]$DefaultButton = 1
)
# determine the possible default button
if ($Buttons -eq 'OK') {
$Default = 'Button1'
}
elseif (#('AbortRetryIgnore', 'YesNoCancel') -contains $Buttons) {
$Default = 'Button{0}' -f [math]::Max([math]::Min($DefaultButton, 3), 1)
}
else {
$Default = 'Button{0}' -f [math]::Max([math]::Min($DefaultButton, 2), 1)
}
Add-Type -AssemblyName System.Windows.Forms
# added from tip by [Ste](https://stackoverflow.com/users/8262102/ste) so the
# button gets highlighted when the mouse hovers over it.
[void][System.Windows.Forms.Application]::EnableVisualStyles()
# Setting the first parameter 'owner' to $null lets he messagebox become topmost
[System.Windows.Forms.MessageBox]::Show($null, $Message, $Title,
[Windows.Forms.MessageBoxButtons]::$Buttons,
[Windows.Forms.MessageBoxIcon]::$Icon,
[Windows.Forms.MessageBoxDefaultButton]::$Default)
}
With this function in place, you call it like:
Show-MessageBox -Title 'Important message' -Message 'Hi there!' -Icon Information -Buttons OK
Edit
As asked by Ste, the above function shows the messagebox TopMost. That however does not mean it is Modal. It only means the box is shown on top when first displayed, but can be pushed to the background by activating other windows.
For a real Modal messagebox that cannot be pushed to the background, I use this:
function Show-MessageBox {
[CmdletBinding()]
param(
[parameter(Mandatory = $true, Position = 0)]
[string]$Message,
[parameter(Mandatory = $false)]
[string]$Title = 'MessageBox in PowerShell',
[ValidateSet("OKOnly", "OKCancel", "AbortRetryIgnore", "YesNoCancel", "YesNo", "RetryCancel")]
[string]$Buttons = "OKCancel",
[ValidateSet("Critical", "Question", "Exclamation", "Information")]
[string]$Icon = "Information"
)
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.Interaction]::MsgBox($Message, "$Buttons,SystemModal,$Icon", $Title)
}
Show-MessageBox -Title 'Important message' -Message 'Hi there!' -Icon Information -Buttons OKOnly
Theos' answer is great but if you want the MessageBox to be modal and not allow any more interaction with the $Form then this is the way to go about it.
I've updated this to show how to show a modal messagebox with a $Form.Add_Load event handler should you need a modal message box on execution of the script.
Comments in the code on how to achieve this:
# I've added this as an answer here:
# Edit added the $Form.Add_Load event handler.
# https://stackoverflow.com/questions/59371640/powershell-popup-in-form/67368911#67368911
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '444,55'
$Form.StartPosition = "CenterScreen"
$Form.Text = "..."
$Form.TopMost = $true
$Btn = New-Object System.Windows.Forms.Button
$Btn.Height = $Form.Height-39
$Btn.Text = "DO NOT PRESS..."
$Btn.Width = $Form.Width-15
$Form.Controls.Add($Btn)
$Btn.Add_Click({
# To have a modal messagebox you need to have it called from a form and use: $this.ActiveForm as shown below.
[System.Windows.Forms.MessageBox]::Show($this.ActiveForm, 'Not to!!', 'I Told You..', [Windows.Forms.MessageBoxButtons]::"OK", [Windows.Forms.MessageBoxIcon]::"Warning")
})
# Here you can do an if statement and then fire the message box when the Form loads which will be modal also.
$Form.Add_Load({
[System.Windows.Forms.MessageBox]::Show($this.ActiveForm, 'You can add the message box as an event handler to show a message when the form is loaded...', 'A Message on Form Load Event!!', [Windows.Forms.MessageBoxButtons]::"OK", [Windows.Forms.MessageBoxIcon]::"Warning")
})
# This below needs to be added to focus the dialog when it opens after the $Form.
$Form.Add_Shown({$Form.Activate(); $Btn.Focus()})
$Form.ShowDialog()
Theos great function modified to accept a -Modal switch.
# Mod of Theos' answer here: https://stackoverflow.com/a/59378301/8262102
# Edit by Ste: Can now be shown modal to the parent form with the usage of the
# -Modal switch.
# Mod of Theos' answer here: https://stackoverflow.com/a/59378301/8262102
# Edit by Ste: Can now be shown modal to the parent form with the usage of the
# -Modal switch.
Function Show-MessageBox {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false)]
[string]$Title = 'MessageBox in PowerShell',
[Parameter(Mandatory = $true)]
[string]$Message,
[Parameter(Mandatory = $false)]
[ValidateSet('OK', 'OKCancel', 'AbortRetryIgnore', 'YesNoCancel', 'YesNo', 'RetryCancel')]
[string]$Buttons = 'OKCancel',
[Parameter(Mandatory = $false)]
[ValidateSet('Error', 'Warning', 'Information', 'None', 'Question')]
[string]$Icon = 'Information',
[Parameter(Mandatory = $false)]
[ValidateRange(1,3)]
[int]$DefaultButton = 1,
[Parameter(Mandatory = $false)]
[Switch]$Modal = $false
)
# Determine the possible default button.
if ($Buttons -eq 'OK') {
$Default = 'Button1'
}
elseif (#('AbortRetryIgnore', 'YesNoCancel') -contains $Buttons) {
$Default = 'Button{0}' -f [math]::Max([math]::Min($DefaultButton, 3), 1)
}
else {
$Default = 'Button{0}' -f [math]::Max([math]::Min($DefaultButton, 2), 1)
}
# Create a new form to hold the object and set it properties for the main
# FolderBrowserDialog form.
Add-Type -AssemblyName System.Windows.Forms
$MessageBoxParentForm = New-Object System.Windows.Forms.Form
$MessageBoxParentForm.TopMost = $Modal
Add-Type -AssemblyName System.Windows.Forms
[void][System.Windows.Forms.Application]::EnableVisualStyles()
[System.Windows.Forms.MessageBox]::Show(($MessageBoxParentForm), $Message, $Title,
[Windows.Forms.MessageBoxButtons]::$Buttons,
[Windows.Forms.MessageBoxIcon]::$Icon,
[Windows.Forms.MessageBoxDefaultButton]::$Default)
}
# Non-modal example.
# Show-MessageBox -Title 'Important message' -Message 'Hi there!' -Icon None -Buttons OKCancel
# Modal example.
Show-MessageBox -Modal -Title 'Important message' -Message 'Hi there!' -Icon Warning -Buttons OK
Related
I have the following code that I can run outside of a function and it works just fine.
I am trying to make a form that will be displaying multiple images so I need to format it into a function.
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.StartPosition = "CenterScreen"
$Form.Size = New-Object System.Drawing.Point(800,800)
$Form.text = "Example Form"
$Form.TopMost = $false
$Form.MaximumSize = $Form.Size
$Form.MinimumSize = $Form.Size
$Picture = (get-item ("C:\Users\User\Desktop\t.png"))
$img = [System.Drawing.Image]::Fromfile($Picture)
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $img.Size.Width
$pictureBox.Height = $img.Size.Height
$pictureBox.Image = $img
$pictureBox.Location = [Drawing.Point]::new(15, 15)
$Form.controls.add($pictureBox)
$Form.ShowDialog()
but when I tried to assemble this as a function it is not working
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.StartPosition = "CenterScreen"
$Form.Size = New-Object System.Drawing.Point(800,800)
$Form.text = "Example Form"
$Form.TopMost = $false
$Form.MaximumSize = $Form.Size
$Form.MinimumSize = $Form.Size
function pictureBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[string] $p,
[Parameter (Mandatory = $True)]
[int] $lx,
[Parameter (Mandatory = $True)]
[int] $ly
)
$Picture = (get-item ($p))
$img = [System.Drawing.Image]::Fromfile($Picture)
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Size = [Drawing.Size]::new($img.Size.Width,$img.Size.Height)
$pictureBox.Location = [Drawing.Point]::new($lx, $ly)
$pictureBox.Image = $img
}
$v1 = pictureBox -p "C:\Users\User\Desktop\t.png" -lx 15 -ly 15
$Form.controls.add($v1)
$Form.ShowDialog()
What am I doing wrong?
Your function pictureBox is not returning anything, it creates a new PictureBox instance, updates it's properties but then that object is never outputted, hence $v1 gets assigned AutomationNull.Value.
# Same code for creating the Form here
function pictureBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[string] $p,
[Parameter (Mandatory = $True)]
[int] $lx,
[Parameter (Mandatory = $True)]
[int] $ly
)
$img = [Drawing.Image]::Fromfile($PSCmdlet.GetUnresolvedProviderPathFromPSPath($p))
[Windows.Forms.PictureBox]#{
Size = [Drawing.Size]::new($img.Size.Width, $img.Size.Height)
Location = [Drawing.Point]::new($lx, $ly)
Image = $img
}
}
$v1 = pictureBox -p "C:\Users\User\Desktop\t.png" -lx 15 -ly 15
$Form.controls.add($v1)
$Form.ShowDialog()
I have an application I am making with multiple text boxes and I am trying to clean it up by making a text box function. However the name parameter itself needs to return a variable name and I am just not quite sure how to do that. I tried giving the parameter the
[psvariable] type but that does not seem to be working.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.StartPosition = "CenterScreen"
$Form.Size = New-Object System.Drawing.Point(415,838)
$Form.text = "Test Form"
$Form.TopMost = $false
$Form.MaximumSize = $Form.Size
$Form.MinimumSize = $Form.Size
function textBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[psvariable]$name,
[Parameter (Mandatory = $True)]
[string]$ml,
[Parameter (Mandatory = $True)]
[int]$lx,
[Parameter (Mandatory = $True)]
[int]$ly,
[Parameter (Mandatory = $True)]
[string]$text
)
$name = New-Object system.Windows.Forms.TextBox
$name.multiline = $ml
$name.Size = New-Object System.Drawing.Size(300,60)
$name.location = New-Object System.Drawing.Point($lx,$ly)
$name.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$name.Text = $text
}
textBox -name '$source' -ml $True -lx 15 -ly 100 -text "test it"
$Form.controls.AddRange(#($source))
[void]$Form.ShowDialog()
exit
It's unclear why is the $name parameter there in your function to begin with, I don't see any use for it. The other problem is that your function is not returning anything and your function invocation is not capturing anything either.
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
# Form code here
function textBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[bool] $ml,
[Parameter (Mandatory = $True)]
[int] $lx,
[Parameter (Mandatory = $True)]
[int] $ly,
[Parameter (Mandatory = $True)]
[string] $text
)
[Windows.Forms.TextBox]#{
Multiline = $ml
Size = [Drawing.Size]::new(300,60)
Location = [Drawing.Point]::new($lx, $ly)
Font = [Drawing.Font]::new('Microsoft Sans Serif', 10)
Text = $text
}
}
# Capture here
$txtBox = textBox -ml $True -lx 15 -ly 100 -text "test it"
$Form.Controls.AddRange(#($txtBox))
[void] $Form.ShowDialog()
To clarify further, the $Name Parameter was constraint to be a PSVariable:
[psvariable] $Name = $null
Hence when, in your function's body, you try to assign an instance different than a PSVariable:
$name = New-Object System.Windows.Forms.TextBox
You would receive this error, which is basically telling you that PowerShell cannot convert an instance of TextBox to the constraint type:
Cannot convert the "System.Windows.Forms.TextBox, Text: " value of type "System.Windows.Forms.TextBox" to type "System.Management.Automation.PSVariable".
I am tring to make a form with several images that will open a URL.
Is it possible to add the click event to the function itself or do I have to add it after the function has been called?
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.StartPosition = "CenterScreen"
$Form.Size = New-Object System.Drawing.Point(800,800)
$Form.text = "Example Form"
$Form.TopMost = $false
$Form.MaximumSize = $Form.Size
$Form.MinimumSize = $Form.Size
$Form.BackColor = "#000000"
function pictureBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[string] $p,
[Parameter (Mandatory = $True)]
[int] $lx,
[Parameter (Mandatory = $True)]
[int] $ly,
[Parameter (Mandatory = $True)]
[string] $url
)
$img = [Drawing.Image]::Fromfile($PSCmdlet.GetUnresolvedProviderPathFromPSPath($p))
[Windows.Forms.PictureBox]#{
Size = [Drawing.Size]::new($img.Size.Width, $img.Size.Height)
Location = [Drawing.Point]::new($lx, $ly)
Image = $img
}
Add_Click ( { start-process $url } )
}
$v1 = pictureBox -p "C:\Users\User\Desktop\t.png" -lx 20 -ly 65 -url **THE-URL**
$v1.Add_Click( { start-process **THE-URL** } )
It's possible to do it from the function itself, first you need to instantiate the PictureBox, in the example below, the object is stored in $pbx. After that you can add the Click event to it.
As you may note there is also a GetNewClosure() method invocation, in this case it is required because the ScriptBlock needs to "remember" the value of $url.
function pictureBox {
[CmdletBinding()]
param (
[Parameter (Mandatory = $True)]
[string] $p,
[Parameter (Mandatory = $True)]
[int] $lx,
[Parameter (Mandatory = $True)]
[int] $ly,
[Parameter (Mandatory = $True)]
[string] $url
)
$img = [Drawing.Image]::Fromfile($PSCmdlet.GetUnresolvedProviderPathFromPSPath($p))
$pbx = [Windows.Forms.PictureBox]#{
Size = [Drawing.Size]::new($img.Size.Width, $img.Size.Height)
Location = [Drawing.Point]::new($lx, $ly)
Image = $img
}
$pbx.Add_Click({ Start-Process $url }.GetNewClosure())
$pbx
}
$v1 = pictureBox -p "C:\Users\User\Desktop\t.png" -lx 20 -ly 65 -url "https://stackoverflow.com/"
$Form.Controls.Add($v1)
$Form.ShowDialog()
I'm writing PS Script and following block of code shows dialogbox below windows forms gui.
$btn1 = New-Object Windows.Forms.Button
$btn1.Text = "Wybierz folder projektowy"
$btn1.Location = New-Object System.Drawing.Point(170,140)
$btn1.Size = New-Object System.Drawing.Size(160,20)
$btn1.add_Click({
function Select-Folder($message='Select a folder', $path = 0) {
$object = New-Object -comObject Shell.Application
$object.topmost=$true
$folder = $object.BrowseForFolder(0, $message, 0, $path)
if ($folder -ne $null) {
$folder.self.Path
}
}
$folderPath = Select-Folder 'Select the folder where the move scripts reside'
If ($folderPath) {
Set-Content -Path "C:\Projekty\logs\temp_path.txt" -Value $folderPath.ToString() -Encoding Unicode
write-host $folderPath
get-content -Path "C:\Projekty\logs\temp_path.txt"
}
Else { Write-Host 'I do not have a folder path' }
})
$form_acl.Controls.Add($btn1)
Is there any way to make it display on top?
Here's screenshot of a problem:
You can try this alternative instead:
function Select-Folder {
[CmdletBinding()]
param (
# sets the descriptive text displayed above the tree view control in the dialog box
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Please select a directory.",
# sets the (pre)selected path
[Parameter(Mandatory=$false, Position=1)]
[string]$InitialDirectory,
# sets the root folder where the browsing starts from
[Parameter(Mandatory=$false)]
[System.Environment+SpecialFolder]$RootFolder = [System.Environment+SpecialFolder]::Desktop,
# sets a value indicating whether the 'New Folder' button appears in the folder browser dialog box
[switch]$ShowNewFolderButton
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Message
$dialog.SelectedPath = $InitialDirectory
$dialog.RootFolder = $RootFolder
$dialog.ShowNewFolderButton = $ShowNewFolderButton.IsPresent
$selected = $null
# force the dialog TopMost:
# because the owning window will not be used after the dialog has been closed,
# you can simply create a new form inside the method call.
$result = $dialog.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true; TopLevel = $true}))
if ($result -eq [Windows.Forms.DialogResult]::OK){
$selected = $dialog.SelectedPath
}
# clear the FolderBrowserDialog from memory
$dialog.Dispose()
# return the selected folder
$selected
}
Select-Folder -Message 'Select the folder where the move scripts reside' -ShowNewFolderButton
Theo's helpful answer shows an alternative, WinForms-based way to invoke a folder-browsing dialog, via the System.Windows.Forms.FolderBrowserDialog class.
However, it seems that all that is missing from your original approach is to pass your form's window handle (hWND, .Handle) as the first argument to the Shell.Application COM object's .BrowseForFolder() method, which makes it the dialog's owner window and therefore shows the dialog on top of it - even if the form itself has the .TopMost property set:
$folder = (New-Object -ComObject Shell.Application).BrowseForFolder(
$form_acl.Handle, # Pass your form's window handle to make it the owner window
$message,
0,
$path)
Here's a simplified, self-contained example (requires PSv5+, but can be adapted to earlier versions):
using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type -AssemblyName System.Windows.Forms
# Create a sample topmost form with a single
# button that invokes a folder-browsing dialog.
($form = [Form] #{
Text = "Topmost Form"
Size = [Size]::new(300, 100)
TopMost = $true # Make the form topmost.
StartPosition = 'CenterScreen'
}).Controls.AddRange(#(
($folderBrowseButton = [Button] #{
Location = [Point]::new(70, 20)
Size = [Size]::new(160,30)
Text = 'Browse for Folder'
})
))
$folderBrowseButton.add_Click({
$path = 'C:\'
# IMPORTANT: Pass $form.Handle, the form's window handle (HWND) as the first argument, which
# makes the form the owning window, ensuring that the dialog is displayed on
# top - even if the form itself is set to topmost.
$folder = (New-Object -ComObject Shell.Application).BrowseForFolder(
$form.Handle,
'Pick a target folder:',
0, # Options
$path # Starting directory path.
)
if ($null -ne $folder) {
Write-Verbose -vb ('Folder picked: ' + $folder.self.Path)
} else {
Write-Verbose -vb 'Folder browsing canceled.'
}
})
$null = $form.ShowDialog()
PowerScript Noob here.
I've found a snippet of code that lets the user select a folder thru a Folder Browser Dialog, instead of having enter the path to the folder manually.
Works as expected, except the Folder Browser Dialog often opens behind other windows on the screen, which is getting tiresome.
Here is the code:
Function Get-Folder($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null
$foldername = New-Object System.Windows.Forms.FolderBrowserDialog
$foldername.Description = "Select a folder"
$foldername.rootfolder = "MyComputer"
$foldername.SelectedPath = $initialDirectory
if($foldername.ShowDialog() -eq "OK")
{
$folder += $foldername.SelectedPath
}
return $folder
}
$FolderNavn = Get-Folder($StartFolder)
How do I get the Folder Browser Dialog to open 'on top of' all other Windows?
Thanks.
To set the BrowseForFolder dialog TopMost, you need to use the ShowDialog() overloaded method with a parameter that specifies the dialogs owner (parent) form.
The easiest I think it to just create a new Form with property Topmost set to $true and use that as owner form:
function Get-Folder {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
[string]$Message = "Please select a directory.",
[Parameter(Mandatory=$false, Position=1)]
[string]$InitialDirectory,
[Parameter(Mandatory=$false)]
[System.Environment+SpecialFolder]$RootFolder = [System.Environment+SpecialFolder]::Desktop,
[switch]$ShowNewFolderButton
)
Add-Type -AssemblyName System.Windows.Forms
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Message
$dialog.SelectedPath = $InitialDirectory
$dialog.RootFolder = $RootFolder
$dialog.ShowNewFolderButton = if ($ShowNewFolderButton) { $true } else { $false }
$selected = $null
# force the dialog TopMost
# Since the owning window will not be used after the dialog has been
# closed we can just create a new form on the fly within the method call
$result = $dialog.ShowDialog((New-Object System.Windows.Forms.Form -Property #{TopMost = $true }))
if ($result -eq [Windows.Forms.DialogResult]::OK){
$selected = $dialog.SelectedPath
}
# clear the FolderBrowserDialog from memory
$dialog.Dispose()
# return the selected folder
$selected
}