How to handle timer with GUI using powershell? - powershell

I have cmd script. I want to use timer for 10 second to give a decision to continue cmd script process or to pause it.
I want to put this script at the first line of my cmd script
powershell.exe -ExecutionPolicy Bypass -File %~dp0\Pause_GUI.ps1
It will pop up 10s countdown, after 10s, it will continue the cmd script process by return errorlevel, but if we click button pause, the cmd script will pause, also by return errorlevel.
Anyone can help please
UPDATED
#------------------------------------------- Add in Forms Controls -------------------------------------------#
Add-Type -AssemblyName System.Windows.Forms
#-------------------------------------------------------------------------------------------------------------#
#---------------------------------------- Begins creation of the form ----------------------------------------#
$MainForm = New-Object System.Windows.Forms.Form
$MainForm.Text = "Message"
$MainForm.Width = 500
$MainForm.Height = 200
$MainForm.StartPosition = "CenterScreen"
$MainForm.BackColor = "#e2e2e2"
#-------------------------------------------------------------------------------------------------------------#
#----------------------------------------------- Button Clicks -----------------------------------------------#
$Auto_Button = ({ $global:result=1
$MainForm.Close() })
$Manual_Button = ({ $global:result=0
$MainForm.Close() })
#-------------------------------------------------------------------------------------------------------------#
#-------------------------------------------------- Buttons --------------------------------------------------#
$Automatic = New-Object System.Windows.Forms.Button
$Automatic.Location = New-Object System.Drawing.Size(110,80)
$Automatic.Size = New-Object System.Drawing.Size(120,30)
$Automatic.Text = "Continue After 10s"
$Automatic.BackColor = "#e47104"
$Automatic.Add_Click($Auto_Button)
$MainForm.Controls.Add($Automatic)
$Manual = New-Object System.Windows.Forms.Button
$Manual.Location = New-Object System.Drawing.Size(270,80)
$Manual.Size = New-Object System.Drawing.Size(100,30)
$Manual.Text = "Pause"
$Manual.BackColor = "#e47104"
$Manual.Add_Click($Manual_Button)
$MainForm.Controls.Add($Manual)
#-------------------------------------------------------------------------------------------------------------#
#--------------------------------------------- Displays the Form ---------------------------------------------#
$result=0
$MainForm.ShowDialog()
exit $result
#-------------------------------------------------------------------------------------------------------------#
How to handle the button "Continue after 10s" as a timer? And the GUI will close automatically after 10s

You need a System.Windows.Forms.Timer-object that counts your time and a .tick-event that will trigger when the time has come. However you need to stop (and dispose) the timer or it will keep on triggering the event even when the window is closed. (In Powershell ISE that could cause windows to close as soon as you load them). To grab the timer from within it's own event you need to adress it in the right scope. I used the global-scope for that.
$Auto_Button = ({
$global:Counter = 0
$global:timer = New-Object -type System.Windows.Forms.Timer
$global:timer.Interval = 1000
$global:timer.add_Tick({
if ($Counter -eq 10){
write-host $global:counter
$global:timer.Stop()
$global:timer.Dispose()
$result=1
$MainForm.Close()
$global:Counter++
}else{
write-host $global:counter
$global:Counter++
}
})
$global:timer.Start()
})

Related

Messagebox and action if not pressed

I'm trying to develop a script to pop up a messagebox to a user who has the computer connected at a certain hour of the day. If the user confirms he's using the computer by pressing a button of the messagebox then the process stops.
If the user does not press anything for 30 m means he's not at the computer, and we shut down the computer.
I can pop up the messagebox with the message, but how can I check if the button has been pressed or give it a wait 30 m and then if nothing happens, do something else?
You can use the Wscript.Shell COM object's .Popup() method, which supports specifying a timeout after which the dialog (message box) is automatically closed.
In the simplest case:
# Show a message box with an OK button that auto-closes
# after 3 seconds.
# Return value is:
# -1, if the dialog auto-closes
# 1 if the user pressed OK
$response =
(New-Object -ComObject WScript.Shell).Popup(
'I will close in 3 seconds',
3, # timeout in seconds
'Title',
48 # exclamation-point icon; OK button by default
)
if ($response -eq -1) { 'Auto-closed.' } else { 'User pressed OK.' }
A complete example that shows a dialog with OK and Cancel buttons: if the dialog auto-closes or the user presses OK, the script continues; otherwise, processing is aborted:
# How long to display the dialog before it closes automatically.
$timeoutSecs = 30 * 60 # 30 minutes.
# Translate this into the time of day, to show to the user.
$timeoutTimeOfDay = (Get-Date).AddSeconds($timeoutSecs).ToString('T')
# Put up a message box with a timeout and OK / Cancel buttons.
$response =
(New-Object -ComObject WScript.Shell).Popup(
#"
Your computer will shut down at $timeoutTimeOfDay.
Press OK to shut down now, or Cancel to cancel the shutdown.
"#,
$timeoutSecs,
'Pending shutdown',
49 # 48 + 1 == exclamation-point icon + OK / Cancel buttons
)
if ($response -eq 2) { # Cancel pressed.
Write-Warning 'Shutdown aborted by user request.'
exit 2
}
# OK pressed ($response -eq 1) or timed out ($response -eq -1),
# proceed with shutdown.
'Shutting down...'
# Restart-Computer -Force
Note:
The above dialog is statically shows only the time of day at which the shutdown will be initiated.
If you want a realtime countdown of the seconds remaining, more work is needed: see Fitzgery's helpful answer.
Here's that Function that I mentioned in my comment
Function Invoke-RestartTimer {
<#
.SYNOPSIS
Restart Computer After Timer reaches 0
.DESCRIPTION
Pops-Up a GUI Window that Counts Down Seconds until the Computer restarts. It also has buttons to either Restart Now or Cancel the Restart.
If the Restart is Cancelled it will write to the host with Write-Warning that the Restart was cancelled by the Current User.
.PARAMETER Seconds
Identifies the Amount of Seconds the Timer will countdown from
.EXAMPLE
Restart-Computer -Seconds 30
.NOTES
Multi-use Advanced Function for performing a Visual Countdown Restart.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[Int32]$Seconds = "15"
)
#Builds Assemblies for Custom Forms used in the function
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
#Identifies logged on user for if restart is cancelled
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
#Adds 1 to the identified time to ensure restart happens at 0 seconds and not -1 seconds.
#$Seconds++
#Builds Form
$RestartForm = New-Object System.Windows.Forms.Form
$RestartForm.StartPosition = "CenterScreen"
$RestartForm.Size = New-Object System.Drawing.Size(300,150)
$RestartForm.Text = "Restart Window"
$RestartForm.AllowTransparency = $true
$RestartForm.BackColor = "LightGray"
$RestartForm.FormBorderStyle = "Fixed3D"
#Builds Text Label
$Script:TimerLabel = New-Object System.Windows.Forms.Label
$TimerLabel.AutoSize = $true
$TimerLabel.Font = "Microsoft Lucida Console,10"
$RestartForm.Controls.Add($TimerLabel)
#Builds Timer
$Timer = New-Object System.Windows.Forms.Timer
$Timer.Interval = 1000 #1 second countdown, in milliseconds
$script:CountDown = $Seconds #seconds to countdown from
$Timer.add_Tick({
$Script:TimerLabel.Text = "
The Computer will restart in $CountDown seconds.
Press the Restart Button to Restart Now."
$Script:CountDown--
If($CountDown -eq "-2"){#Needs to be 2 below wanted value. i.e 0 = -2
$Timer.Stop()
$RestartForm.Close()
Restart-Computer -Force
}
})
$Timer.Start()
#Builds a Restart Button
$RestartButton = New-Object System.Windows.Forms.Button
$RestartButton.Text = "Restart"
$RestartButton.FlatStyle = "Popup"
$RestartButton.Location = New-Object System.Drawing.Size(50,80)
$RestartButton.Size = New-Object System.Drawing.Size(80,23)
$RestartButton.Font = "Microsoft Lucida Console,10"
$RestartButton.Add_Click({
$Timer.Stop()
$RestartForm.Close()
Restart-Computer -Force
})
$RestartForm.Controls.Add($RestartButton)
#Builds a Cancel Button
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Text = "Cancel"
$CancelButton.FlatStyle = "Popup"
$CancelButton.Location = New-Object System.Drawing.Size(150,80)
$CancelButton.Size = New-Object System.Drawing.Size(80,23)
$CancelButton.Font = "Microsoft Lucida Console,10"
$CancelButton.Add_Click({
$Timer.Stop()
$RestartForm.Close()
Write-Warning "Restart was aborted by $CurrentUser"
})
$RestartForm.Controls.Add($CancelButton)
[void]$RestartForm.ShowDialog()
}

Random number generator not working after converting to Exe

I have written a dice roller script. I have one with a single die and one with two dice. they both work in powershell, and the single die version works after converting to .exe using ps2exe. but the two die version runs as an exe but I get the following error "You cannot call a method on a null-valued expression"
Below is the 2 die script that gives the error after converting.
<#
.NAME
Random Dice Roller
#>
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Display1 = 1,2,3,4,5,6 | Get-Random
$Display2 = 1,2,3,4,5,6 | Get-Random
$LabelImage = [system.drawing.image]::FromFile("f:\psscripts\Die1.png")
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(400,300)
$Form.text = "Roll The Dice"
$Form.TopMost = $false
$Form.location = New-Object System.Drawing.Point(1,1)
$Form.StartPosition = "CenterScreen"
$Die1 = New-Object system.Windows.Forms.Label
$Die1.Text = "$Display1"
$Die1.AutoSize = $false
$Die1.width = 200
$Die1.height = 200
$Die1.location = New-Object System.Drawing.Point(1,1)
$Die1.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',150,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))
$Die1.ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#000000")
$Die1.BackgroundImage = $LabelImage
$Die2 = New-Object system.Windows.Forms.Label
$Die2.Text = "$Display2"
$Die2.AutoSize = $false
$Die2.width = 200
$Die2.height = 200
$Die2.location = New-Object System.Drawing.Point(200,1)
$Die2.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',150,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))
$Die2.ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#000000")
$Die2.BackgroundImage = $LabelImage
$Button1 = New-Object System.Windows.Forms.Button
$Button1.Location = New-Object System.Drawing.Size(175,220)
$Button1.Size = New-Object System.Drawing.Size(80,50)
$Button1.Text = "Roll Me"
$Button1.Add_Click({Roll})
$Form.controls.AddRange(#($Die1,$Die2,$Button1))
$Die.Add_Click({ Roll })
#region Logic
function Roll{
$Form.Close()
$Form.Dispose()
.\Dice.exe}
#endregion
[void]$Form.ShowDialog()
There are two possible causes here:
Note that there is no safety in wrapping you PowerShell script in an .exe file. In fact the PowerShell script is extracted in a temporary folder and executed from there which might be the cause of your issue (e.g. the relative .\Dice.exe location)
It is unwise to do a $Form.Dispose() from within a form function/event
Remove that from the function and put it outside the form, e.g.:
[void]$Form.ShowDialog(); $Form.Dispose()
(Or do not use the Dispose method at all as PowerShell will usually already takes care of that if you [void] the $From.)
What is your motive for creating an EXE?
An alternative might be to create a CMD file with a Batch bootstrap instead. Create a CMD file with the following code and include your script after this.
<# :
#ECHO OFF
SET f0=%~f0
PowerShell -NoProfile -ExecutionPolicy RemoteSigned -Command ".([scriptblock]::Create((get-content -raw $Env:f0)))"
PAUSE
GOTO :EOF
<#~#>
# Insert PowerShell script after this line.

Creating a label which disappears after a few seconds

I am trying to achieve the following
User clicks a button
Label1 appears at the top left (text = "Hi there")
Label1 disappears after 5 seconds
I have tried to work with the timer function but the Interval setting just appears to work as it states, in intervals.
I don't want to use the Start-Sleep option because I need the form to still be active while this prompt appears.
$timerPrompt = New-Object System.Windows.Forms.Timer
$timerPrompt.Interval = 3000
$timerPrompt.Add_Tick({$form.Controls.Remove($label1)})
As Drew already pointed out, your code does not show where you start, stop or dispose of the timer object.
This very simple form below creates a timer that gets started when the user clicks the button. In the Tick event of the timer it shuts itself down again.
Instead of removing the label, it hides and unhides it from view .
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Test Timer'
$form.Size = New-Object System.Drawing.Size(300,200)
$form.MinimumSize = New-Object System.Drawing.Size(300,150)
$form.StartPosition = "CenterScreen"
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Point(10,20)
$label.Size = New-Object System.Drawing.Size(($form.Width - 20),50)
$label.Text = "Hi There"
$label.Anchor = "Top","Left","Right"
$label.Visible = $false
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 5000
$timer.Add_Tick({
# hide the label and stop the timer
$label.Visible = $false
$timer.Stop()
})
$button = New-Object System.Windows.Forms.Button
$button.Location = New-Object System.Drawing.Point(($form.Width - 185),($form.Height - 80))
$button.Size = New-Object System.Drawing.Size(75,23)
$button.Anchor = "Bottom","Left","Right"
$button.Text = "&Click Me"
$button.Add_Click({
# show the label and start the timer
$label.Visible = $true
$timer.Start()
})
# add the controls to the form
$form.Controls.Add($label)
$form.Controls.Add($button)
[void]$form.ShowDialog()
# when done, clean up the objects
$timer.Stop()
$timer.Dispose()
$form.Dispose()
Hope that explains
I am not very good with GUI programming but I have a trick that will solve that problem for you as I have used it many of my GUI programs.
for ($i = 0; $i -lt 25; $i++)
{
Start-Sleep -Milliseconds 200
[System.Windows.Forms.Application]::DoEvents()
}
Be advised that this is not ideal but I can't argue with the results. The little loop will give you a delay of 5 seconds while the DoEvents() part will keep ur form from going non responsive. Basically it checks for interrupts every 200ms giving the impression that the form is active.
Make the loop count 50 and reduce time to 100ms if you like. same results but extra responsiveness.

How to stop an event in powershell if No is pressed in a confirmation message box

I have a DataGridView filled with rows with the ability to select and delete a row using Delete key.
There is a confirmation message box popping up when Delete is pressed, asking Yes or No to proceed with the deletion.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '800,800'
$Form.text = "Form"
$Form.TopMost = $false
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.BackColor = "#f7f7f7"
$DataGridView1.width = 771
$DataGridView1.height = 716
$DataGridView1.Anchor = 'top,right,bottom,left'
$DataGridView1.location = New-Object System.Drawing.Point(15,68)
$import = New-Object system.Windows.Forms.Button
$import.text = "import"
$import.width = 60
$import.height = 30
$import.location = New-Object System.Drawing.Point(25,22)
$import.Font = 'Microsoft Sans Serif,10'
$save = New-Object system.Windows.Forms.Button
$save.text = "save"
$save.width = 60
$save.height = 30
$save.location = New-Object System.Drawing.Point(125,22)
$save.Font = 'Microsoft Sans Serif,10'
$Form.controls.AddRange(#($DataGridView1,$import,$save))
$import.Add_Click({ importXML })
$save.Add_Click({ saveXML })
$DataGridView1.Add_UserDeletingRow({ message })
$DataGridView1.AutoSizeColumnsMode = 16
Function importXML(){
$xml_input = Get-FileName
$ds = New-Object System.Data.Dataset
$ds.ReadXml($xml_input)
$DataGridView1.DataSource = $ds.Tables[0]
}
Function message(){
$msgBoxInput = [System.Windows.Forms.MessageBox]::Show("Proceed with the deletion?","Delete confirmation","YesNo","Question")
if ($msgBoxInput -eq "YES" )
{
[System.Windows.Forms.MessageBox]::Show("The selected row will be deleted")
}
else
{
#stop the deletion
}
}
Function saveXML(){
$xml_output = Save-FileName
$DataGridView1.DataSource.writexml($xml_output)
}
[void]$Form.ShowDialog()
Everything is working perfectly except after else. I have no idea on how to abort the deletion event.
Any suggestion?
In the MSDN examples, we see them do this in c# by setting e as a reference to the current event, and then setting e.Cancel equal to true, which allows us to cancel the event as covered here. The syntax looks like this:
private void DataGridView1_UserDeletingRow(object sender,
DataGridViewRowCancelEventArgs e){
e.Cancel = true; //Cancel the event
}
Well, in PowerShell if we try to add an event handler in this way, we'll get errors, because event handler methods generally only allow us to specify one overload which is the scriptblock to run on the event.
It turns out that it's deceptively easy to reference the current event, fortunately! To cancel the Deletion, simply add this to your add_UserDeletingRow() scriptblock.
else
{
#stop the deletion
$PSItem.Cancel=$true
}
You could also use the $_ current item syntax as well, which would look like
else
{
#stop the deletion
$_.Cancel=$true
}
Any time you're adding an event handler and need to refer to the event itself with PowerShell( and there are LOTs of Events like this, look at all of them for DataGridView alone!) you'll use $_ or $PSItem. So in those examples from MSDN, if you see them referencing the current event with e or something similar, just substitute $_ or $PSItem and you'll be good to go.

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