Click here to Skip to main content
15,867,835 members
Articles / Programming Languages / XML
Alternative
Article

Autoshutdown Service in C#

Rate me:
Please Sign up or sign in to vote.
5.00/5 (18 votes)
10 Jan 2014CPOL3 min read 45.2K   2.2K   47   8
This is an alternative for "AutoShut, my first program in C#"

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.
C#
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Threading;

namespace AutoShutdownService
{
    internal class AutoShutdownWorker
    {
        #region Fields
        /// <summary>
        /// A bool to keep our worker function running
        /// </summary>
        private volatile bool _runWorker;

        /// <summary>
        /// A list of shutdown schedules
        /// </summary>
        private AutoShutdownSchedules _autoShutdownSchedules;

        /// <summary>
        /// Used to watch if the AutoShutdownSchedules XML file has been changed
        /// </summary>
        private readonly FileSystemWatcher _fileSystemWatcher = new FileSystemWatcher();

        /// <summary>
        /// The name of the file that contains the schedule information
        /// </summary>
        private readonly string _scheduleFileName = 
                   new FileInfo(Assembly.GetExecutingAssembly().Location).Directory +
                                                    "\\AutoShutdownSchedules.xml";
        #endregion

        #region Start
        /// <summary>
        /// Start the worker function
        /// </summary>
        public void Start()
        {
            // When there is no AutoShutdownSchedules.xml we create an example file
            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");

                    // Stop the service
                    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);
                }
            }

            // Otherwhise read the schedule file
            ReadAutoShutdownSchedulesFile();

            // Set the FileSystemWatcher so that we are notified 
            // when the AutoShutdownSchedules file changes
            _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
        /// <summary>
        /// Stop the worker function
        /// </summary>
        public void Stop()
        {
            _runWorker = false;
        }
        #endregion

        #region ReadAutoShutdownSchedulesFile
        /// <summary>
        /// Read the autoshutdown schedules file if it exists
        /// </summary>
        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
        /// <summary>
        /// The shutdown worker function
        /// </summary>
        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;

                        // Check all the running processes to see if we can safely shutdown
                        var processes = Process.GetProcesses();

                        // Prevent ReSharper from annoying me that the foreach loop 
                        // can be converted to a LINQ expression.
                        // Yes ReSharper it can... but the code gets a lott more unreadable :-) 
                        // so stop bugging me
                        // ReSharper disable once LoopCanBeConvertedToQuery
                        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;
                        }

                        // When we are on the correct day and time and there is not program 
                        // running that keeps us from
                        // shutting down then start the shutdown process.
                        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.

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;

namespace AutoShutdownService
{
    #region enum ScheduleWeekDays
    /// <summary>
    /// The weekdays
    /// </summary>
    public enum ScheduleWeekDays
    {
        Sunday,
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saterday,
    }
    #endregion

    [Serializable]
    public class AutoShutdownSchedules
    {
        #region Fields
        /// <summary>
        /// Programs that are not allowed to be active when we want to do a shutdown or reboot
        /// </summary>
        [XmlArray("Programs")]
        [XmlArrayItem("Program")]
        public List<string> Programs = new List<string>();

        /// <summary>
        /// The day and time that we want to shutdown
        /// </summary>
        [XmlArray("Schedules")]
        [XmlArrayItem("Schedule")]
        public List<schedule> Schedules = new List<schedule>();
        #endregion

        #region LoadFromString
        /// <summary>
        /// Create this object from an XML string
        /// </summary>
        /// <param name="xml" />The XML string
        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
        /// <summary>
        /// Creates and example xml file as string
        /// </summary>
        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
        /// <summary>
        /// Serialize this object to a string
        /// </summary>
        /// <returns>The object as an XML string</returns>
        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
        /// <summary>
        /// Set's the PC in a specific state, default is shutdown
        /// </summary>
        public ComputerStateType ComputerState { get; set; }

        /// <summary>
        /// Used to bind the ComputerStateType enum to a DataGridView
        /// </summary>
        [XmlIgnore]
        public string ComputerStateAsString { get 
                 { return Enum.GetName(typeof(ComputerStateType), ComputerState); } }
        
        /// <summary>
        /// The day of the week on which we want to do a shutdown or reboot
        /// </summary>
        public ScheduleWeekDays Day { get; set; }

        /// <summary>
        /// Used to bind the Day enum to a DataGridView
        /// </summary>
        [XmlIgnore]
        public string DayAsString { get { return Enum.GetName(typeof(ScheduleWeekDays), Day); } }

        /// <summary>
        /// The time on the day of the week on which we want to change the PC state
        /// </summary>
        public DateTime Time { get; set; }
        #endregion

        #region Constructor
        public Schedule()
        {
            // Default value for the computerstate type when nothing is set in the XML
            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.

C#
#if (!DEBUG)
using System.Runtime.InteropServices;
#endif
using System.Windows.Forms;

namespace AutoShutdownService
{
    #region enum ComputerStateType
    public enum ComputerStateType
    {
        /// <summary>
        /// Logoff the user
        /// </summary>
        Logoff,

        /// <summary>
        /// Logoff the user and terminate any application that prevents it
        /// </summary>
        LogoffForced,

        /// <summary>
        /// Reboot the PC
        /// </summary>
        Reboot,

        /// <summary>
        /// Shutdown the PC
        /// </summary>
        Shutdown,

        /// <summary>
        /// Hibernate the PC
        /// </summary>
        Hibernate,

        /// <summary>
        /// Put the PC in sleep mode
        /// </summary>
        Sleep
    }
    #endregion

    /// <summary>
    /// With this class the state of the PC can be controlled.
    /// </summary>
    internal static class ComputerState
    {
        #region DllImports
#if (!DEBUG)
        [DllImport("user32.dll")]
        private static extern int ExitWindowsEx(int uFlags, int dwReason);
#endif
        #endregion

        #region SetState
        /// <summary>
        /// Puts the PC in a specific state
        /// </summary>
        /// <param name="computerState" />
        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.

Image 1

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:

XML
<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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
Programming since I was a kid. Started on the Commodore 64 with BASIC. Since then I used programming languages like Turbo Pascal, Delphi, C++ and Visual Basic 6.

Now a days I do a lot of programming in C# with underlying databases like MS SQL

Comments and Discussions

 
QuestionDoesn't seem to do anything... Pin
Member 1062988118-Apr-20 8:40
Member 1062988118-Apr-20 8:40 
I tried using the precompile exe and whilst it installed as an NT service OK and runs, it has no effect on my W10x64 dev box..

Any suggestions as to how to start debugging this pls?

Thanks
QuestionJust soso. Pin
Highflyer17-May-16 17:09
Highflyer17-May-16 17:09 
Questionservice not fuctioning as suppose. How to debug? Pin
xRayen9-Nov-15 6:48
xRayen9-Nov-15 6:48 
GeneralMy vote of 3 Pin
Renato Moura24-Mar-14 16:18
professionalRenato Moura24-Mar-14 16:18 
QuestionVote of 5 Pin
Ganesan Senthilvel9-Jan-14 14:19
Ganesan Senthilvel9-Jan-14 14:19 
QuestionVery nice article ! Pin
Volynsky Alex8-Jan-14 3:42
professionalVolynsky Alex8-Jan-14 3:42 
AnswerRe: Very nice article ! Pin
Kees van Spelde8-Jan-14 6:43
professionalKees van Spelde8-Jan-14 6:43 
GeneralRe: Very nice article ! Pin
Volynsky Alex8-Jan-14 7:20
professionalVolynsky Alex8-Jan-14 7:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.