How to detect Windows Closing in Timer event code in VB.Net

1

I've been writing a program for the community, and have come up against a problem that I can't find the answer to.

I have a VB.NET application, which has a timer event that fires every second to check if a task exists and if it's enabled, this runs some code that depending on the version of Windows will either run schtask.exe, or jt.exe, both are to do with creating and checking scheduled tasks.

Now when the user logs off, or shuts down Windows while my program is running, We often get an error "SCHTasks.exe - Application error. The application was unable to start correctly (0xc0000142). Click OK to close the application", I get the same error for JT.exe as well, and I presume this is because the timer event has fired during the shutdown/log off and windows won't let the respective program start.

I've tried disabling the timer in the form closing code with the following, but that didn't work.

If e.CloseReason = CloseReason.WindowsShutDown Then Timer2ChkLoggingTask.enabled = False

The latest I tried I found on the internet, but that doesn't seem to work either, the add handler is in my form load code. ShuttingDwn is a public shared variable.

    AddHandler Microsoft.Win32.SystemEvents.SessionEnding, AddressOf SessionEnding

    Private Sub SessionEnding(ByVal sender As System.Object, ByVal e As Microsoft.Win32.SessionEndingEventArgs)

    ShuttingDwn = True

    End Sub

Here is my code that runs schtasks.exe, there are some various bits of code that uses this function, but this is the only bit that actually runs schtasks.exe, and I would of thought that checking the value of ShuttingDwn would of stopped the problems, unless of cause it actually hasn't been updated when my code reaches this point.

    Public Function SCHTasksRun(ByVal Arguments As String) As String                'Creates a task using SCHTasks


    If Form1.ShuttingDwn = True Then Return "Void" 'This in theory should stop an error when shutting down.

    ' This is the code for the base process 

    Dim myProcess As New Process()
    ' Start a new instance of this program
    Dim myProcessStartInfo As New ProcessStartInfo("schTasks")
    myProcessStartInfo.Arguments = Arguments
    myProcessStartInfo.UseShellExecute = False
    myProcessStartInfo.CreateNoWindow = True
    myProcessStartInfo.RedirectStandardOutput = True
    myProcessStartInfo.RedirectStandardError = True

    myProcess.StartInfo = myProcessStartInfo

    Try
        myProcess.Start()
    Catch ex As Exception
        MsgBox("There was an error: " & ex.Message)
    End Try
    Dim myStreamReader As StreamReader = myProcess.StandardOutput
    Dim myErrorStreamReader As StreamReader = myProcess.StandardError
    ' Read the standard output of the spawned process. 
    Dim myString As String = myStreamReader.ReadToEnd
    myString = myString & myErrorStreamReader.ReadToEnd
    myProcess.WaitForExit()
    myProcess.Close()


    Return myString

End Function 

So it never gets to the point of running either of the two programs (schtasks.exe or Jt.exe), or least that was the theory, problem is I still occasionally get the error.

The Try/catch is not producing the error either, as I don't get my message window.

So any idea's on how I can stop these errors, they only happen when shutting down or logging off, but I'd still like to fix them.

vb.net
windows
timer
asked on Stack Overflow Nov 3, 2013 by Ronski • edited May 1, 2014 by nobody

2 Answers

1

You can handle the WM_QUERYENDSESSION message that Windows sends to your application, or (even better if you ask me) is to use the new Windows 7 shutdown blocking APIs, like ShutdownBlockReasonCreate

answered on Stack Overflow Nov 4, 2013 by Sam Axe
0

With the help of Dan-o pointing me in the correct direction I have now solved the problem. I have no idea if this code is totally correct, but it does seem to work well so far.

The original timer was firing using a Windows.Forms.Timer which runs in the same thread so my task checking code was blocking the detection of windows shutting down, I'm now using a System.Timers.Timer, which will not block the code. I've also revised the code for checking the tasks, and isolated so it can't be run by any of my other routines.

I added this just after the Public Class Form1

Public Const WM_QUERYENDSESSION As Integer = &H11       'From http://vbcity.com/forums/t/45823.aspx
Public Shared ShuttingDwn As Boolean = False    'If Windows is shutting down or logging off this gets set to true.

Public Structure TaskExistEnabled               'Structure variable used in function for checking if task exists and is enabled.
    Public TaskExists As Boolean
    Public TaskEnabled As Boolean
End Structure

Then added this sub to my main form, form1.

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    'Listens for system closing down - This sets a flag when windows Windows shuts down or logs off
    'From http://vbcity.com/forums/t/45823.aspx
    If m.Msg = WM_QUERYENDSESSION Then
        ShuttingDwn = True
    End If
    MyBase.WndProc(m)
End Sub 'WndProc 

Created my System.Timers.Timer in the Sub Form1_Load code.

    AddHandler Timer2ChkLoggingTask.Elapsed, New System.Timers.ElapsedEventHandler(AddressOf Timer2ChkLoggingTask_Tick) 'Timer for the task checking
    Timer2ChkLoggingTask.Interval = 1000

I then created the timer code on my main Form1

  Private Sub Timer2ChkLoggingTask_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)

    'This is only used by the timer for checking the tasks - it runs in a different thread, and so needs to be entirely self contained.
    'It was made to run in parallel so the main code was not blocked, so that system shutting would be picked up.

    Timer2ChkLoggingTask.Stop()         'Stopping the timer stops the code being called more than once if it take longer than the interval to complete.

    Dim TaskExists_oldVal As Boolean = Taskinfo.TaskExists         'Store the old value so we can see if its changed at the end
    Dim TaskEnabled_OldVal As Boolean = Taskinfo.TaskEnabled       'Store the old value so we can see if its changed at the end
    Dim Mystring As String

    If TaskVersion = "V0" Or TaskVersion = "V1" Then          'We have an OS that stores the tasks in Windows/Task

        Taskinfo.TaskExists = (IO.File.Exists(Environment.GetEnvironmentVariable("windir") & "\Tasks\" & LoggingTaskName & ".job"))
        If Taskinfo.TaskExists = True Then
            If ShuttingDwn = False Then  'This in theory should stop an error when shutting down.

                ' This is the code for the base process 
                Dim myProcess As New Process()
                ' Start a new instance of this program

                Dim myProcessStartInfo As New ProcessStartInfo(JT)

                myProcessStartInfo.Arguments = " /LJ " & QUOTE & Environment.GetEnvironmentVariable("windir") & "\Tasks\" & LoggingTaskName & ".job" & QUOTE & " /PJ"
                myProcessStartInfo.UseShellExecute = False
                myProcessStartInfo.CreateNoWindow = True
                myProcessStartInfo.RedirectStandardOutput = True
                myProcess.StartInfo = myProcessStartInfo

                Try
                    myProcess.Start()
                Catch ex As Exception
                    MsgBox(ex.Message)
                End Try
                Dim myStreamReader As StreamReader = myProcess.StandardOutput
                ' Read the standard output of the spawned process. 
                Mystring = myStreamReader.ReadToEnd
                myProcess.WaitForExit()
                myProcess.Close()
                Taskinfo.TaskEnabled = Not Mystring.Contains("Suspend                 = 1")
            End If

        End If

    Else                                                                    'We have Vista upwards and will use schtasks

        If ShuttingDwn = False Then 'This in theory should stop an error when shutting down.

            ' This is the code for the base process 

            Dim myProcess As New Process()
            ' Start a new instance of this program
            Dim myProcessStartInfo As New ProcessStartInfo("schTasks")
            myProcessStartInfo.Arguments = " /Query /TN " & QUOTE & LoggingTaskName & QUOTE
            myProcessStartInfo.UseShellExecute = False
            myProcessStartInfo.CreateNoWindow = True
            myProcessStartInfo.RedirectStandardOutput = True
            myProcessStartInfo.RedirectStandardError = True

            myProcess.StartInfo = myProcessStartInfo

            Try
                myProcess.Start()
            Catch ex As Exception
                MsgBox("There was an error: " & ex.Message)
            End Try
            Dim myStreamReader As StreamReader = myProcess.StandardOutput
            Dim myErrorStreamReader As StreamReader = myProcess.StandardError
            ' Read the standard output of the spawned process. 
            Mystring = myStreamReader.ReadToEnd
            Mystring = Mystring & myErrorStreamReader.ReadToEnd
            myProcess.WaitForExit()
            myProcess.Close()
            Taskinfo.TaskExists = Not Mystring.Contains("ERROR: The system cannot find the file specified.")
            Taskinfo.TaskEnabled = Not Mystring.Contains("Disabled")
        End If

    End If

    If TaskEnabled_OldVal <> Taskinfo.TaskEnabled Or TaskExists_oldVal <> Taskinfo.TaskExists Then  'If either of the values have changed we need to update the buttons.
        Me.BeginInvoke(New TimerStart(AddressOf TimerStartFunction)) 'Whilst a background worker thread can stop a timer it can't start - see here http://stackoverflow.com/questions/18545787/timer-can-be-stopped-in-backgroundworker-but-cant-be-started
    End If
    Timer2ChkLoggingTask.Start()


End Sub

The actual updating of the display buttons/labels is now handled by a separate timer that just looks at the values of Taskinfo, the timer is enabled only if any of the taskinfo values have changed, and the button update code then disables the timer, so it only runs once when either of taskinfo variables change.

Update. Whilst this seems to work perfectly in W7 & W8 I still get an error in Windows XP when shutting down/logging off.

answered on Stack Overflow Nov 9, 2013 by Ronski • edited Nov 13, 2013 by Ronski

User contributions licensed under CC BY-SA 3.0