1

I am fiddling with the timer object and message boxes and hit a brick wall.

I am trying to manage the timer object and popups through reusable functions.

I tested most of these functions on their own and in small parts first. Since everything worked, I decided to string everything together.

Together, it did not seem to work, so i broke down functionality to the basics:

# functionfile
function Show-MsgBox {
  Write.Host "debugFUNC2"
  #usage:
  #Show-MsgBox -Prompt "This is the prompt" -Title "This Is The Title" -Icon Critical -BoxType YesNo -DefaultButton 1

  [CmdletBinding()]
  param(
    [Parameter(Position=0, Mandatory=$true)] [string]$Prompt,
    [Parameter(Position=1, Mandatory=$false)] [string]$Title ="",
    [Parameter(Position=2, Mandatory=$false)] [string]$Icon ="Information",
    [Parameter(Position=3, Mandatory=$false)] [string]$BoxType ="OkOnly",
    [Parameter(Position=4, Mandatory=$false)] [int]$DefaultButton = 1
  )
  [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
  switch ($Icon) {
    "Question" {$vb_icon = [microsoft.visualbasic.msgboxstyle]::Question }
    "Critical" {$vb_icon = [microsoft.visualbasic.msgboxstyle]::Critical}
    "Exclamation" {$vb_icon = [microsoft.visualbasic.msgboxstyle]::Exclamation}
    "Information" {$vb_icon = [microsoft.visualbasic.msgboxstyle]::Information}
  }
  switch ($BoxType) {
    "OKOnly" {$vb_box = [microsoft.visualbasic.msgboxstyle]::OKOnly}
    "OKCancel" {$vb_box = [microsoft.visualbasic.msgboxstyle]::OkCancel}
    "AbortRetryIgnore" {$vb_box = [microsoft.visualbasic.msgboxstyle]::AbortRetryIgnore}
    "YesNoCancel" {$vb_box = [microsoft.visualbasic.msgboxstyle]::YesNoCancel}
    "YesNo" {$vb_box = [microsoft.visualbasic.msgboxstyle]::YesNo}
    "RetryCancel" {$vb_box = [microsoft.visualbasic.msgboxstyle]::RetryCancel}
  }
  switch ($Defaultbutton) {
    1 {$vb_defaultbutton = [microsoft.visualbasic.msgboxstyle]::DefaultButton1}
    2 {$vb_defaultbutton = [microsoft.visualbasic.msgboxstyle]::DefaultButton2}
    3 {$vb_defaultbutton = [microsoft.visualbasic.msgboxstyle]::DefaultButton3}
  }
  $popuptype = $vb_icon -bor $vb_box -bor $vb_defaultbutton 
  $ans = [Microsoft.VisualBasic.Interaction]::MsgBox($prompt,$popuptype,$title)
  return $ans
} #end function

function mafunc {
  Write-Host "debugFUNC1"
  $timer.start()
  $timer
  return 0
}

# calling file

$timer = New-Object System.Timers.Timer
#$timer.interval = 1800000
$timer.interval = 30000
$timer.Enabled = $true
$timer.AutoReset = $False
$scriptsleep = 0
$timer.stop()

$ThirtyAction = {
  Write-Host "action"
  Write-Host "action2"
  if ((Show-MsgBox -Prompt "prompt" -Title "title" -Icon Critical -BoxType YesNo -DefaultButton 1) -eq "No") {
    Write-Host "debugNO"
  } else {
    Write-Host "debugYES"
    $timer
    $timer.stop()
    Show-MsgBox -Prompt "prompt" -Title "title" # standard promt = OKonly button
    $timer.interval=30000
    $timer.start()
    $timer
    Write-Host "timer start"
  }
}

Register-ObjectEvent -InputObject $timer -EventName Elapsed -SourceIdentifier ThirtySecTimer -Action $ThirtyAction

myfunc

I still cannot run the script as it keeps "hanging" during the Elapsed action ($Thirtyaction). The last thing it shows in the PowerShell console is: action and action2. So I figured the problem lies within the box function, but:

  • if I call the script with ISE, it will run as expected
  • if I first execute the functionfile and then the file, it will again stop/hang at the (first/YesNo) msgBox
  • if I manually enter the msgBox function into the console and then start the file, it will show msgBoxes, but does not execute the code in the else statement correctly (stops after the (second/OKonly)msgBox has been shown)

I can't think of any way this makes sense and I've spent a lot of time trying to debug/switch it up/dumb it down etc.

edit1: A fresh day and a bit of more Information.

I changed the function to windows.Forms but had no luck. I then proceded to print out thread IDs for all functions and parts of the script and voila:

Main script
6
1176

myfunc
6
1176

Elapsedaction ($Thirtyaction)
11
4236

The $action that triggers when the Timerevent fires, is run in a new PowerShell Runspace and therefore has no more acces to Variables and functions that have been defined before.

I'm not sure how to fix this or work around it, but at least i know what's the problem. Any Ideas are welcome and thanks to the ppl that already answered and gave me a hint in the right direction!

Edit2 with solution and workaround:

thanks to another user if found an answer to the question of how to controll the timer attributes from within the $Thirtyaction scriptblock:

link

I can use the MsgBox function by simply dotsourcing the functionfile in the scriptblock

my workaround for variables was to use three environement variables. I'm pretty sure this is neither smart nor best practice, but shouldn't be an issue in my special case and it works.

as an example here is my current $Thirtyaction scriptblock:

 $ThirtyAction={
    . .\testfunction.ps1
    $Sender.stop()
    Write-Host "action"
    Write-Host "action2"
    if ((($ans=(Show-new-MsgBox -Prompt "Prompt" -Title "Title" -Icon "Warning" -BoxType "YesNo")) -eq "No") -or !($Env:Sessionsleepstate -eq 0)) {
        Write-Host $Env:Sessionsleepstate
        $Env:Sessionsleepstate = 3
        if ($Env:Sessionsleepstate -eq 1) {
            Show-new-MsgBox -Prompt "Prompt" -Title "Title"
        }
        Show-new-MsgBox -Prompt "Prompt" -Title "Title"
        Write-Host "killfunction placeholder"
    }
    else{
    $Sender.stop()
    Show-new-MsgBox -Prompt "Prompt" -Title "Title"
    $Sender.interval=300000
    $Env:Sessionsleepstate = 1
    $Sender.start()
    Write-Host "timer start"
    }
    }
Community
  • 1
  • 1
dopi
  • 15
  • 4
  • Hi, 1. save script in ps1 file 2. call file in PS console as follows : `powershell -file "path/to/script.ps1" -sta` 3. hope - Why are you not using `WinForms` or `WPF` ? That may be easier and remove the original issue. – sodawillow Dec 28 '15 at 17:45

1 Answers1

0

How interesting... I ran some checks.

First I thought GUI access would just be blocked from a console window. But I found that the message box is displayed just fine if you call it straight up (not from the timer function) like so:

Add-Type -AssemblyName "Microsoft.VisualBasic"
[Microsoft.VisualBasic.Interaction]::MsgBox("MyPrompt",[microsoft.visualbasic.msgboxstyle]::OKOnly,"title")

Then I thought it might be a threading thing, that in a console the timer would call the function on a different thread and the VisualBasic assembly would ignore or refuse it. But I found

Write-Host ([System.Threading.Thread]::CurrentThread).ManagedThreadId
Write-Host ([System.AppDomain]::GetCurrentThreadId())

gave the same values whether in the main code or in the function called by the timer.

Then I thought the assembly might no longer be accessible from the timer function. But I found that calling

([Microsoft.VisualBasic.Interaction]::Beep())

worked just fine from a console window.

So it is just weird. However, there appears to be a simple workaround: if you use System.Windows.Forms.MessageBox it does work as expected:

Add-Type -AssemblyName System.Windows.Forms
$Result = [System.Windows.Forms.MessageBox]::Show('Now I''ll be a son of a gun...', 'How about that?', 'YesNo', 'Warning')
Martin Maat
  • 714
  • 4
  • 23
  • Your second thought was about as close as it could get and made me think. i've put threadIDs in all functions and parts of the script and it turned out, that the Elapsed eventaction is called in a different thread and therefore has no access to previously called functions... I'm not sure yet how to fix/work around this, but at least i am closer to a solution. Thank you! – dopi Dec 29 '15 at 08:01
  • It was puzzled by your report that using Windows.Forms did not make a difference for you (although it would have been ever the more weird if it had) because I had tested it. But it appears the call still works in ThirtyAction, before calling Show-MsgBox, but it fails in Show-MsgBox because PowerShell never gets there! The call to Show-MsgBox fails for some reason (in my PowerShell console window anyway). I created a function Dummy with no paramters. Same result, any function call from $ThirtyAction will fail and seemingly block the script. – Martin Maat Dec 29 '15 at 12:03
  • Yes, this is due to the fact that the $thirtyaction scriptblock is run in a new PowerShell runspace, and therefore has no access to the function anymore. The workaround for this is to dotsource the functionfile in the scriptblock again. an unsolved problem is, that i cannot access my timer object during the scriptblock. – dopi Dec 29 '15 at 12:12
  • Ah, now you are ahead of me. I just read they're called sessions these days. If you haven't stumbled upon this article yet, it might help. http://learn-powershell.net/2013/04/19/sharing-variables-and-live-objects-between-powershell-runspaces/ – Martin Maat Dec 29 '15 at 12:34
  • Sadly it seems like the eventaction has no runspace property and this method cannot be used :( – dopi Dec 29 '15 at 13:55
  • i found a solution and a workaround if you are still interested. i posted it at the bottom of the question. – dopi Dec 29 '15 at 15:30
  • Cool, I learned a lot. It is still strange that I could never reproduce the different thread id in the event handler code. The symptoms were the same though. I tested on Winows 8 PS version 4.0. – Martin Maat Dec 29 '15 at 18:30