Introduction
Previously I wrote:
In this article, I show how you can auto shutdown a PC (server) on a scheduled way using a Windows service that runs in the background. With an XML configuration file, a detailed configuration can be set. But now, it is...
In this article, I show how you can put your PC (server) on a scheduled way into another state. I expanded the program with some new options. You can now:
- Logoff the user
- Logoff the user with force
- Reboot the PC
- Shutdown the PC
- Hibernate the PC
- Put the PC into sleep mode
And all of this you can do in a scheduled way. You can say on which day and which time you want to change the state of the PC.
Background
I personally use this program to shutdown my server every night to save some on the power costs. I use the service to shutdown the server at a specific moment and use the BIOS auto start-up time to start everything back up in the morning.
Using the Code
The service is very straight forward, nothing fancy, just a simple Windows service with an infinite loop that checks every second if a specific moment in time is hit with a specific set of configuration. The configuration is all done in an XML file that is automatically loaded when the Windows Service is started or when the file gets changed by the user with, for example, Notepad. To let the service do its work, I have created an AutoShutdownWorker
class that is shown below. The class does the following jobs:
- Check if a file called AutoShutdownSchedules.xml exists in the location where the service is installed.
- If the file does not exist, create an example file and write some information to the Windows event log and shutdown the Windows service.
- If the file exists load it, if an error happens (e.g. bad XML file), write some error information to the Windows event log and shutdown the Windows service.
- Startup a
filesystemwatcher
to check if the configuration file gets changed, when the file is changed by the user, then reload this file.
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
namespace AutoShutdownService
{
internal class AutoShutdownWorker
{
#region Fields
private volatile bool _runWorker;
private AutoShutdownSchedules _autoShutdownSchedules;
private readonly FileSystemWatcher _fileSystemWatcher = new FileSystemWatcher();
private readonly string _scheduleFileName =
new FileInfo(Assembly.GetExecutingAssembly().Location).Directory +
"\\AutoShutdownSchedules.xml";
#endregion
#region Start
public void Start()
{
if (!File.Exists(_scheduleFileName))
{
try
{
File.WriteAllText(_scheduleFileName, AutoShutdownSchedules.CreateExampleString());
EventLogging.WriteWarning("Could not find the file '" + _scheduleFileName +
"' an example file has been created.
Please fill this file with your required reboot schedules");
return;
}
catch (Exception e)
{
EventLogging.WriteError(
"Tried to create an example 'AutoShutdownSchedules.xml' file in the folder '" +
Path.GetDirectoryName(_scheduleFileName) +
" but this failed, error: " + e.Message);
}
}
ReadAutoShutdownSchedulesFile();
_fileSystemWatcher.Path = Path.GetDirectoryName(_scheduleFileName);
_fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileSystemWatcher.Changed += (source, e) => ReadAutoShutdownSchedulesFile();
_fileSystemWatcher.Created += (source, e) => ReadAutoShutdownSchedulesFile();
_fileSystemWatcher.EnableRaisingEvents = true;
_runWorker = true;
Worker();
}
#endregion
#region Stop
public void Stop()
{
_runWorker = false;
}
#endregion
#region ReadAutoShutdownSchedulesFile
private void ReadAutoShutdownSchedulesFile()
{
try
{
var xml = File.ReadAllText(_scheduleFileName);
_autoShutdownSchedules = AutoShutdownSchedules.LoadFromString(xml);
}
catch (Exception e)
{
EventLogging.WriteError("Tried to read the file '" +
_scheduleFileName + "' but an error happened, error: " + e.Message);
Stop();
}
}
#endregion
#region Worker
private void Worker()
{
while (_runWorker)
{
var weekDay = (int) DateTime.Now.DayOfWeek;
var schedule = _autoShutdownSchedules.Schedules.Find(m => (int) m.Day == weekDay);
if (schedule != null)
{
if (DateTime.Now.ToString("HH:mm") == schedule.Time.ToString("HH:mm"))
{
var canClose = true;
var processes = Process.GetProcesses();
foreach (var process in processes)
{
var process1 = process;
var result = _autoShutdownSchedules.Programs.Find
(m => m.ToLower() == process1.ProcessName.ToLower());
if (result == null) continue;
canClose = false;
break;
}
if (canClose)
{
Stop();
ComputerState.SetState(schedule.ComputerState);
}
}
}
if(_runWorker)
Thread.Sleep(1000);
}
}
#endregion
}
}
The AutoShutdownSchedules
class is used to load the configuration from the XML file (or write an example file). The XML gets serialized to an object so that we can use it very simply in code.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;
namespace AutoShutdownService
{
#region enum ScheduleWeekDays
public enum ScheduleWeekDays
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saterday,
}
#endregion
[Serializable]
public class AutoShutdownSchedules
{
#region Fields
[XmlArray("Programs")]
[XmlArrayItem("Program")]
public List<string> Programs = new List<string>();
[XmlArray("Schedules")]
[XmlArrayItem("Schedule")]
public List<schedule> Schedules = new List<schedule>();
#endregion
#region LoadFromString
public static AutoShutdownSchedules LoadFromString(string xml)
{
try
{
var xmlSerializer = new XmlSerializer(typeof(AutoShutdownSchedules));
var rdr = new StringReader(xml);
return (AutoShutdownSchedules) xmlSerializer.Deserialize(rdr);
}
catch (Exception e)
{
throw new Exception("The XML string contains invalid XML, error: " + e.Message);
}
}
#endregion
#region CreateExampleString
public static string CreateExampleString()
{
var autoShowdownSchedules = new AutoShutdownSchedules();
autoShowdownSchedules.Programs.Add("outlook");
autoShowdownSchedules.Programs.Add("thunderbird");
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.Logoff,
Day = ScheduleWeekDays.Monday,
Time = DateTime.Parse("11:00")
});
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.LogoffForced,
Day = ScheduleWeekDays.Tuesday,
Time = DateTime.Parse("11:00")
});
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.Reboot,
Day = ScheduleWeekDays.Wednesday,
Time = DateTime.Parse("11:00")
});
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.Shutdown,
Day = ScheduleWeekDays.Thursday,
Time = DateTime.Parse("11:00")
});
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.Hibernate,
Day = ScheduleWeekDays.Friday,
Time = DateTime.Parse("11:00")
});
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.Sleep,
Day = ScheduleWeekDays.Saterday,
Time = DateTime.Parse("11:00")
});
autoShowdownSchedules.Schedules.Add(new Schedule
{
ComputerState = ComputerStateType.Shutdown,
Day = ScheduleWeekDays.Sunday,
Time = DateTime.Parse("11:00")
});
return autoShowdownSchedules.SerializeToString();
}
#endregion
#region SerializeToString
public string SerializeToString()
{
var stringWriter = new StringWriter(new StringBuilder());
var s = new XmlSerializer(GetType());
s.Serialize(stringWriter, this);
var output = stringWriter.ToString();
output = output.Replace("", string.Empty);
output = output.Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", string.Empty);
output = output.Replace
("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", string.Empty);
return output;
}
#endregion
}
[Serializable]
public class Schedule
{
#region Properties
public ComputerStateType ComputerState { get; set; }
[XmlIgnore]
public string ComputerStateAsString { get
{ return Enum.GetName(typeof(ComputerStateType), ComputerState); } }
public ScheduleWeekDays Day { get; set; }
[XmlIgnore]
public string DayAsString { get { return Enum.GetName(typeof(ScheduleWeekDays), Day); } }
public DateTime Time { get; set; }
#endregion
#region Constructor
public Schedule()
{
ComputerState = ComputerStateType.Shutdown;
}
#endregion
}
}
In this new version, a new class called ComputerState
has been made. Through this class, you can set the state of the PC.
#if (!DEBUG)
using System.Runtime.InteropServices;
#endif
using System.Windows.Forms;
namespace AutoShutdownService
{
#region enum ComputerStateType
public enum ComputerStateType
{
Logoff,
LogoffForced,
Reboot,
Shutdown,
Hibernate,
Sleep
}
#endregion
internal static class ComputerState
{
#region DllImports
#if (!DEBUG)
[DllImport("user32.dll")]
private static extern int ExitWindowsEx(int uFlags, int dwReason);
#endif
#endregion
#region SetState
public static void SetState(ComputerStateType computerState)
{
#if (DEBUG)
switch (computerState)
{
case ComputerStateType.Logoff:
MessageBox.Show("PC would have logged of when we were not in DEBUG mode");
break;
case ComputerStateType.LogoffForced:
MessageBox.Show
("PC would have logged of with force when we were not in DEBUG mode");
break;
case ComputerStateType.Reboot:
MessageBox.Show("PC would have been rebooted when we were not in DEBUG mode");
break;
case ComputerStateType.Shutdown:
MessageBox.Show("PC would have shutdown when we were not in DEBUG mode");
break;
case ComputerStateType.Hibernate:
MessageBox.Show("PC would have hibernated of when we were not in DEBUG mode");
break;
case ComputerStateType.Sleep:
MessageBox.Show("PC would have gone to sleep when we were not in DEBUG mode");
break;
}
#else
switch (computerState)
{
case ComputerStateType.Logoff:
ExitWindowsEx(0, 0);
break;
case ComputerStateType.LogoffForced:
ExitWindowsEx(4, 0);
break;
case ComputerStateType.Reboot:
ExitWindowsEx(2, 0);
break;
case ComputerStateType.Shutdown:
ExitWindowsEx(1, 0);
break;
case ComputerStateType.Hibernate:
Application.SetSuspendState(PowerState.Hibernate, true, true);
break;
case ComputerStateType.Sleep:
Application.SetSuspendState(PowerState.Suspend, true, true);
break;
}
#endif
}
#endregion
}
}
The Configuration XML
In the previous version, you had to set the configuration XML yourself with Notepad. To make everything easier, a form is added to the AutoShutdown Windows Service. When you start the AutoShutdownService.exe file, a form will be presented to you where you can do the needed configuration.
After pressing the save button, this configuration will be written to an XML file which change will be detected by the AutoShutdown Windows Service. The configuration file looks something like this:
<autoshutdownschedules>
<programs>
<program>outlook</program>
<program>thunderbird</program>
</programs>
<schedules>
<schedule>
<computerstate>Shutdown</computerstate>
<day>Thursday</day>
<time>2014-01-09T21:55:00</time>
</schedule>
<schedule>
<computerstate>Hibernate</computerstate>
<day>Tuesday</day>
<time>2014-01-09T11:00:00</time>
</schedule>
<schedule>
<computerstate>Reboot</computerstate>
<day>Wednesday</day>
<time>2014-01-09T21:43:00</time>
</schedule>
</schedules>
</autoshutdownschedules>
Because we use a DateTimeField
, a date is also added in the XML file, but this date is ignored by the program.
How to Install the Windows Service
To make the AutoshutdownService
do its work, you need to install it. First, make sure that you compiled the code in RELEASE
mode. After that, copy the AutoShutdownService.exe in a folder somewhere on your PC. For example, C:\Program Files\AutoshutdownService.
Start a command box (run it as administrator) and type the following command:
InstallUtil autoshutdownservice.exe
If the installutil file can't be found, you can copy it from here --> c:\Windows\Microsoft.NET\Framework\v4.0.30319\.
If everything is installed, there is only one thing left. You have to start the service. You can do this with the following command:
net start autoshutdownservice
History
- 2014-01-07: First version
- 2014-01-09: Version 1.1 - Added more state options and a configuration form