/*
* 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 });
}
}