Messagebox and action if not pressed - powershell

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()
}

Related

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 handle timer with GUI using 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()
})

exit from powershell script causes Unhandled exception

I am trying to execute exit after closing the form when clicking "NO" button, but exit causes a system error
Code:
function No {
$Form.close()
exit
}
$NoButton.Add_Click({No})
Without exit, it closes the form but it continues executing the script
System error:
Unhandled exception has occured in a component in your application. If you click Continue, the application will ignore this error and
attempt to continue.
System error.
Full button code:
function No {
$Form.close()
exit
} #end No
# No button
$NoButton = New-Object System.Windows.Forms.Button
$NoButton.Location = New-Object System.Drawing.Size(95,80)
$NoButton.Size = New-Object System.Drawing.Size(80,20)
$NoButton.Text = "No"
$NoButton.Add_Click({No})
$NoButton.Cursor = [System.Windows.Forms.Cursors]::Hand
$Form.Controls.Add($NoButton)
You need to wait till the $Form is actually closed and then better kill it's own process:
$NoButton = New-Object System.Windows.Forms.Button
$NoButton.Location = New-Object System.Drawing.Size(95,80)
$NoButton.Size = New-Object System.Drawing.Size(80,20)
$NoButton.Text = "No"
$NoButton.Add_Click({$Form.close()})
$NoButton.Cursor = [System.Windows.Forms.Cursors]::Hand
$Form.Controls.Add($NoButton)
$Form.Add_Closed({Stop-Process -ID $PID}) # Kill it's own process when the form is closed

Powershell closing popup box

I'm trying to create a popup window that stays open until the script finishes.
I have the following code to create a popup box
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Operation Completed",0,"Done",0x1)
$wshell.quit
I figured $wshell.quit would close the window, but it doesn't. Is there a way to close this dialog box from within the script without user interaction?
The use of $wsheel.quit won't work here because in PowerShell when you execute $wshell.Popup(..) the session will wait untill the form is closed.
You won't be able to run any other command untill the window will be closed.
What you can do is to create the popup window in different session and by that you can run you code and when your code finish, search for the job and kill it.
Solution #1:
function killJobAndItChilds($jobPid){
$childs = Get-WmiObject win32_process | where {$_.ParentProcessId -eq $jobPid}
foreach($child in $childs){
kill $child.ProcessId
}
}
function Kill-PopUp($parentPid){
killJobAndItChilds $parentPid
Get-Job | Stop-Job
Get-Job | Remove-Job
}
function Execute-PopUp(){
$popupTitle = "Done"
$popupScriptBlock = {
param([string]$title)
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Operation Completed",0,$title,0x1)
}
$job = Start-Job -ScriptBlock $popupScriptBlock -ArgumentList $popupTitle
# Waiting till the popup will be up.
# Can cause bugs if you have other window with the same title, so beaware for the title to be unique
Do{
$windowsTitle = Get-Process | where {$_.mainWindowTitle -eq $popupTitle }
}while($windowsTitle -eq $null)
}
Execute-PopUp
#[YOUR SCRIPT STARTS HERE]
Write-Host "Your code"
Start-Sleep 3
#[YOUR SCRIPT ENDs HERE]
Kill-PopUp $pid
It creates your pop-up and only when the window is up (Verifying by the title. Notice that it can cause colissions if there is another process with the same window's title) your code will start run.
When your code will finish it will kill the job.
Notice that I didn't use Stop-Job to stop the job.
I guess it because when the job created the pop-up it can't receive any commands untill the popup will be close.
To overcome it I kill the job's process.
Solution #2 (using events):
function Kill-PopUp(){
kill (Get-Event -SourceIdentifier ChildPID).Messagedata
Get-Job | Stop-Job
Get-Job | Remove-Job
}
function Execute-PopUp(){
$popupTitle = "Done"
$popupScriptBlock = {
param([string]$title)
Register-EngineEvent -SourceIdentifier ChildPID -Forward
New-Event -SourceIdentifier ChildPID -MessageData $pid > $null
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Operation Completed",0,$title,0x1)
}
$job = Start-Job -ScriptBlock $popupScriptBlock -ArgumentList $popupTitle
# Waiting till the popup will be up.
# Can cause bugs if you have other window with the same title, so beaware for the title to be unique
Do{
$windowsTitle = Get-Process | where {$_.mainWindowTitle -eq $popupTitle }
}while($windowsTitle -eq $null)
}
Execute-PopUp
#[YOUR SCRIPT STARTS HERE]
Write-Host "Your code"
Start-Sleep 3
#[YOUR SCRIPT ENDs HERE]
Kill-PopUp
You can, from within power shell, open internet explorer, displaying a local html page (aka a splash screen) then, when done, close it
$IE=new-object -com internetexplorer.application
$IE.navigate2("www.microsoft.com")
$IE.visible=$true
...
$IE.Quit()
Some reading Here https://social.technet.microsoft.com/Forums/ie/en-US/e54555bd-00bb-4ef9-9cb0-177644ba19e2/how-to-open-url-through-powershell?forum=winserverpowershell
Some more reading Here How to properly close Internet Explorer when launched from PowerShell?
See https://msdn.microsoft.com/en-us/library/x83z1d9f(v=vs.84).aspx for the parameters.
The one you need is [nSecondsToWait], if the value is 0 the script waits indeffinetly, use a value for the seconds and it wil close by itself.
intButton = wShell.Popup(strText,[nSecondsToWait],[strTitle],[nType])
Another way would be sending a keystoke to the dialog with wshShell.SendKeys "%N"
Using the first method here an example of what you could do.
I'm using vbscript here, no experience with powershell but it's almost the same solution.
Set wShell = WScript.CreateObject("WScript.Shell")
count = 1
result = -1
Do while result = -1
result = wShell.Popup("Operation Completed", 1, "Done", 0)
count = count + 1
Wscript.echo count
if count = 10 then Exit Do ' or whatever condition
Loop

PowerShell Loading Animation

I would like to create a GUI that shows a loading bar while a job is running in the background. For simplicity, I've made the job an infinite loop so it should always be running. I've only included necessary parts of the code:
$Label = new-object system.windows.forms.Label
$Label.Font = 'Ariel,12pt'
$Label.Text = ""
$Label.AutoSize = $True
$Label.Location = new-object system.drawing.size(50,10)
$Form.Controls.Add($Label)
$LoadingAnimation = #(".....","0....",".0...","..0..","...0.","....0",".....")
$AnimationCount = 0
$test = start-job -Name Job -ScriptBlock { for($t=1;$t -gt 0; $t++){} }
while ($test.JobStateInfo.State -eq "Running")
{
$Label.Text = $LoadingAnimation[($AnimationCount)]
$AnimationCount++
if ($AnimationCount -eq $LoadingAnimation.Count){$AnimationCount = 0}
Start-Sleep -Milliseconds 200
}
Upon testing this code in the console, just using Write-Host instead of $Label.Text, it works just fine. What needs to be done differently to get this to work in a windows form created by PowerShell?
In PowerShell, you can create all sorts of status, including multi-level, using Write-Progress. Don't forget to call with -Completed when done (a common mistake I see).
Get-Help Write-Progress
After going through the little details of the script, I found the problem. This is how I activated my form:
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()
This caused the script to stop when the form was launched, ShowDialog stops the script to allow interaction. The fix was:
$Form.Add_Shown({$Form.Activate()})
[void] $Form.Show()
Using Form.Show lets the script to continue to run because it doesn't require interaction.