/* * Author: Satria Sudewo (webmaster@satria.de) * License: GPL (GNU General Public License, open source) * This software is delivered "as is", without any warranty. Use at own risk! * Bugs may be reported and might be fixed on a "free-will" basis. * */ using System; using System.ComponentModel; using System.Configuration.Install; using System.Diagnostics; using System.ServiceProcess; using System.Runtime.InteropServices; using System.Timers; static class Program { static void Main(string[] args) { ServiceBase[] ServicesToRun = new ServiceBase[] { new CStayAwake(args) }; ServiceBase.Run(ServicesToRun); } } public class CStayAwake : ServiceBase { [DllImport("kernel32.dll")] static extern bool IsSystemResumeAutomatic(); [DllImport("kernel32.dll")] static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE Flags); [Flags] public enum EXECUTION_STATE : uint { ES_SYSTEM_REQUIRED = 0x00000001, ES_DISPLAY_REQUIRED = 0x00000002, ES_USER_PRESENT = 0x00000004, ES_CONTINUOUS = 0x80000000 } Timer KeepAliveTimer, ExecDelayTimer; Process procCmd; ProcessStartInfo psiCmd; string strSource; string strLog; string strMessage; string strExecFile; string strExecCmd; string strExecParam; int iDelay; bool bDebug, bWasSuspended, bIsWakingUp; public CStayAwake(string[] p_args) { this.CanHandlePowerEvent = true; if (p_args.Length > 0) bDebug = p_args[0].ToLower() == "/debug"; bWasSuspended = false; iDelay = 15; strExecFile = (new System.IO.FileInfo(Process.GetCurrentProcess().MainModule.FileName)).DirectoryName + "\\StayAwakeExec.txt"; // Prepare System-log strSource = "StayAwake"; strLog = "Application"; if (!EventLog.SourceExists(strSource)) EventLog.CreateEventSource(strSource, strLog); // Prepare timers KeepAliveTimer = new Timer(60000); KeepAliveTimer.Elapsed += new ElapsedEventHandler(KeepAliveTimer_Elapsed); ExecDelayTimer = new Timer(iDelay); ExecDelayTimer.Elapsed += new ElapsedEventHandler(ExecDelayTimer_Elapsed); ExecDelayTimer.AutoReset = false; // Startmeldung bei "debug" if (bDebug) EventLog.WriteEntry(strSource, "StayAwake started in debug mode. Version " + this.Version, EventLogEntryType.Information, 5400); #if DEBUG this.OnPowerEvent(PowerBroadcastStatus.ResumeAutomatic); #endif } protected override void OnStop() { SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS); KeepAliveTimer.Stop(); EventLog.WriteEntry(strSource, "StayAwake is exiting! ThreadExecutionState back to normal, idle timer off.", EventLogEntryType.Information, 5406); base.OnStop(); } void KeepAliveTimer_Elapsed(object sender, ElapsedEventArgs e) { // This timer is called to keep the system awake SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED); } void ExecDelayTimer_Elapsed(object sender, ElapsedEventArgs e) { // This timer is called once for the delayed execution of the command stored in ".\StayAwakyExec.txt" ExecDelayTimer.Stop(); try { procCmd = new Process(); psiCmd = new ProcessStartInfo(strExecCmd); psiCmd.CreateNoWindow = false; psiCmd.WindowStyle = ProcessWindowStyle.Normal; psiCmd.Arguments = strExecParam; procCmd.StartInfo = psiCmd; procCmd.Start(); EventLog.WriteEntry(strSource, strExecCmd + " " + strExecParam + " executed.", EventLogEntryType.Information, 5408); } catch { EventLog.WriteEntry(strSource, "Executing " + strExecCmd + " " + strExecParam + " FAILED!", EventLogEntryType.Warning, 5418); } } /// /// This method is triggered on several power-related events. /// protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus) { EXECUTION_STATE? prevstate; strMessage = ""; bIsWakingUp = false; switch (powerStatus) { case PowerBroadcastStatus.BatteryLow: if (bDebug) strMessage = "Battery power is low."; break; case PowerBroadcastStatus.OemEvent: if (bDebug) strMessage = "An Advanced Power Management (APM) BIOS signaled an APM OEM event."; break; case PowerBroadcastStatus.PowerStatusChange: if (bDebug) strMessage = "A change in the power status of the computer is detected, such as a switch from battery power to A/C or if the battery power changes by a specified percentage."; break; case PowerBroadcastStatus.QuerySuspend: if (bDebug) strMessage = "The system has requested permission to suspend the computer."; bWasSuspended = true; break; case PowerBroadcastStatus.QuerySuspendFailed: if (bDebug) strMessage = "The system was denied permission to suspend the computer."; bWasSuspended = false; break; case PowerBroadcastStatus.ResumeAutomatic: strMessage = "The computer has woken up automatically to handle an event."; bIsWakingUp = true; break; case PowerBroadcastStatus.ResumeCritical: strMessage = "The system has resumed operation after a critical suspension caused by a failing battery."; bIsWakingUp = true; break; case PowerBroadcastStatus.ResumeSuspend: strMessage = "The system has resumed operation after being suspended."; bIsWakingUp = true; break; case PowerBroadcastStatus.Suspend: if (bDebug) strMessage = "The computer is about to enter a suspended state."; bWasSuspended = true; break; } // Do this only once on wakeup. And do this explicitly when debug is enabled if (bWasSuspended || bDebug) { if (bIsWakingUp) bWasSuspended = false; if (strMessage != "") EventLog.WriteEntry(strSource, strMessage, EventLogEntryType.Information, 5402); } if (powerStatus == PowerBroadcastStatus.ResumeAutomatic && IsSystemResumeAutomatic()) { // Terminal services requires active display, otherwise users are not able to login prevstate = SetThreadExecutionState(EXECUTION_STATE.ES_DISPLAY_REQUIRED); if (prevstate == null) EventLog.WriteEntry(strSource, "Display activated after suspend FAILED!", EventLogEntryType.Warning, 5413); else EventLog.WriteEntry(strSource, "Display activated after suspend.", EventLogEntryType.Information, 5403); // Set the system to continuously reset its idle timer KeepAliveTimer.Start(); //prevstate = SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED); // Deactivated, because it did not help. The KeepAliveTimer does it all. /*if (prevstate == null) EventLog.WriteEntry(strSource, "Keep-live enabled after suspend FAILED!", EventLogEntryType.Warning, 5414); else*/ EventLog.WriteEntry(strSource, "Keep-live enabled after suspend.", EventLogEntryType.Information, 5404); // Execute the command, defined in .\StayAwakeExec.txt if (System.IO.File.Exists(strExecFile)) { ReadExecFile(out strExecCmd, out strExecParam, out iDelay); if (bDebug) EventLog.WriteEntry(strSource, "Command string read from " + strExecFile + ".\n" + strExecCmd + " " + strExecParam, EventLogEntryType.Information, 5407); ExecDelayTimer.Interval = Math.Min(Math.Max(iDelay * 1000, 15), Double.MaxValue); // Minimum allowed: 15ms, Maximum allowed: max. double-value ExecDelayTimer.Enabled = false; // If the timer has not yet been triggered from the previous call ExecDelayTimer.Start(); } } if (powerStatus == PowerBroadcastStatus.ResumeSuspend) { // This is triggered on the first user interaction on keyboard or mouse KeepAliveTimer.Stop(); //prevstate = SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS); // On my PC this caused the monitor to flicker shortly, causing my TV application (if running) to hang. /*if (prevstate == null) EventLog.WriteEntry(strSource, "Keep-alive disabled after user interaction FAILED!", EventLogEntryType.Warning, 5415); else*/ EventLog.WriteEntry(strSource, "Keep-alive disabled after user interaction.", EventLogEntryType.Information, 5405); } return base.OnPowerEvent(powerStatus); } /// /// Reads the first line of the file ".\StayAwakeExec.txt" and parses it. It returns the command and its parameters and a delay if specified as a prefix of the command separated by "?". Example: 10?"C:\WINDOWS\notepad.exe" C:\boot.ini /// private void ReadExecFile(out string p_Cmd, out string p_Param, out int p_Delay) { System.IO.StreamReader sr; string[] strParts; string strTmp, strCmd, strSeparator; try { sr = System.IO.File.OpenText(strExecFile); strTmp = sr.ReadLine().Trim(); sr.Close(); // Determine if a delay is given in front of the command, separated by "?". strParts = strTmp.Split(new string[] { "?" }, StringSplitOptions.RemoveEmptyEntries); if (strParts.Length > 1) { // Separate delay from command strCmd = strParts[1].Trim(); Int32.TryParse(strParts[0].Trim(), out p_Delay); p_Delay = Math.Max(p_Delay, 0); // Must not be smaller than 0s } else { // no delay given strCmd = strParts[0].Trim(); p_Delay = 0; } // Separate command from parameters strSeparator = (strCmd.StartsWith("\"")) ? "\"" : " "; strParts = strCmd.Split(new string[] { strSeparator }, StringSplitOptions.RemoveEmptyEntries); p_Cmd = strParts[0].Replace("\"", ""); p_Param = ""; if (strParts.Length > 1) { for (int ix = 1; ix <= strParts.Length - 1; ix++) p_Param += strParts[ix].Trim() + " "; } p_Param = p_Param.Trim(); // Add missing full path to command if (p_Cmd.Substring(0, 1) == "\\") p_Cmd = Process.GetCurrentProcess().MainModule.FileName.Substring(0, 2) + strExecCmd; // put drive letter + : in front if (p_Cmd.Substring(1, 1) != ":") p_Cmd = (new System.IO.FileInfo(Process.GetCurrentProcess().MainModule.FileName)).DirectoryName + "\\" + strExecCmd; } catch { p_Cmd = ""; p_Param = ""; p_Delay = 0; } } private string Version { get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Major.ToString() + "." + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Minor.ToString() + "." + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Build.ToString(); } } } [RunInstaller(true)] public class ProjectInstaller : Installer { public ProjectInstaller() { ServiceProcessInstaller serviceProcessInstaller1 = new ServiceProcessInstaller(); serviceProcessInstaller1.Account = ServiceAccount.LocalSystem; ServiceInstaller serviceInstaller1 = new ServiceInstaller(); serviceInstaller1.Description = "After Windows resumes from Standby or Hibernation, this service ensures that Windows does not return to sleep after the \"unattended idle timer\""; serviceInstaller1.DisplayName = "Stay Awake"; serviceInstaller1.ServiceName = "StayAwake"; serviceInstaller1.StartType = ServiceStartMode.Automatic; Installers.AddRange(new Installer[] { serviceProcessInstaller1, serviceInstaller1 }); } }