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.
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
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.
User contributions licensed under CC BY-SA 3.0