using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using WatchdogLib.WPF; using WatchdogLib.Thread; using System.Timers; using Watchdog.WatchdogLib.Process; using System.Diagnostics; using WatchdogLib.File; using WatchdogLib.Tools; using Watchdog.WatchdogLib.File; using Watchdog.WatchdogLib.Registry; using Watchdog.WatchdogLib.Assembly; using System.Windows.Threading; using Watchdog.WatchdogLib.Other; using Watchdog.WPFWindows; using Watchdog.WatchdogLib.Monitor; using Watchdog.WatchdogLib.Net; using Watchdog.WatchdogLib.Win32; namespace Watchdog { /// /// Interaction logic for MainWindow.xaml /// public partial class HiddenMainWindow : Window { #region Private Statics // Core Timers * And HiddenWindow Var * private static KeepHidden _HiddenWindow = null; private static TTimer _timer = new TTimer(TTimer_ElapsedEventHandler, 1523, false, false); // Kicks in every ~1.5 Seconds private static TTimer _timerScheduler = new TTimer(TTimer_ElapsedEventHandlerScheduler, 61491, false, false); // Kicks in every ~1 Minute private static TTimer _timerSchedulerBackup = new TTimer(TTimer_ElapsedEventHandlerSchedulerBackup, 99986, false, false); // Kicks in every 1.665 Minutes (uneven so timers have the highest chance of not overlapping) // In case an unexpected process shutdown occurs, let's try to do more clean-up private static bool s_WindowCloseGotCalled = false; // to makes sure we have a clean shut down * Prev State Tracking * private static bool s_bIsTimerRunning = false; private static bool s_bIsSchedulerTimerRunning = false; private static bool s_bIsSchedulerBackupTimerRunning = false; // Start/Stop Timers *While shuting down _timer can be null* calling These functions and ignore any errors, // Functions return the * previous state before calling * so if the previous state was 'Off' then it returns false, previous state was 'On' it returns true; private static bool StartTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsTimerRunning; try { _timer.Start(); s_bIsTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } private static bool StopTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsTimerRunning; try { _timer.Stop(); s_bIsTimerRunning = false; } catch (Exception) { /* ignore */ } return bPrevState; } } private static bool StartSchedulerTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerTimerRunning; try { _timerScheduler.Start(); s_bIsSchedulerTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } private static bool StopSchedulerTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerTimerRunning; try { _timerScheduler.Stop(); s_bIsSchedulerTimerRunning = false; } catch (Exception) { /* ignore */ } return bPrevState; } } //private static bool StartSchedulerBackupTimer(uint IntervalInMiliseconds = 99986) { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerBackupTimerRunning; try { _timerSchedulerBackup.Start(IntervalInMiliseconds); s_bIsSchedulerBackupTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } private static bool StartSchedulerBackupTimer(uint IntervalInMiliseconds) { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerBackupTimerRunning; try { _timerSchedulerBackup.Start(IntervalInMiliseconds); s_bIsSchedulerBackupTimerRunning = true; } catch (Exception) { /* ignore */ } return bPrevState; } } private static bool StopSchedulerBackupTimer() { lock (_HiddenWindow) { bool bPrevState = s_bIsSchedulerBackupTimerRunning; try { _timerSchedulerBackup.Stop(); s_bIsSchedulerBackupTimerRunning = false; } catch (Exception) { /* ignore */ } return bPrevState; } } #endregion #region Internal Statics * (Monitor API) * /// /// Use this to Start the Process Monitor * Also Called by Scheduler and Scheduler Backup * /// /// true to force a one-time refresh of XML DS Data, false otherwise /// true to interact with desktop, false otherwise //internal static void StartMonitoring(bool bReloadConfigurationData = false, bool bShowUserPopup = true) internal static void StartMonitoring(bool bReloadConfigurationData, bool bShowUserPopup) { // If the Monitor was Paused Prior? - restart the Timers if (App.State_IsMonitorPaused()) { lock (_HiddenWindow) { // Set to Running App.State_MonitorStarted(); // Re-start Timers StartTimer(); StartScheduler(); StartSchedulerBackup(99986); // Log and Inform the User App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_STARTED_FROM_PAUSE", App.APPLICATION_NAME_SHORT, false)); if(bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_START_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); // * Set the System Tray Icon According to Running State * if(!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); return; } } // Is the Monitor already running? if (App.State_MonitorIsInRunningState()) return; // Set to Running App.State_MonitorStarted(); // Let's get Started lock (_HiddenWindow) { // Reset the Last Scheduler Run Time if (!App.State_IsSchedulerExecuting()) App.setting.LastSchedulerRun = DateTime.MinValue; // Reset the Last Scheduler Backup Run Time if (!App.State_IsSchedulerBackupExecuting()) App.setting.LastSchedulerBackupRun = DateTime.MinValue; // Reset Error State * But Only if we are NOT being Called by the Scheduler * if (!App.State_IsSchedulerExecuting()) App.State_Error_ResetErrorState(); // Log Start of monitoring App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_STARTED", App.APPLICATION_NAME_SHORT, bReloadConfigurationData ? "True" : "False")); // * Set the System Tray Icon According to Running State * if(!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); // Popup Std Start Monitoring Message if (bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) { if (App.State_IsSchedulerExecuting()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); else if (App.State_IsSchedulerBackupExecuting()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP_DISPLAY2", App.APPLICATION_NAME_SHORT)); else WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_START_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); } // Reload Configuration, if neccessary App.xmlfile.ForceRefreshOnNext_ReadData = bReloadConfigurationData; if (!bReloadConfigurationData) { // make sure that at least all the process/service info is cleared App.xmlfile.ReadData(false).ClearAllStartNFailTimes(); // also clear PID information App.xmlfile.ReadData(false).MonitoredProcesses.ClearPidInformation(); } // Let the Schedulers Handle the starting of the Timers, // * don't start anything if it is already executing - concurrency * if (!App.State_IsSchedulerExecuting() && !App.State_IsSchedulerBackupExecuting()) { if(App.setting.SchedulerSettingsExist || App.setting.SchedulerBackupSettingsExist) { // When we StartMonitoring, we should delay start the Scheduler, // to give the timer a change to start all processes. 10 Seconds should do. ElapsedEventHandler timedStart = delegate(object sender, ElapsedEventArgs e) { Timer timer = (Timer)sender; timer.Stop(); StartScheduler(); // * Start the Scheduler, if it is set * StartSchedulerBackup(99986); // * Start the Scheduler Backup, if it is set * timer.Dispose(); timer = null; }; TTimer delayStartScheduler = new TTimer(timedStart, 5000, true, true); } } // Allow Scheduler to manage this timer if(!App.State_IsSchedulerExecuting()) StartTimer(); } } /// /// Pause the Monitor * Also Called by Scheduler Backup * /// /// true to interact with desktop, false otherwise //internal static void PauseMonitoring(bool bShowUserPopup = true) internal static void PauseMonitoring(bool bShowUserPopup) { if (App.State_IsMonitorPaused() || !App.State_MonitorIsInRunningState()) return; App.log.Info("PauseMonitoring Called"); lock (_HiddenWindow) { // Stop Both Timers, inside a Lock, // should do the trick * Neither should execute * StopTimer(); StopScheduler(); if (!App.State_IsSchedulerBackupExecuting()) StopSchedulerBackup(); // Mark the System as a Paused State App.State_MonitorPaused(); // Log Pause of monitoring App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_PAUSED", App.APPLICATION_NAME_SHORT)); if (bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_MONITOR_PAUSED", App.APPLICATION_NAME_SHORT)); // * Set the System Tray Icon According to Current State * if(!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); } } /// /// Restarts the Monitor * Also Called by Scheduler * /// /// Time in seconds to wait before starting (after stopping) /// true to interact with desktop, false otherwise //internal static void RestartMonitoring(uint nWaitBeforeStartSeconds = 4, bool bShowUserPopup = true) internal static void RestartMonitoring(uint nWaitBeforeStartSeconds, bool bShowUserPopup) { App.log.Info("RestartMonitoring Called"); // Stop the Monitor StopMonitoring(bShowUserPopup, false); // Sleep, for a little while,... System.Threading.Thread.Sleep(TimeSpan.FromSeconds(nWaitBeforeStartSeconds)); // Now Start again... StartMonitoring(!App.State_IsSchedulerExecuting(), bShowUserPopup); } /// /// Restarts the Monitor, including all services /// /// Time in seconds to wait before starting (after stopping) /// true to interact with desktop, false otherwise //internal static void RestartAllMonitoring(uint nWaitBeforeStartSeconds = 4, bool bShowUserPopup = true) internal static void RestartAllMonitoring(uint nWaitBeforeStartSeconds, bool bShowUserPopup) { App.log.Info("RestartAllMonitoring Called"); // Stop the Monitor StopMonitoring(bShowUserPopup, true); // Sleep, for a little while,... System.Threading.Thread.Sleep(TimeSpan.FromSeconds(nWaitBeforeStartSeconds)); // Now Start again... StartMonitoring(!App.State_IsSchedulerExecuting(), bShowUserPopup); } /// /// Stops Monitoring, including all Services /// /// true to interact with desktop, false otherwise //internal static void StopAllMonitoring(bool bShowUserPopup = true) internal static void StopAllMonitoring(bool bShowUserPopup) { App.log.Info("StopAllMonitoring Called"); StopMonitoring(bShowUserPopup, true); } /// /// Use this to Stop the Process Monitor * Also Called by Scheduler and Scheduler Backup * /// //internal static void StopMonitoring(bool bShowUserPopup = true, bool bForceShutdownOfServices = false) internal static void StopMonitoring(bool bShowUserPopup, bool bForceShutdownOfServices) { // Is the Monitor even running? if (!App.State_MonitorIsInRunningState()) return; // Set to Stopped App.State_MonitorStopped(); lock (_HiddenWindow) { // Reset Error State * But Only if we are NOT being Called by the Scheduler * if (!App.State_IsSchedulerExecuting() && !App.State_IsSchedulerBackupExecuting()) App.State_Error_ResetErrorState(); // Log Stop of monitoring App.log.Info(AppResx.GetStringFormated("MESSAGE_MONITOR_STOPPED", App.APPLICATION_NAME_SHORT)); // Stop the Internal Timers * Always do this * just in case StopTimer(); StopSchedulerTimer(); if (!App.State_IsSchedulerBackupExecuting()) StopSchedulerBackupTimer(); // Read the config WatchdogConfiguration config = App.xmlfile.ReadData(false); // * Set the System Tray Icon According to Running State * if(!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); // Popup Std Stop Monitoring Message * But Only if Scheduler && Scheduler Backup is NOT running * if (bShowUserPopup && !App.State_SpecialMode_IsRunAsServiceSet()) { if(App.State_IsSchedulerExecuting()) { WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); } else if (App.State_IsSchedulerBackupExecuting()) { WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP_DISPLAY1", App.APPLICATION_NAME_SHORT)); } else { if (config.ConfiguredHasAny) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_STOP_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME, bForceShutdownOfServices)); else WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_NO_CONFIGURATION_FOUND", App.APP_XML_DS_FILENAME)); } if (config.ConfiguredHasAny) App.log.Info(AppResx.GetStringFormated("MESSAGE_STOP_SUCCESS", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME, bForceShutdownOfServices)); else App.log.Error(AppResx.GetStringFormated("MESSAGE_NO_CONFIGURATION_FOUND", App.APP_XML_DS_FILENAME)); } // Shutdown any of our Running Processes TraceM.TraceBegin(); bool bKillApplications = !(App.State_SpecialCircum_ShouldWeNotCloseApplicationsOnExit() && s_WindowCloseGotCalled); if(bKillApplications) ProcessKillAllMonitoredProcessesIfAny(config); TraceM.TraceEnd("Killing Running Applications"); // Shutdown any of our Running Services TraceM.TraceBegin(); ServicesKillAllMonitoredServicesIfAny(config, bForceShutdownOfServices); TraceM.TraceEnd("Killing Running Services"); // Try Reclaim memory by running the Garbage Collector // * Really appears to not be doing much * if (!App.State_IsSchedulerExecuting() && !App.State_IsSchedulerBackupExecuting()) { TraceM.TraceBegin(); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); TraceM.TraceEnd("Garbage Collector Called"); } } } /// /// Use this to Start the Scheduler /// internal static void StartScheduler() { lock (_HiddenWindow) { // Start the Scheduler Only if there is any Scheduler Information (and the Settings Window // isn't currently being shown - Settings window disables/enables Scheduler on it's own * if (!App.State_IsSchedulerExecuting() && App.setting.SchedulerSettingsExist && !WPFWindowMaker.IsWindowOfTypeWPFWindowShowing_WithinLock(WPFWindow.Settings_Window)) StartSchedulerTimer(); } } /// /// Use this to Stop the Scheduler /// internal static void StopScheduler() { lock (_HiddenWindow) { StopSchedulerTimer(); } } /// /// Use this to Start the Scheduler Backup /// //internal static void StartSchedulerBackup(uint IntervalInMiliseconds = 99986) internal static void StartSchedulerBackup(uint IntervalInMiliseconds) { lock (_HiddenWindow) { // Start the Scheduler Backup Only if there is any Scheduler Backup Information (and the Settings Window // isn't currently being shown - Settings window disables/enables Scheduler Backup on it's own * if (!App.State_IsSchedulerBackupExecuting() && App.setting.SchedulerBackupSettingsExist && !WPFWindowMaker.IsWindowOfTypeWPFWindowShowing_WithinLock(WPFWindow.Settings_Window)) StartSchedulerBackupTimer(IntervalInMiliseconds); } } /// /// Use this to Stop the Scheduler Backup /// internal static void StopSchedulerBackup() { lock (_HiddenWindow) { StopSchedulerBackupTimer(); } } #endregion #region Construction /// /// Constructor /// public HiddenMainWindow() { InitializeComponent(); // Only continue, if no fatal application error occured if (!App.s_FatalErrorOccured) { // * Force this window to remain hidden indefinitly* _HiddenWindow = new KeepHidden(this); // Assign ProcessExit Event to Handle more specialized Cleanup AppDomain.CurrentDomain.ProcessExit += new EventHandler(CurrentDomain_ProcessExit); // We DON'T NEED TO SHOW THE HIDDEN WINDOW * Triggering OnLoad(), if we are // being launched via the Command-Line. if (App.State_SpecialMode_IsCommandLineSet()) { // If we are in Command-Line Mode * Then Hidden Window should never be shown * // in fact we'll enter into a whole new entry point * CommandLine MODE ElapsedEventHandler EnterCommandLineMode = delegate(object sender, ElapsedEventArgs e) { Timer timer = (Timer)sender; timer.Stop(); // * Enter CommandLine Mode * CMDlineHandler.EnterCommandLineMode(); timer.Dispose(); timer = null; // Done Here, shut down the app App.Current.Shutdown(); }; TTimer timerEnterCommandLineMode = new TTimer(EnterCommandLineMode, 500, true, true); } else { // Calling Show() directly in the Constructor causes a .Net Run-time Crash // upon first launch only. Calling it .5 seconds later does the trick of avoiding that error, // because by then the object is fully constructed ElapsedEventHandler DelayShowHiddenWindow = delegate(object sender, ElapsedEventArgs e) { Timer timer = (Timer)sender; timer.Stop(); // * Show the Hidden Window * _HiddenWindow.Show(); timer.Dispose(); timer = null; }; TTimer timerDelayShowHiddenWindow = new TTimer(DelayShowHiddenWindow, 500, true, true); } } } #endregion #region Process Monitor / Service Monitor * CORE LOGIC * /// /// Called every Nth Interval, does the grunt of the work for this whole /// Application. It checks the processes, and if they are running, etc... /// * Core Business Logic * /// private static void TTimer_ElapsedEventHandler(object sender, ElapsedEventArgs e) { lock (_HiddenWindow) { // Stop the timer to prevent multiple call-Ins, while work is still being done StopTimer(); // Everything is Good, Monitor the Processes / Services Start any processes / Services that aren't available if (App.State_MonitorIsInRunningState() && !App.State_IsMonitorPaused()) { // Get the Configuration WatchdogConfiguration config = App.xmlfile.ReadData(false); // * NO CONFIGURATION * Stop! - Nothing to monitor if (!config.ConfiguredHasAny) { StopMonitoring(true, false); return; } // * Read the Ini Configuration Settings * TraceM.TraceBegin(); uint nMaxFailureCount = App.setting.MaxFailureCount; uint nMaxFailureCountHour = App.setting.MaxFailureCountHour; TraceM.TraceEnd("Monitor - Reading in INI Configuration"); // * SERVICE MONITORING * TraceM.TraceBegin(); bool bErrorThrownInServicesMonitor = !ServiceMonitor(config, nMaxFailureCount, nMaxFailureCountHour); TraceM.TraceEnd("* Service Monitoring *"); // * PROCESS MONITORING * TraceM.TraceBegin(); bool bErrorThrownInProcessMonitor = !ProcessMonitor(config, nMaxFailureCount, nMaxFailureCountHour); TraceM.TraceEnd("* Process Monitoring *"); // If Any Unknown Errors were Thrown! Stop the Monitor if (bErrorThrownInServicesMonitor || bErrorThrownInProcessMonitor) { StopMonitoring(true, false); //// // An Error Occured with the Application * Better Notify User, if specified * //// if (App.setting.EmailNotificationEnabled && App.setting.EmailSettingsValid) { bool bIsSend = Emailer.SendEmail(App.setting.EmailStmpServer, App.setting.EmailSenderEmail, App.APPLICATION_NAME_SHORT, App.setting.EmailReceiverEmail, AppResx.GetStringFormated("ERROR_EMAIL_SUBJECT", App.APPLICATION_NAME_SHORT), AppResx.GetStringFormated("ERROR_EMAIL_BODY", App.APPLICATION_NAME_SHORT, App.setting.EmailCustomMessageText), App.setting.EmailSmtpPort, String.Empty, App.setting.EmailStmpServerRequiresAuth, App.setting.EmailSmtpUsername, App.setting.EmailSmtpPassword, App.setting.EmailStmpServerRequiresSSL, 30); if (!bIsSend) App.log.Error("Failed to Send ERROR_EMAIL"); else App.log.Info(String.Format("Email (ERROR_EMAIL) Notification send to {0}", App.setting.EmailReceiverEmail)); } return; } if (!App.State_IsMonitorPaused()) // If the System Is Paused, no need to start the Timer again { // Re-Start the timer, if all went well // * At the end of each Cycle, Set the System Tray Icon Accordingly * Keep track when we were last run if(!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.SetSysTrayIconAccordingToRunningState(App.State_GetSystemState()); StartTimer(); // Everything went well, continue... } } } } /// /// Used by the Scheduler Timer to determine if a restart is to occur /// /// now datetime stamp /// scheduler start datetime stamp /// datetime when last run occured /// numeric value indicating hours or minutes /// true if hours, false if minutes /// true if we want the Scheduler to Restart, false otherwise private static bool PerformRestartTimeSchedulerTriggered(DateTime now, DateTime start, DateTime LastRun, uint nHoursOrMinutes, bool bIsHours) { // * OLD CALCULATION * OLD SCHOOL * Make sure enough time has passed since last run, otherwise no need to continue //TimeSpan tsLastRun = now - LastRun; //bool bEnoughTimeSinceLastRunHasPassed = bIsHours ? (((double)(tsLastRun.TotalMinutes / 60)) >= ((double)nHoursOrMinutes)) : (tsLastRun.TotalMinutes >= ((double)nHoursOrMinutes)); //return bEnoughTimeSinceLastRunHasPassed; // If the current time is within 2 minutes of last run, there is no need to continue TimeSpan tsDiffBetweenNowAndLastRun = (now - LastRun); bool bIsWithin2MinutesOfLastRun = (tsDiffBetweenNowAndLastRun.TotalMinutes >= 0) && (tsDiffBetweenNowAndLastRun.TotalMinutes <= 2.9997); if (bIsWithin2MinutesOfLastRun) return false; // Check the start time * if we are near the start time and a last run hasn't occured, then yes, run this * TimeSpan tsDiffBetweenNowAndStart = (now - start); bool bIsWithin2MinutesOfScheduledStartTime = (tsDiffBetweenNowAndStart.TotalMinutes >= 0) && (tsDiffBetweenNowAndStart.TotalMinutes <= 2.9997); if (bIsWithin2MinutesOfScheduledStartTime) return true; // Perform Cycle Calculation double durationInMinutes = bIsHours ? nHoursOrMinutes * 60 : nHoursOrMinutes; double remCycle = tsDiffBetweenNowAndStart.TotalMinutes % durationInMinutes; bool bIsWithin2MinutesOfRemCycle = (remCycle >= 0) && (remCycle <= 2.9997); return bIsWithin2MinutesOfRemCycle; } /// /// Called every Nth Interval, does the grunt of the work for the Scheduler /// * Core Business Logic * /// private static void TTimer_ElapsedEventHandlerScheduler(object sender, ElapsedEventArgs e) { lock (_HiddenWindow) { // *imp * stop the process/services monitor (so that while // this timer occurs it won't conflict with what happens here bool bPrevStateTimer = StopTimer(); // Stop the Scheduler timer to prevent multiple call-Ins, while work is still being done StopSchedulerTimer(); // Everything is Good, if (App.State_MonitorIsInRunningState() && !App.State_IsSchedulerExecuting() && !App.State_IsMonitorPaused()) { // Set the Scheduler State App.State_SchedulerExecution_Started(); // Fetch scheduler settings bool bFoundSchedulerSettings = false; DateTime dtStart = DateTime.MinValue; DateTime dtLastRun = DateTime.MinValue; uint nHoursOrMinutes = 0; bool bIsHours = true; // Only Do something if there are any settings bFoundSchedulerSettings = App.setting.GetSchedulerSettings(out dtStart, out dtLastRun, out nHoursOrMinutes, out bIsHours); if (!bFoundSchedulerSettings) return; // No Scheduler Settings, no need to continue // Get the Now Time DateTime dtNow = DateTime.Now; // Make sure we've reached the Start Time, else there is no need to continue if (dtNow >= dtStart && PerformRestartTimeSchedulerTriggered(dtNow, dtStart, dtLastRun, nHoursOrMinutes, bIsHours)) { // Log the fact that Scheduler is about to stop / start App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_STARTSTOP", App.APPLICATION_NAME_SHORT, App.APP_XML_DS_FILENAME)); // * Perform Restart * RestartMonitoring(4, true); // Set Last Run App.setting.LastSchedulerRun = dtNow; } // Reset the Scheduler State App.State_SchedulerExecution_Stopped(); } if (!App.State_IsMonitorPaused()) // If the System Is Paused, no need to start the Timers again { // Re-Start the Scheduler timer * Everything went well s * StartScheduler(); // Now it should be save to start the Main Monitor Timer if (bPrevStateTimer) StartTimer(); } } } /// /// Integrity - When we fire a start Trigger with SchedulerBackupTriggerOccured(), in case another call in occurs, /// we know to ignore it /// private static DateTime _LastStartTriggerFired = DateTime.MinValue; /// /// Integrity - When we fire a end Trigger with SchedulerBackupTriggerOccured(), in case another call in occurs, /// we know to ignore it /// private static DateTime _LastEndTriggerFired = DateTime.MinValue; /// /// Deterimes the State of the Scheduler Backup and what action to take /// /// The Now DateTime /// Scheduled Start DateTime /// Last Run, should be set when the first start occured and passed into here for checking /// the duration from start to end /// we want to perform the following action * Trigger Occured *, 0 = None, 1 = Start, 2 = End private static uint SchedulerBackupTriggerOccured (DateTime dtNow, DateTime dtScheduledStartDT, DateTime dtLastRun, TimeSpan tsDuration) { // Time Of Day Vars TimeSpan TimeOfDayNow = dtNow.TimeOfDay; TimeSpan TimeOfDayScheduled = dtScheduledStartDT.TimeOfDay; // Debug //dtNow = DateTime.Parse("8/13/2011 01:57:29 AM"); //TimeOfDayNow = new TimeSpan(1, 57, 29); //TimeOfDayScheduled = new TimeSpan(1, 56, 11); //tsDuration = new TimeSpan(4, 0, 0); //dtLastRun = DateTime.Parse("1/1/0001 12:00:00 AM"); // Time of Day Within Time Calc; TimeSpan tsDiffBetweenNowAndScheduled = (TimeOfDayNow - TimeOfDayScheduled); bool bIsWithin2MinutesOfScheduledTime = (tsDiffBetweenNowAndScheduled.TotalMinutes > 0) && (tsDiffBetweenNowAndScheduled.TotalMinutes <= 2.9997); TimeSpan tsDiffBetweenNowAndScheduledDurationEnd = TimeOfDayNow - (TimeOfDayScheduled + tsDuration); if(tsDiffBetweenNowAndScheduledDurationEnd.TotalMinutes < 0) // A Day must have passed tsDiffBetweenNowAndScheduledDurationEnd = tsDiffBetweenNowAndScheduledDurationEnd + new TimeSpan(24, 00, 00); bool bIsWithin2MinutesOfScheduledTimeDurationEnd = (tsDiffBetweenNowAndScheduledDurationEnd.TotalMinutes <= 2.9997); // Last Run Calculations * make sure to see if it was started prior // ~Last Run allows us to determine if there exists a start for an end, // we only check the end event to see if a start occured if (bIsWithin2MinutesOfScheduledTimeDurationEnd) { TimeSpan tsDiffBetweenNowAndLastRun = dtNow - dtLastRun; TimeSpan tsDiffInDuration = tsDiffBetweenNowAndLastRun - tsDuration; // In the event the Monitor App got started somewhere in between times, // and we trigger the end event (because time was reached), we // are checking last run, to see if it coincides with this end event, // if it doesn't (as in it is way too long ago ~here anything above 10 minutes out // of bounds from the duration), we won't trigger the end event, assuming // that it never started in this cycle to begin with, waiting for the next cycle bool bIsLastRunOutOfBounce = (tsDiffInDuration.TotalMinutes > 0) && (tsDiffInDuration.TotalMinutes >= 10); if (bIsLastRunOutOfBounce) return 0; } // Integrity Check * prevent multiple callins within time boundary 2.9997, * or actually // within the same day * may as well, we only allow 1 start/end trigger per day if (bIsWithin2MinutesOfScheduledTime) { TimeSpan ts = dtNow - _LastStartTriggerFired; if ((ts.TotalMinutes > 0) && (ts.TotalMinutes > 1000)) // we should only be firing on start trigger per day aka 1440, but 1000 is a good check (even) _LastStartTriggerFired = dtNow; else bIsWithin2MinutesOfScheduledTime = false; } else if (bIsWithin2MinutesOfScheduledTimeDurationEnd) { TimeSpan ts = dtNow - _LastEndTriggerFired; if ((ts.TotalMinutes > 0) && (ts.TotalMinutes > 1000)) // we should only be firing on end trigger per day aka 1440, but 1000 is a good check (even) _LastStartTriggerFired = dtNow; else bIsWithin2MinutesOfScheduledTime = false; } // Trigger Start / Stop / Or Nothing if (bIsWithin2MinutesOfScheduledTime) return 1; else if (bIsWithin2MinutesOfScheduledTimeDurationEnd) return 2; else return 0; } /// /// Called every Nth Interval, does the grunt of the work for the Scheduler Backup /// * Core Business Logic * /// private static void TTimer_ElapsedEventHandlerSchedulerBackup(object sender, ElapsedEventArgs e) { lock (_HiddenWindow) { // Stop the Scheduler Backup timer to prevent multiple call-Ins, while work is still being done StopSchedulerBackupTimer(); // Everything is Good, //if (App.State_MonitorIsInRunningState() && !App.State_IsSchedulerBackupExecuting()) // BUG if (!App.State_IsSchedulerBackupExecuting()) { // Set the Scheduler State App.State_SchedulerBackupExecution_Started(); // Fetch scheduler Backup settings bool bFoundSchedulerBackupSettings = false; DateTime dtStart = DateTime.MinValue; DateTime dtLastRun = DateTime.MinValue; uint nHoursOrMinutes = 0; bool bIsHours = true; // Only Do something if there are any settings bFoundSchedulerBackupSettings = App.setting.GetSchedulerBackupSettings(out dtStart, out dtLastRun, out nHoursOrMinutes, out bIsHours); if (!bFoundSchedulerBackupSettings) return; // No Scheduler Backup Settings, no need to continue // Get the Now Time DateTime dtNow = DateTime.Now; // Make sure we've reached the Start Time, else there is no need to continue if (dtNow >= dtStart) { // Get the Duration TimeSpan tsDuration = bIsHours ? TimeSpan.FromHours(nHoursOrMinutes) : TimeSpan.FromMinutes(nHoursOrMinutes); // Determine the State of the Scheduler Backup an the Action To Take uint nTriggerResult = SchedulerBackupTriggerOccured(dtNow, dtStart, dtLastRun, tsDuration); bool bPerformAnAction = (nTriggerResult == 1) || (nTriggerResult == 2); if (bPerformAnAction) { // Get Current State App.StateSystem state = App.State_GetSystemState(); bool bStopInsteadOfPause = App.setting.SchedulerBackupStopInsteadOfPause; bool bStopServices = App.setting.SchedulerBackupStopServices; // Log the fact that Scheduler Backup is about to pause / stop / start App.log.Info(AppResx.GetStringFormated("MESSAGE_SCHEDULER_BACKUP_STARTSTOP", App.APPLICATION_NAME_SHORT, (state == App.StateSystem.Paused))); // * Perform System Pause * or // * Perform System Start *, or * System Stop *, depending on system state and Settings switch (nTriggerResult) { case 1: // Duration Start { // We are in a wrong state to perform a start if (state != App.StateSystem.Error && state != App.StateSystem.Running) { App.log.Error(String.Format("SchedulerBackup Wrong State for Action 'Duration Start' for State {0}. SchedulerBackup Trigger Ignored", Enum.GetName(typeof(App.StateSystem), state))); } else { // Perform Action if (bStopInsteadOfPause) StopMonitoring(true, bStopServices); else PauseMonitoring(true); // Mark Scheduler Backup as Started App.setting.LastSchedulerBackupRun = dtNow; } break; } case 2: // Duration End { // We are in a wrong state to perform a start if (state != App.StateSystem.Paused && state != App.StateSystem.Stopped) { App.log.Error(String.Format("SchedulerBackup Wrong State for Action 'Duration End' for State {0}. SchedulerBackup Trigger Ignored", Enum.GetName(typeof(App.StateSystem), state))); } else { // Perform Action StartMonitoring(false, true); } break; } } } } // Reset the Scheduler Backup State App.State_SchedulerBackupExecution_Stopped(); } // Re-Start the Scheduler Backup timer * Waiting for the next iteration * StartSchedulerBackup(99986); } } /// /// * Core Logic * Monitor Processes from the Configuration. Start Any Processes that are Ok to /// be started (ProcessOkToBeStarted_Check) that are currently not running /// /// Monitor Configuration /// Max Failure Count /// Max Failure Count in given Hour Timespan /// true if successfull, false if ProcessMonitor Threw an Error private static bool ProcessMonitor(WatchdogConfiguration config, uint nMaxFailureCount, uint nMaxFailureCountHour) { // * PROCESS MONITORING * if (config.ConfiguredHasAnyProcesses) { try { // Start Any Processes that are NOT Running uint uProcessStartSuccessCount = 0; uint uProcessStarFailCount = 0; List nonRunningProcesses = Processes_ThatNeedToBeStarted_Check(config); if (nonRunningProcesses.Count > 0) { // Iterate each non-running Process and try to Start it foreach (WatchdogConfiguration.ProcessExe mProcess in nonRunningProcesses) { // Check that the Process can be started if (!ProcessOkToBeStarted_Check(mProcess, nMaxFailureCount, nMaxFailureCountHour)) continue; App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_START_PROCESS", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms), -1); uint PID = PStarter.StartProcess(PStartInfo.CreateProcess(mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, mProcess.WorkingDirectory, false, ProcessWindowStyle.Normal, true), false, false); if (PID > 0) { // Add to Start Attempt, Set the PID, log, and incr. Counter mProcess.AddStartTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure mProcess.PID = PID; App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_SUCCESS", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms), -1); App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_SUCCESS2", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, mProcess.GetStartTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); uProcessStartSuccessCount++; } else { // Add to Failure, Reset the PID, log, and incr. Counter mProcess.AddFailTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure mProcess.PID = 0; App.log.Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms), -1); App.log.Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED2", mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, mProcess.GetFailTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); uProcessStarFailCount++; } } // Imp! - Let the User know Visually that Processes were started or failed to start if (uProcessStartSuccessCount > 0 && uProcessStarFailCount == 0) { App.log.Info(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_COUNT", uProcessStartSuccessCount)); if(!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_COUNT", (int)uProcessStartSuccessCount)); } else if (uProcessStartSuccessCount == 0 && uProcessStarFailCount > 0) { App.log.Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_FAIL_COUNT", uProcessStarFailCount)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_FAIL_COUNT", (int)uProcessStarFailCount)); } else if (uProcessStartSuccessCount > 0 && uProcessStarFailCount > 0) { App.log.Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_AND_N_FAIL_COUNT", uProcessStartSuccessCount, uProcessStarFailCount)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_PROCESS_N_SUCCESS_AND_N_FAIL_COUNT", (int)uProcessStartSuccessCount, (int)uProcessStarFailCount)); } // If any Process Changes occured * Refresh the System Tray Notification Area * if (uProcessStartSuccessCount > 0 || uProcessStarFailCount > 0) { if (!App.State_SpecialMode_IsRunAsServiceSet()) Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); } } else { // We are in a * good State * No Configuration was found that isn't running, // use this time to make sure that our StartedPID Table is in sync * Perform general internal DS Cleanup * PStarter.PerformGeneralCleanupOfAllStartedProcessIDs(config.MonitoredProcesses.QueryAllPidInfo(false, true)); } } catch (Exception ex) { App.log.Error("Process Monitor Running Error Thrown", ex); return false; } } return true; } /// /// Checks to see if the process is Ok to be Started. Checks error state as well as timeout values /// /// a valid process exe, that is going to be started /// Max Failure Count /// Max Failure Count in given Hour Timespan /// true if it is ok to start, false otherwise private static bool ProcessOkToBeStarted_Check(WatchdogConfiguration.ProcessExe processExe, uint nMaxFailureCount, uint nMaxFailureCountHour) { if (processExe == null) return false; // * Ignore any process marked as errored * if (processExe.InErrorState) return false; // If there was a failure starting the process before,... check // the last failed time flag, we don't want to try over and over again, every second, // but we'll give it a try every minute if (!processExe.LastFailTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) { return false; } else if (processExe.GetFailTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) { // We are now officially in an error state App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); processExe.InErrorState = true; App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); return false; // skip Starting of this Process; Start Failure Count too High } // If the process started correctly, but then failed shortly thereafter, // then we want to keep track of it and make sure that don't try to continue starting it if (!processExe.LastStartTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) { return false; } else if (processExe.GetStartTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) { // We are now officially in an error state App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); processExe.InErrorState = true; App.log.Info(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_PROCESS_FAILED_ERROR_STATE", processExe.ProcessExeFileNameNPath, processExe.CommandLinePrms)); return false; // skip Starting of this Process; Start Count too High } return true; } /// /// Static variable * For Performance * used by Processes_ThatNeedToBeStarted_Check() /// private static List s_nonRunningProcesses = new List(); /// /// Goes thru the configuration and returns back a list of all processes that need to be started /// /// Monitor Configuration /// List of all processes currently not running that require a new start, empty list if none found private static List Processes_ThatNeedToBeStarted_Check(WatchdogConfiguration config) { // Clear Non-Running List s_nonRunningProcesses.Clear(); // We don't want to run thru all command-line parameters all the time * Time-Hog * // ~instead we want to check our run-time configuration to see if we have any start/fail information, // if we don't, then we MUST run thru everything, because this means our PID info will be out of date // ~If we are in an Error state, don't perform in-depth search, otherwise once the error state is reached // we'll always fail here and always perform the in-depth search pointlessly since we'll never launch that process bool bPerformInDepthProcessSearch = config.MonitoredProcesses.HasIncompletePidInformation() && (App.State_GetSystemState() != App.StateSystem.Error); if (bPerformInDepthProcessSearch) { // Cache Process Lookups * for performance * ProcessW.ClearProcessObjsGetterCache(); foreach (WatchdogConfiguration.ProcessExe mProcess in config.MonitoredProcesses.ProcessExes) { // Immediatly Cache the Process Getter Calls * Better Performance * Process[] RunningProcessesOfExe = ProcessW.AllRunningProcessesOf(mProcess.ProcessName, true, true); // Perform ExactProcessExeNCommandLinePrms Matching Process rProcess = null; int nProcessIndex = -1; TraceM.TraceBegin(); bool bFoundMatch = ProcessW.FoundExactProcessExeNCommandLinePrmsInRunningProcesses(RunningProcessesOfExe, mProcess.ProcessExeFileNameNPath, mProcess.CommandLinePrms, (int)mProcess.PID, out rProcess, out nProcessIndex); TraceM.TraceEnd("Calling FoundExactProcessExeNCommandLinePrmsInRunningProcessess"); // If no Match was found in the Running Processes, add to the list if (!bFoundMatch) { s_nonRunningProcesses.Add(mProcess); } else if(rProcess != null) // we found a match * update the configuration * { // Update the Pid in our configuration mProcess.PID = (uint)rProcess.Id; rProcess = null; } } } else // Quick Search * Better Performances * { // Fastest way to check for PID changes is to retrieve // all Pids on the system once and then do everything in memory TraceM.TraceBegin(); List AllComputerPIDs = ProcessW.AllRunningPids(); foreach (uint pid in config.MonitoredProcesses.QueryAllPidInfo(false, true)) { if (!AllComputerPIDs.Contains(pid)) { WatchdogConfiguration.ProcessExe p = config.MonitoredProcesses.GetProcessExeForPid(pid); if(p != null) s_nonRunningProcesses.Add(p); } } TraceM.TraceEnd("Going Thru Applications Pid-by-Pid"); } // Return all Processes that aren't running return s_nonRunningProcesses; } /// /// Iterates thru the Configured Processes and Try's to Kill Any Processes that /// are running. /// private static void ProcessKillAllMonitoredProcessesIfAny(WatchdogConfiguration config) { // Kill all Processes that exist in our configuration uint nAssumedRunningProcesses = 0; uint nErroredProcesses = 0; uint nProcessesKilled = 0; // Allow the User to not kill certain processes via the Restart Scheduler bool bIncludeNonRestartProcesses = !App.State_IsSchedulerExecuting(); // Check Configuration if (config.ConfiguredHasAnyProcesses) { nErroredProcesses = config.MonitoredProcesses.NumberOfPidsInErrorState(); if (nErroredProcesses > 0) App.log.Info(AppResx.GetStringFormated("MESSAGE_NPROCESSES_IN_AN_ERROR_STATE", App.APPLICATION_NAME_SHORT, nErroredProcesses)); nAssumedRunningProcesses = (uint)config.MonitoredProcesses.QueryAllPidInfo(false, bIncludeNonRestartProcesses).Length; if (nAssumedRunningProcesses > 0) App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_CLOSE_N_PROCESSES", App.APPLICATION_NAME_SHORT, nAssumedRunningProcesses)); if (nAssumedRunningProcesses <= 0) // nothing to do here return; } else { return; // nothing to do here } // Iterate the Configuration * Use the PIDs whe have stored * foreach (uint pid in config.MonitoredProcesses.QueryAllPidInfo(false, bIncludeNonRestartProcesses)) { WatchdogConfiguration.ProcessExe p = config.MonitoredProcesses.GetProcessExeForPid(pid); bool bSuccess = PStarter.KillProcess(pid, false, 1, true, 2); if (bSuccess) { App.log.Info(AppResx.GetStringFormated("MESSAGE_CLOSE_PID_SUCCESS", pid, p.ProcessExeFileNameNPath, p.CommandLinePrms), -1); nProcessesKilled++; } else { App.log.Error(AppResx.GetStringFormated("MESSAGE_CLOSE_PID_FAILURE", pid, p.ProcessExeFileNameNPath, p.CommandLinePrms), -1); } } // Refresh the taskbar, after killing all the processes if(nProcessesKilled > 0 && !App.State_SpecialMode_IsRunAsServiceSet()) Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); // Let the User know that errors occured if (App.State_GetSystemState() != App.StateSystem.Error) /* if We already are in an error state, no need to pop-up a message again */ { if (nProcessesKilled != nAssumedRunningProcesses) { if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_FAILURE", (int)(nAssumedRunningProcesses - nProcessesKilled))); App.log.Error(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_FAILURE", (config.MonitoredProcesses.ProcessExes.Length - nProcessesKilled))); } else { if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_SUCCESS", nProcessesKilled)); App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_CLOSE_N_PROCESSES_SUCCESS", nProcessesKilled)); } } } /// /// * Core Logic * Monitor Services from the Configuration. Start Any Services that are Ok to /// be started (ServiceOkToBeStarted_Check) that are currently not running /// /// Monitor Configuration /// Max Failure Count /// Max Failure Count in given Hour Timespan /// true if successfull, false if ServiceMonitor Threw an Error private static bool ServiceMonitor(WatchdogConfiguration config, uint nMaxFailureCount, uint nMaxFailureCountHour) { // * SERVICE MONITORING * if (config.ConfiguredHasAnyServices) { try { // Check To see if the Service is up and running, if it isn't, restart it * simple stuff * uint uServiceStartSuccessCount = 0; uint uServiceStartFailCount = 0; foreach (WatchdogConfiguration.ServiceExe s in config.MonitoredServices.ServiceExes) { // Start the Service up to Five Times * Simplified way of doing this * TraceM.TraceBegin(); bool bIsServiceRunning = ServiceW.IsServiceRunning(s.Name); TraceM.TraceEnd("Checking Service is Running"); if (!bIsServiceRunning) { // Check that the Service can be started if (!ServiceOkToBeStarted_Check(s, nMaxFailureCount, nMaxFailureCountHour)) continue; // * Try to Restart the Service * TraceM.TraceBegin(); App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_RESTART_SERVICE", s.Name), -1); bool bRestartOk = ServiceW.RestartService(s.Name, true, 240 , 2); TraceM.TraceEnd("Restarting Service"); if (bRestartOk) { // Add to Start Attempt, Log the Successful Launch, incr. Start Service Counter s.AddStartTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure App.log.Info(AppResx.GetStringFormated("MESSAGE_START_SERVICE_SUCCESS", s.Name, s.GetStartTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); uServiceStartSuccessCount++; } else { // Add to Failure, Log the Errored Launch, incr. Failed Service Counter s.AddFailTime(DateTime.Now, (int)(nMaxFailureCount + 2)); // *Always* Keep the List 2 bigger than nMaxFailureCount, this way we can track up to nMaxFailure App.log.Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED", s.Name, s.GetFailTimesInGivenTimeSpan(TimeSpan.FromHours(nMaxFailureCountHour)), nMaxFailureCountHour), -1); uServiceStartFailCount++; } } } // Imp! - Let the User know Visually that Processes were started or failed to start if (uServiceStartSuccessCount > 0 && uServiceStartFailCount == 0) { App.log.Info(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_COUNT", uServiceStartSuccessCount)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_COUNT", (int)uServiceStartSuccessCount)); } else if (uServiceStartSuccessCount == 0 && uServiceStartFailCount > 0) { App.log.Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_FAIL_COUNT", uServiceStartFailCount)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_FAIL_COUNT", (int)uServiceStartFailCount)); } else if (uServiceStartSuccessCount > 0 && uServiceStartFailCount > 0) { App.log.Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_AND_N_FAIL_COUNT", uServiceStartSuccessCount, uServiceStartFailCount)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_SERVICE_N_SUCCESS_AND_N_FAIL_COUNT", (int)uServiceStartSuccessCount, (int)uServiceStartFailCount)); } } catch (Exception ex) { App.log.Error("Error Thrown in Service Monitor", ex); return false; }; } return true; } /// /// Checks to see if the service is Ok to be Started. Checks error state as well as timeout values /// /// a valid service exe, that is going to be started /// Max Failure Count /// Max Failure Count in given Hour Timespan /// true if it is ok to start, false otherwise private static bool ServiceOkToBeStarted_Check(WatchdogConfiguration.ServiceExe serviceExe, uint nMaxFailureCount, uint nMaxFailureCountHour) { if (serviceExe == null) return false; // * Ignore any services marked as errored * if (serviceExe.InErrorState) return false; // If there was a failure starting the Service before,... check // the last failed time flag, we don't want to try over and over again, every second, // but we'll give it a try every minute if (!serviceExe.LastFailTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) { return false; } else if (serviceExe.GetFailTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) // Default 1 Hour { // We are now officially in an error state App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); serviceExe.InErrorState = true; App.log.Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); return false; // skip Starting of this Service; Start Failure Count too High } // If the service started correctly, but then failed shortly thereafter, // then we want to keep track of it and make sure that don't try to continue starting it if (!serviceExe.LastStartTimeTimeoutExceeded(TimeSpan.FromMinutes(1))) { return false; } else if (serviceExe.GetStartTimesInGivenTimeSpanMaxTryExceeded(new TimeSpan((int)nMaxFailureCountHour, 0, 0), nMaxFailureCount)) // Default 1 Hour { // We are now officially in an error state App.State_Error_SetErrorState(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); serviceExe.InErrorState = true; App.log.Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_START_SERVICE_FAILED_ERROR_STATE", serviceExe.Name)); return false; // skip Starting of this Service; Start Count too High } return true; } /// /// Iterates thru the Configured Processes and Try's to Kill Any Services that /// are running and configured to be Stopped. /// /// Set to true to force shutting down of services, false otherwise //private static void ServicesKillAllMonitoredServicesIfAny(WatchdogConfiguration config, bool bForceShutdownOfServices = false) private static void ServicesKillAllMonitoredServicesIfAny(WatchdogConfiguration config, bool bForceShutdownOfServices) { // Should we shutdown Services??? bool bRestartServices = bForceShutdownOfServices || (App.State_IsSchedulerExecuting() && App.setting.SchedulerRestartsServices); if (bRestartServices) { int nServicesClosed = 0; int nServicesNotClosed = 0; // Allow the User to not kill certain services via the Restart Scheduler bool bIncludeNonRestartServices = !App.State_IsSchedulerExecuting(); if (config.ConfiguredHasAnyServices) App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_CLOSE_N_SERVICES", App.APPLICATION_NAME_SHORT, config.MonitoredServices.QueryAllServiceInfo(false, bIncludeNonRestartServices).Length)); foreach (string ServiceName in config.MonitoredServices.QueryAllServiceInfo(false, bIncludeNonRestartServices)) { // This is only getting called when the scheduler is executing... // the scheduler will be responsible for starting the monitor which in turn will start the services and keep track of all // service starts, hence all we have to do here is shut down the services * they will automatically be restarted * App.log.Info(AppResx.GetStringFormated("MESSAGE_ABOUT_TO_CLOSE_SERVICE", ServiceName), -1); bool bCloseServiceSuccess = ServiceW.StopService(ServiceName, true, 240); if (bCloseServiceSuccess) { App.log.Info(AppResx.GetStringFormated("MESSAGE_CLOSE_SERVICE_SUCCESS", ServiceName), -1); nServicesClosed++; } else { App.log.Error(AppResx.GetStringFormated("MESSAGE_CLOSE_SERVICE_FAILURE", ServiceName), -1); nServicesNotClosed++; } } // Imp! - Let the User know Visually that Services were Stopped if (nServicesClosed > 0 && nServicesNotClosed == 0) { App.log.Info(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_COUNT", nServicesClosed)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_COUNT", nServicesClosed)); } else if (nServicesClosed == 0 && nServicesNotClosed > 0) { App.log.Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_FAIL_COUNT", nServicesNotClosed)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_FAIL_COUNT", nServicesNotClosed)); } else if (nServicesClosed > 0 && nServicesNotClosed > 0) { App.log.Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_AND_N_FAIL_COUNT", nServicesClosed, nServicesNotClosed)); if (!App.State_SpecialMode_IsRunAsServiceSet()) WatchdogSysTray.PopUpBallon_Error(AppResx.GetStringFormated("MESSAGE_STOPSERVICE_N_SUCCESS_AND_N_FAIL_COUNT", nServicesClosed, nServicesNotClosed)); } } } #endregion #region Loaded/Unloaded Event Handlers /// /// Handles the Main Window StartUp event. /// private void Window_Loaded(object sender, RoutedEventArgs e) { // * To make sure, we are not fatally closing * if (App.s_FatalErrorOccured) return; // * To make sure, do nothing in command-line mode * if (App.State_SpecialMode_IsCommandLineSet()) return; if (!App.State_SpecialMode_IsRunAsServiceSet()) { // Performance Load WPF Windows WPFWindows.WPFWindowMaker.PerformanceCache(); // Set Registry key to Start w. Windows, if specified bool bStartWithWindows = App.setting.StartWithWindows; App.log.Info(String.Format("Starting with Windows is set to: '{0}'", bStartWithWindows)); } // Ensure proper existence of HardLink, if specified bool bCreateHardLink = App.setting.CreateExeHardLinkInRootPath; App.log.Info(String.Format("Creating .Exe HardLink in Root Path is set to: '{0}'", bCreateHardLink)); // If we are deployed by ClickOnce, let's modify the uninstall icon (Add/Remove) Programs // ~there appears to be no better way than modifying the registry manually * Now Add/Remove will have a nice // little App icon instead of the default ClickOnce Icon * string strKeyFetched = RegKey.GetKey("Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + App.APPLICATION_PRODUCT_CLICKONCE_ID, "DisplayIcon", ""); if(!String.IsNullOrEmpty(strKeyFetched)) RegKey.SetKey("Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + App.APPLICATION_PRODUCT_CLICKONCE_ID, "DisplayIcon", AssemblyW.SpecializedAssemblyInfo.GetAssemblyFileNameNPath(AssemblyW.AssemblyST.Entry)); // Sleep a little before showing the System Tray System.Threading.Thread.Sleep(250); if (!App.State_SpecialMode_IsRunAsServiceSet()) { // Refresh the taskbar, when launching (maybe there are trailing instances) Watchdog.WatchdogLib.Win32.Functions.RefreshTaskbarNotificationArea(); // Show the System Tray Icon/Menu WatchdogSysTray.WatchdogSysTray_Show(); } // Start Process Monitoring upon start? * Default is Yes * if (App.setting.StartMonitorUponStart) { // We should only start the monitor, if there is any configuration if (App.xmlfile.ReadData(false).ConfiguredHasAny) { StartMonitoring(false, true); } else if (!App.State_SpecialMode_IsRunAsServiceSet()) { WatchdogSysTray.PopUpBallon_Info(AppResx.GetStringFormated("MESSAGE_NO_CONFIGURATION_FOUND", App.APP_XML_DS_FILENAME)); WPFWindows.WPFWindowMaker.CreateNShowWindow(WPFWindows.WPFWindow.Settings_Window); } else { // Nothing to do Here (we must be in Service Mode), without a configuration App.FatalExceptionStopExecution("No Configuration Found in ServiceMode", false); } } } /// /// Handles the Main Window Exit event. /// private void Window_Closed(object sender, EventArgs e) { // * To make sure, we are not fatally closing * if (App.s_FatalErrorOccured) return; // * To make sure, do nothing in command-line mode * if (App.State_SpecialMode_IsCommandLineSet()) return; // for unexpected cleanup s_WindowCloseGotCalled = true; // Stop the Process Monitor StopMonitoring(false, false); // Make sure the latest DS is saved to file App.xmlfile.SaveData(); if (!App.State_SpecialMode_IsRunAsServiceSet()) { // Close the System Tray Icon/Menu WatchdogSysTray.WatchdogSysTray_Hide(); // Unload All Windows (incl. Performance) * Close Log Tail if exists Prior * LogViewerWindow.CloseFileTailBeforeExitIfExists(); WPFWindows.WPFWindowMaker.CloseAll(); } } /// /// Handle Specialized Cleanup /// void CurrentDomain_ProcessExit(object sender, EventArgs e) { try { // Stop Internal Timers StopTimer(); StopSchedulerTimer(); StopSchedulerBackupTimer(); if (_timer != null) _timer.Dispose(); if (_timerScheduler != null) _timerScheduler.Dispose(); if (_timerSchedulerBackup != null) _timerSchedulerBackup.Dispose(); // Make sure that the close get's called * Unexpected cleanup * if (!s_WindowCloseGotCalled) Window_Closed(null, null); } catch (Exception) { /* ignore */ } } #endregion } }