Skip to content

Instantly share code, notes, and snippets.

@meitinger
Last active July 3, 2018 17:17
Show Gist options
  • Save meitinger/6378859 to your computer and use it in GitHub Desktop.
Save meitinger/6378859 to your computer and use it in GitHub Desktop.
Replacement of System.ServiceProcess.ServiceBase with quite a bunch of improvements and better (proper) service fault handling.
/* Copyright (C) 2012-2014, Manuel Meitinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
namespace Aufbauwerk.ServiceProcess
{
#region NetworkBindingChanged classes
/// <summary>
/// Specifies the reason for a network binding change notice.
/// </summary>
public enum NetworkBindingChangedReason
{
/// <summary>
/// Notifies a network service that there is a new component for binding.
/// The service should bind to the new component.
/// </summary>
Add,
/// <summary>
/// Notifies a network service that a component for binding has been removed.
/// The service should reread its binding information and unbind from the removed component.
/// </summary>
Remove,
/// <summary>
/// Notifies a network service that a disabled binding has been enabled.
/// The service should reread its binding information and add the new binding.
/// </summary>
Enable,
/// <summary>
/// Notifies a network service that one of its bindings has been disabled.
/// The service should reread its binding information and remove the binding.
/// </summary>
Disable,
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.NetworkBindingChanged"/> event.
/// </summary>
public sealed class NetworkBindingChangedEventArgs : EventArgs
{
public NetworkBindingChangedEventArgs(NetworkBindingChangedReason reason)
{
ServiceApplication.CheckEnum("reason", reason);
Reason = reason;
}
/// <summary>
/// Gets the reason for a network binding change.
/// </summary>
public NetworkBindingChangedReason Reason { get; private set; }
}
#endregion
#region DeviceEvent classes
/// <summary>
/// Specifies the type of <see cref="ServiceApplication.DeviceEvent"/> event.
/// </summary>
public enum DeviceEventType
{
/// <summary>
/// A device or piece of media has been inserted and becomes available.
/// </summary>
Arrival = 0x8000,
/// <summary>
/// A device or piece of media has been physically removed.
/// </summary>
RemoveComplete = 0x8004,
/// <summary>
/// Request permission to remove a device or piece of media.
/// </summary>
QueryRemove = 0x8001,
/// <summary>
/// Request to remove a device or piece of media has been canceled.
/// </summary>
QueryRemoveFailed = 0x8002,
/// <summary>
/// A device or piece of media is being removed and is no longer available for use.
/// </summary>
RemovePending = 0x8003,
/// <summary>
/// A driver-defined custom event has occurred.
/// </summary>
CustomEvent = 0x8006,
}
/// <summary>
/// Specifies the type of device for <see cref="ServiceApplication.DeviceEvent"/> events.
/// </summary>
public enum DeviceEventDeviceType
{
/// <summary>
/// Unknown or not implemented device type.
/// </summary>
Unknown = -1,
/// <summary>
/// Class of devices.
/// </summary>
Interface = 0x00000005,
/// <summary>
/// File system handle.
/// </summary>
Handle = 0x00000006,
/// <summary>
/// OEM- or IHV-defined device type.
/// </summary>
Oem = 0x00000000,
/// <summary>
/// Port device (serial or parallel).
/// </summary>
Port = 0x00000003,
/// <summary>
/// Logical volume.
/// </summary>
Volume = 0x00000002,
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.DeviceEvent"/> event.
/// </summary>
public abstract class DeviceEventArgs : CancelEventArgs
{
#region Win32
private const ushort DBTF_MEDIA = 0x0001;
private const ushort DBTF_NET = 0x0002;
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
private static extern void CopyMemory([In] IntPtr destination, [In] IntPtr source, [In] int length);
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
private sealed class OpenArrayAttribute : Attribute { }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_HDR
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
public Guid dbcc_classguid;
[OpenArray, MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public char[] dbcc_name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_HANDLE
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
public IntPtr dbch_handle;
public IntPtr dbch_hdevnotify;
public Guid dbch_eventguid;
public int dbch_nameoffset;
[OpenArray, MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public byte[] dbch_data;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_OEM
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
public uint dbco_identifier;
public uint dbco_suppfunc;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_VOLUME
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
public uint dbcv_unitmask;
public ushort dbcv_flags;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_PORT
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
[OpenArray, MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public char[] dbcp_name;
}
#endregion
private static DeviceEventArgs CreateInternal<T>(IntPtr buffer, int bufferSize, Converter<T, DeviceEventArgs> convert) where T : struct
{
// ensure that the buffer is large enough
var typeSize = Marshal.SizeOf(typeof(T));
var sizeDifference = bufferSize - typeSize;
if (sizeDifference < 0)
throw new ArgumentOutOfRangeException("bufferSize");
// read the buffer
var structure = (T)Marshal.PtrToStructure(buffer, typeof(T));
if (sizeDifference > 0)
{
// the buffer is larger, read all elements of an open array if there is one (otherwise the structure might be newer)
var openArrayField = (from field in typeof(T).GetFields() where field.GetCustomAttributes(typeof(OpenArrayAttribute), true).Length > 0 select field).SingleOrDefault();
if (openArrayField != null)
{
// ensure that the additional size is a multiple of the array type
var elementType = openArrayField.FieldType.GetElementType();
var elementSize = elementType == typeof(char) ? 2 : Marshal.SizeOf(elementType);
if (sizeDifference % elementSize != 0)
throw new ArgumentOutOfRangeException("bufferSize");
// create and copy all array elements (from the offset of the field not the end, since there might be padding)
var array = Array.CreateInstance(elementType, sizeDifference / elementSize + 1);
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try { CopyMemory(Marshal.UnsafeAddrOfPinnedArrayElement(array, 0), new IntPtr(buffer.ToInt64() + Marshal.OffsetOf(typeof(T), openArrayField.Name).ToInt32()), elementSize + sizeDifference); }
finally { handle.Free(); }
object boxed = structure;
openArrayField.SetValue(boxed, array);
structure = (T)boxed;
}
}
// convert the structure and return the result
return convert(structure);
}
internal static DeviceEventArgs FromNative(int eventType, IntPtr eventData)
{
// check the input data
if (!Enum.IsDefined(typeof(DeviceEventType), eventType))
return null;
var type = (DeviceEventType)eventType;
if (eventData == IntPtr.Zero)
return new UnknownDeviceEventArgs(type);
var header = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(eventData, typeof(DEV_BROADCAST_HDR));
// handle each device type
switch ((DeviceEventDeviceType)header.dbcc_devicetype)
{
case DeviceEventDeviceType.Interface:
return CreateInternal<DEV_BROADCAST_DEVICEINTERFACE>
(
eventData, header.dbcc_size,
device => new InterfaceDeviceEventArgs(type, device.dbcc_classguid, new string(device.dbcc_name, 0, device.dbcc_name.Length - 1))
);
case DeviceEventDeviceType.Handle:
return CreateInternal<DEV_BROADCAST_HANDLE>
(
eventData, header.dbcc_size,
handle => new HandleDeviceEventArgs
(
type,
handle.dbch_handle,
type == DeviceEventType.CustomEvent ? handle.dbch_eventguid : Guid.Empty,
type == DeviceEventType.CustomEvent && handle.dbch_nameoffset > 0 ? Marshal.PtrToStringUni(new IntPtr(eventData.ToInt64() + handle.dbch_nameoffset)) : null,
type == DeviceEventType.CustomEvent ? handle.dbch_data : null
)
);
case DeviceEventDeviceType.Oem:
return CreateInternal<DEV_BROADCAST_OEM>
(
eventData, header.dbcc_size,
oem => new OemDeviceEventArgs(type, oem.dbco_identifier, oem.dbco_suppfunc)
);
case DeviceEventDeviceType.Port:
return CreateInternal<DEV_BROADCAST_PORT>
(
eventData, header.dbcc_size,
port => new PortDeviceEventArgs(type, new string(port.dbcp_name, 0, port.dbcp_name.Length - 1))
);
case DeviceEventDeviceType.Volume:
return CreateInternal<DEV_BROADCAST_VOLUME>
(
eventData, header.dbcc_size,
volume => new VolumeDeviceEventArgs
(
type,
volume.dbcv_unitmask,
(volume.dbcv_flags & DBTF_MEDIA) != 0,
(volume.dbcv_flags & DBTF_NET) != 0
)
);
default:
return new UnknownDeviceEventArgs(type);
}
}
#if DEBUG
internal bool ToNative(out int eventType, out IntPtr eventData)
{
// store the event type and create the the corresponding native structure
eventType = (int)Type;
int size;
object data;
switch (DeviceType)
{
case DeviceEventDeviceType.Interface:
data = new DEV_BROADCAST_DEVICEINTERFACE()
{
dbcc_size = size = Marshal.SizeOf(typeof(DEV_BROADCAST_DEVICEINTERFACE)) + ((InterfaceDeviceEventArgs)this).Name.Length * 2,
dbcc_devicetype = (int)DeviceType,
dbcc_reserved = 0,
dbcc_classguid = ((InterfaceDeviceEventArgs)this).ClassGuid,
dbcc_name = (((InterfaceDeviceEventArgs)this).Name + '\0').ToCharArray(),
};
break;
case DeviceEventDeviceType.Handle:
data = new DEV_BROADCAST_HANDLE()
{
dbcc_size = size = Marshal.SizeOf(typeof(DEV_BROADCAST_HANDLE)) +
(((HandleDeviceEventArgs)this).Data == null || ((HandleDeviceEventArgs)this).Data.Length == 0 ? 0 : (((HandleDeviceEventArgs)this).Data.Length - 1)) +
(((HandleDeviceEventArgs)this).Name == null ? 0 : (((HandleDeviceEventArgs)this).Name.Length + 1) * 2),
dbcc_devicetype = (int)DeviceType,
dbcc_reserved = 0,
dbch_handle = ((HandleDeviceEventArgs)this).Handle,
dbch_hdevnotify = IntPtr.Zero,
dbch_eventguid = ((HandleDeviceEventArgs)this).EventGuid,
dbch_nameoffset = ((HandleDeviceEventArgs)this).Name == null ? 0 : (Marshal.OffsetOf(typeof(DEV_BROADCAST_HANDLE), "dbch_data").ToInt32() + (((HandleDeviceEventArgs)this).Data == null || ((HandleDeviceEventArgs)this).Data.Length == 0 ? 1 : ((HandleDeviceEventArgs)this).Data.Length)),
dbch_data = (((HandleDeviceEventArgs)this).Data == null || ((HandleDeviceEventArgs)this).Data.Length == 0 ? new byte[1] { 0x00 } : ((HandleDeviceEventArgs)this).Data).Concat(((HandleDeviceEventArgs)this).Name == null ? new byte[0] : System.Text.Encoding.Unicode.GetBytes(((HandleDeviceEventArgs)this).Name + '\0')).ToArray(),
};
break;
case DeviceEventDeviceType.Oem:
data = new DEV_BROADCAST_OEM()
{
dbcc_size = size = Marshal.SizeOf(typeof(DEV_BROADCAST_OEM)),
dbcc_devicetype = (int)DeviceType,
dbcc_reserved = 0,
dbco_identifier = ((OemDeviceEventArgs)this).Identifier,
dbco_suppfunc = ((OemDeviceEventArgs)this).SuppliedFunction,
};
break;
case DeviceEventDeviceType.Port:
data = new DEV_BROADCAST_PORT()
{
dbcc_size = size = Marshal.SizeOf(typeof(DEV_BROADCAST_PORT)) + ((PortDeviceEventArgs)this).Name.Length * 2,
dbcc_devicetype = (int)DeviceType,
dbcc_reserved = 0,
dbcp_name = (((PortDeviceEventArgs)this).Name + '\0').ToCharArray(),
};
break;
case DeviceEventDeviceType.Volume:
data = new DEV_BROADCAST_VOLUME()
{
dbcc_size = size = Marshal.SizeOf(typeof(DEV_BROADCAST_VOLUME)),
dbcc_devicetype = (int)DeviceType,
dbcc_reserved = 0,
dbcv_unitmask = ((VolumeDeviceEventArgs)this).UnitMask,
dbcv_flags = (ushort)((((VolumeDeviceEventArgs)this).IsMediaEvent ? DBTF_MEDIA : 0) | (((VolumeDeviceEventArgs)this).IsNetworkVolume ? DBTF_NET : 0)),
};
break;
default:
eventData = IntPtr.Zero;
return false;
}
// allocate the memory and fill it
eventData = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(data, eventData, false);
// if there isn't any additional data, we're done
var type = data.GetType();
var actualSize = Marshal.SizeOf(type);
if (actualSize == size)
return true;
// store the additional data
var openArrayField = (from field in type.GetFields() where field.GetCustomAttributes(typeof(OpenArrayAttribute), true).Length > 0 select field).Single();
var array = (Array)openArrayField.GetValue(data);
var elementType = openArrayField.FieldType.GetElementType();
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try { CopyMemory(new IntPtr(eventData.ToInt64() + Marshal.OffsetOf(type, openArrayField.Name).ToInt32()), Marshal.UnsafeAddrOfPinnedArrayElement(array, 0), (elementType == typeof(char) ? 2 : Marshal.SizeOf(elementType)) * array.Length); }
finally { handle.Free(); }
return true;
}
#endif
internal DeviceEventArgs(DeviceEventType type, DeviceEventDeviceType deviceType)
{
ServiceApplication.CheckEnum("type", type);
ServiceApplication.CheckEnum("deviceType", deviceType);
Type = type;
DeviceType = deviceType;
}
/// <summary>
/// Gets the type of event.
/// </summary>
public DeviceEventType Type { get; private set; }
/// <summary>
/// Gets the type of device.
/// </summary>
public DeviceEventDeviceType DeviceType { get; private set; }
public override bool CanBeCanceled
{
get { return Type == DeviceEventType.QueryRemove; }
}
}
/// <summary>
/// Contains only the type of the event and will be returned if the device information couldn't be gathered.
/// </summary>
public sealed class UnknownDeviceEventArgs : DeviceEventArgs
{
public UnknownDeviceEventArgs(DeviceEventType type) : base(type, DeviceEventDeviceType.Unknown) { }
}
/// <summary>
/// Contains information about a class of devices.
/// </summary>
public sealed class InterfaceDeviceEventArgs : DeviceEventArgs
{
public InterfaceDeviceEventArgs(DeviceEventType type, Guid classGuid, string name)
: base(type, DeviceEventDeviceType.Interface)
{
if (name == null)
throw new ArgumentNullException("name");
ClassGuid = classGuid;
Name = name;
}
/// <summary>
/// The GUID for the interface device class.
/// </summary>
public Guid ClassGuid { get; private set; }
/// <summary>
/// A string that specifies the name of the device.
/// </summary>
public string Name { get; private set; }
}
/// <summary>
/// Contains information about a file system handle.
/// </summary>
public sealed class HandleDeviceEventArgs : DeviceEventArgs
{
public HandleDeviceEventArgs(DeviceEventType type, IntPtr handle, Guid eventGuid, string name, byte[] data)
: base(type, DeviceEventDeviceType.Handle)
{
if (handle == IntPtr.Zero)
throw new ArgumentNullException("handle");
Handle = handle;
EventGuid = eventGuid;
Name = name;
Data = data;
}
/// <summary>
/// A handle to the device to be checked.
/// </summary>
public IntPtr Handle { get; private set; }
/// <summary>
/// The GUID for the custom event.
/// </summary>
public Guid EventGuid { get; private set; }
/// <summary>
/// Optional string buffer.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Optional binary data.
/// </summary>
public byte[] Data { get; private set; }
}
/// <summary>
/// Contains information about an OEM-defined device type.
/// </summary>
public sealed class OemDeviceEventArgs : DeviceEventArgs
{
public OemDeviceEventArgs(DeviceEventType type, uint identifier, uint suppliedFunction)
: base(type, DeviceEventDeviceType.Oem)
{
Identifier = identifier;
SuppliedFunction = suppliedFunction;
}
/// <summary>
/// The OEM-specific identifier for the device.
/// </summary>
public uint Identifier { get; private set; }
/// <summary>
/// The OEM-specific function value. Possible values depend on the device.
/// </summary>
public uint SuppliedFunction { get; private set; }
}
/// <summary>
/// Contains information about a modem, serial, or parallel port.
/// </summary>
public sealed class PortDeviceEventArgs : DeviceEventArgs
{
public PortDeviceEventArgs(DeviceEventType type, string name)
: base(type, DeviceEventDeviceType.Port)
{
if (name == null)
throw new ArgumentNullException("name");
Name = name;
}
/// <summary>
/// A string specifying the friendly name of the port or the device connected to the port.
/// </summary>
public string Name { get; private set; }
}
/// <summary>
/// Contains information about a logical volume.
/// </summary>
public sealed class VolumeDeviceEventArgs : DeviceEventArgs
{
public VolumeDeviceEventArgs(DeviceEventType type, uint unitMask, bool isMediaEvent, bool isNetworkEvent)
: base(type, DeviceEventDeviceType.Volume)
{
UnitMask = unitMask;
IsMediaEvent = isMediaEvent;
IsNetworkVolume = isNetworkEvent;
}
/// <summary>
/// The logical unit mask identifying one or more logical units.
/// Each bit in the mask corresponds to one logical drive.
/// Bit 0 represents drive A, bit 1 represents drive B, and so on.
/// </summary>
public uint UnitMask { get; private set; }
/// <summary>
/// Change affects media in drive. If not set, change affects physical device or drive.
/// </summary>
public bool IsMediaEvent { get; private set; }
/// <summary>
/// Indicated logical volume is a network volume.
/// </summary>
public bool IsNetworkVolume { get; private set; }
}
#endregion
#region HardwareProfileEvent classes
/// <summary>
/// Specifies the type of <see cref="ServiceApplication.HardwareProfileEvent"/> event.
/// </summary>
public enum HardwareProfileEventType
{
/// <summary>
/// The current configuration has changed, due to a dock or undock.
/// </summary>
ConfigChanged = 0x0018,
/// <summary>
/// Request permission to change the current configuration (dock or undock).
/// </summary>
QueryChangeConfig = 0x0017,
/// <summary>
/// Request to change the current configuration (dock or undock) has been canceled.
/// </summary>
ConfigChangeCanceled = 0x0019,
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.HardwareProfileEvent"/> event.
/// </summary>
public sealed class HardwareProfileEventArgs : CancelEventArgs
{
internal static HardwareProfileEventArgs FromNative(int eventType, IntPtr eventData)
{
// check the input parameter and return the the args
if (!Enum.IsDefined(typeof(HardwareProfileEventType), eventType))
return null;
return new HardwareProfileEventArgs((HardwareProfileEventType)eventType);
}
#if DEBUG
internal bool ToNative(out int eventType, out IntPtr eventData)
{
// store the type
eventType = (int)Type;
eventData = IntPtr.Zero;
return false;
}
#endif
public HardwareProfileEventArgs(HardwareProfileEventType type)
{
ServiceApplication.CheckEnum("type", type);
Type = type;
}
/// <summary>
/// Gets the type of hardware profile event.
/// </summary>
public HardwareProfileEventType Type { get; private set; }
public override bool CanBeCanceled
{
get { return Type == HardwareProfileEventType.QueryChangeConfig; }
}
}
#endregion
#region PowerEvent classes
/// <summary>
/// Specifies the type of <see cref="ServiceApplication.PowerEvent"/> event.
/// </summary>
public enum PowerEventType
{
/// <summary>
/// Request for permission to suspend.
/// </summary>
QuerySuspend = 0x0,
/// <summary>
/// Suspension request denied.
/// </summary>
QuerySuspendFailed = 0x2,
/// <summary>
/// System is suspending operation.
/// </summary>
Suspend = 0x4,
/// <summary>
/// Operation resuming after critical suspension.
/// </summary>
ResumeCritical = 0x6,
/// <summary>
/// Operation is resuming from a low-power state.
/// </summary>
ResumeSuspend = 0x7,
/// <summary>
/// Battery power is low.
/// </summary>
BatteryLow = 0x9,
/// <summary>
/// Power status has changed.
/// </summary>
PowerStatusChange = 0x10,
/// <summary>
/// OEM-defined event occurred.
/// </summary>
OemEvent = 0xB,
/// <summary>
/// Operation is resuming automatically from a low-power state.
/// This message is sent every time the system resumes.
/// </summary>
ResumeAutomatic = 0x12,
/// <summary>
/// A power setting change event has been received.
/// </summary>
PowerSettingChange = 0x8013,
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.PowerEvent"/> event.
/// </summary>
public abstract class PowerEventArgs : CancelEventArgs
{
internal static PowerEventArgs FromNative(int eventType, IntPtr eventData)
{
// check if the type is known
if (!Enum.IsDefined(typeof(PowerEventType), eventType))
return null;
var type = (PowerEventType)eventType;
// return the corresponding event args
switch (type)
{
case PowerEventType.OemEvent:
return new OemPowerEventArgs((ushort)eventData.ToInt32());
case PowerEventType.PowerSettingChange:
var buffer = new byte[Marshal.ReadInt32(eventData, Marshal.SizeOf(typeof(Guid)))];
Marshal.Copy(new IntPtr(eventData.ToInt64() + Marshal.SizeOf(typeof(Guid)) + Marshal.SizeOf(typeof(Int32))), buffer, 0, buffer.Length);
return new SettingChangePowerEventArgs((Guid)Marshal.PtrToStructure(eventData, typeof(Guid)), buffer);
default:
return new SimplePowerEventArgs(type);
}
}
#if DEBUG
internal bool ToNative(out int eventType, out IntPtr eventData)
{
// store the type
eventType = (int)Type;
// store the Code, Data or nothing
switch (Type)
{
case PowerEventType.OemEvent:
eventData = new IntPtr(((OemPowerEventArgs)this).Code);
return false;
case PowerEventType.PowerSettingChange:
eventData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)) + Marshal.SizeOf(typeof(Int32)) + ((SettingChangePowerEventArgs)this).Data.Length);
Marshal.StructureToPtr(((SettingChangePowerEventArgs)this).Setting, eventData, false);
Marshal.WriteInt32(eventData, Marshal.SizeOf(typeof(Guid)), ((SettingChangePowerEventArgs)this).Data.Length);
Marshal.Copy(((SettingChangePowerEventArgs)this).Data, 0, new IntPtr(eventData.ToInt64() + Marshal.SizeOf(typeof(Guid)) + Marshal.SizeOf(typeof(Int32))), ((SettingChangePowerEventArgs)this).Data.Length);
return true;
default:
eventData = IntPtr.Zero;
return false;
}
}
#endif
internal PowerEventArgs(PowerEventType type)
{
ServiceApplication.CheckEnum("type", type);
Type = type;
}
/// <summary>
/// Gets the type of power event that occurred.
/// </summary>
public PowerEventType Type { get; private set; }
public override bool CanBeCanceled
{
get { return Type == PowerEventType.QuerySuspend; }
}
}
/// <summary>
/// Provides data for events that aren't <see cref="PowerEventType.OemEvent"/> or <see cref="PowerEventType.PowerSettingChange"/> typed.
/// </summary>
public sealed class SimplePowerEventArgs : PowerEventArgs
{
public SimplePowerEventArgs(PowerEventType type)
: base(type)
{
if (type == PowerEventType.OemEvent || type == PowerEventType.PowerSettingChange)
throw new InvalidEnumArgumentException("type", (int)type, typeof(PowerEventType));
}
}
/// <summary>
/// Provides data for <see cref="PowerEventType.OemEvent"/> typed events.
/// </summary>
public sealed class OemPowerEventArgs : PowerEventArgs
{
public OemPowerEventArgs(ushort code)
: base(PowerEventType.OemEvent)
{
if (code < 0x0200 || code > 0x02FF)
throw new ArgumentOutOfRangeException("code");
Code = code;
}
/// <summary>
/// Gets the OEM-defined event code that was signaled by the system's APM BIOS.
/// </summary>
public ushort Code { get; private set; }
}
/// <summary>
/// Provides data for <see cref="PowerEventType.PowerSettingChange"/> typed events.
/// </summary>
public sealed class SettingChangePowerEventArgs : PowerEventArgs
{
public SettingChangePowerEventArgs(Guid setting, byte[] data)
: base(PowerEventType.PowerSettingChange)
{
if (setting == null)
throw new ArgumentNullException("setting");
if (data == null)
throw new ArgumentNullException("data");
Setting = setting;
Data = data;
}
/// <summary>
/// Gets the changed power setting.
/// </summary>
public Guid Setting { get; private set; }
/// <summary>
/// Gets the new value of the power setting.
/// </summary>
public byte[] Data { get; private set; }
}
#endregion
#region SessionChanged classes
/// <summary>
/// Specifies the reason for a Terminal Services session change notice.
/// </summary>
public enum SessionChangedReason
{
/// <summary>
/// The session was connected to the console terminal or RemoteFX session.
/// </summary>
ConsoleConnect = 0x1,
/// <summary>
/// The session was disconnected from the console terminal or RemoteFX session.
/// </summary>
ConsoleDisconnect = 0x2,
/// <summary>
/// The session was connected to the remote terminal.
/// </summary>
RemoteConnect = 0x3,
/// <summary>
/// The session was disconnected from the remote terminal.
/// </summary>
RemoteDisconnect = 0x4,
/// <summary>
/// A user has logged on to the session.
/// </summary>
SessionLogon = 0x5,
/// <summary>
/// A user has logged off the session.
/// </summary>
SessionLogoff = 0x6,
/// <summary>
/// The session has been locked.
/// </summary>
SessionLock = 0x7,
/// <summary>
/// The session has been unlocked.
/// </summary>
SessionUnlock = 0x8,
/// <summary>
/// The session has changed its remote controlled status.
/// </summary>
SessionRemoteControl = 0x9,
/// <summary>
/// Reserved for future use.
/// </summary>
SessionCreate = 0xA,
/// <summary>
/// Reserved for future use.
/// </summary>
SessionTerminate = 0xB,
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.SessionChanged"/> event.
/// </summary>
public sealed class SessionChangedEventArgs : EventArgs
{
#region Win32
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WTSSESSION_NOTIFICATION
{
public int cbSize;
public int dwSessionId;
}
#endregion
internal static SessionChangedEventArgs FromNative(int eventType, IntPtr eventData)
{
// check the reason and return event args
if (!Enum.IsDefined(typeof(SessionChangedReason), eventType))
return null;
return new SessionChangedEventArgs((SessionChangedReason)eventType, ((WTSSESSION_NOTIFICATION)Marshal.PtrToStructure(eventData, typeof(WTSSESSION_NOTIFICATION))).dwSessionId);
}
#if DEBUG
internal bool ToNative(out int eventType, out IntPtr eventData)
{
// store the reason and session id
eventType = (int)Reason;
eventData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WTSSESSION_NOTIFICATION)));
Marshal.StructureToPtr
(
new WTSSESSION_NOTIFICATION()
{
cbSize = Marshal.SizeOf(typeof(WTSSESSION_NOTIFICATION)),
dwSessionId = SessionId,
},
eventData,
false
);
return true;
}
#endif
public SessionChangedEventArgs(SessionChangedReason reason, int sessionId)
{
ServiceApplication.CheckEnum("reason", reason);
Reason = reason;
SessionId = sessionId;
}
/// <summary>
/// Gets the reason for a Terminal Services session change.
/// </summary>
public SessionChangedReason Reason { get; private set; }
/// <summary>
/// Gets the identifier of the changed session.
/// </summary>
public int SessionId { get; private set; }
}
#endregion
#region TimeChanged classes
/// <summary>
/// Provides data for the <see cref="ServiceApplication.TimeChanged"/> event.
/// </summary>
public sealed class TimeChangedEventArgs : EventArgs
{
#region Win32
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct SERVICE_TIMECHANGE_INFO
{
public long liNewTime;
public long liOldTime;
}
#endregion
internal static TimeChangedEventArgs FromNative(int eventType, IntPtr eventData)
{
// return the event args
var times = (SERVICE_TIMECHANGE_INFO)Marshal.PtrToStructure(eventData, typeof(SERVICE_TIMECHANGE_INFO));
return new TimeChangedEventArgs(DateTime.FromFileTime(times.liNewTime), DateTime.FromFileTime(times.liOldTime));
}
#if DEBUG
internal bool ToNative(out int eventType, out IntPtr eventData)
{
// ignore the type and store the new and old time
eventType = 0;
eventData = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SERVICE_TIMECHANGE_INFO)));
Marshal.StructureToPtr
(
new SERVICE_TIMECHANGE_INFO()
{
liNewTime = NewTime.ToFileTime(),
liOldTime = OldTime.ToFileTime(),
},
eventData,
false
);
return true;
}
#endif
public TimeChangedEventArgs(DateTime newTime, DateTime oldTime)
{
NewTime = newTime;
OldTime = oldTime;
}
/// <summary>
/// Gets new system time.
/// </summary>
public DateTime NewTime { get; private set; }
/// <summary>
/// Gets the previous system time.
/// </summary>
public DateTime OldTime { get; private set; }
}
#endregion
#region other event classes
/// <summary>
/// Provides data for a cancelable event.
/// </summary>
public abstract class CancelEventArgs : System.ComponentModel.CancelEventArgs
{
/// <summary>
/// Indicates whether this event can be canceled.
/// </summary>
public abstract bool CanBeCanceled { get; }
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.Continue"/>, <see cref="ServiceApplication.Pause"/>, <see cref="ServiceApplication.Preshutdown"/>, <see cref="ServiceApplication.Shutdown"/>, <see cref="ServiceApplication.Stop"/> event.
/// </summary>
public sealed class PendingOperationEventArgs : ServiceApplication.WaitEventArgs { }
/// <summary>
/// Provides data for the <see cref="ServiceApplication.Start"/> event.
/// </summary>
public sealed class StartEventArgs : ServiceApplication.WaitEventArgs
{
public StartEventArgs(string[] arguments)
{
if (arguments == null)
throw new ArgumentNullException("arguments");
Arguments = arguments;
}
/// <summary>
/// Gets data passed by the start command.
/// </summary>
public string[] Arguments { get; private set; }
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.CustomCommand"/> event.
/// </summary>
public sealed class CustomCommandEventArgs : EventArgs
{
public CustomCommandEventArgs(int controlCode)
{
if (controlCode < 128 || controlCode > 255)
throw new ArgumentOutOfRangeException("controlCode");
ControlCode = controlCode;
}
/// <summary>
/// Gets the control code sent to the service.
/// </summary>
public int ControlCode { get; private set; }
}
/// <summary>
/// Provides data for the <see cref="ServiceApplication.ThreadException"/> event.
/// </summary>
public sealed class ThreadExceptionEventArgs : System.ComponentModel.CancelEventArgs
{
public ThreadExceptionEventArgs(Exception exception)
{
if (exception == null)
throw new ArgumentNullException("exception");
Exception = exception;
}
/// <summary>
/// Gets the exception that has occurred.
/// </summary>
public Exception Exception { get; private set; }
}
#endregion
/// <summary>
/// A static <see cref="System.ServiceProcess.ServiceBase"/> replacement for use in single-service applications.
/// </summary>
public static class ServiceApplication
{
#region Win32
[UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Winapi, CharSet = CharSet.Unicode)]
private delegate void LPSERVICE_MAIN_FUNCTION([In] int argc, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] string[] lpszArgv);
[UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Winapi, CharSet = CharSet.Unicode)]
private delegate int LPHANDLER_FUNCTION_EX([In] int dwControl, [In] int dwEventType, [In] IntPtr lpEventData, [In] IntPtr lpContext);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct SERVICE_TABLE_ENTRY
{
public string lpServiceName;
public LPSERVICE_MAIN_FUNCTION lpServiceProc;
public SERVICE_TABLE_ENTRY(string name, LPSERVICE_MAIN_FUNCTION proc)
{
lpServiceName = name;
lpServiceProc = proc;
}
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
public Guid dbcc_classguid;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public char[] dbcc_name;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DEV_BROADCAST_HANDLE
{
public int dbcc_size;
public int dbcc_devicetype;
public uint dbcc_reserved;
public IntPtr dbch_handle;
public IntPtr dbch_hdevnotify;
public Guid dbch_eventguid;
public int dbch_nameoffset;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public byte[] dbch_data;
}
private const int NO_ERROR = 0;
private const int ERROR_NOT_READY = 21;
private const int ERROR_CALL_NOT_IMPLEMENTED = 120;
private const int ERROR_CANCELLED = 1223;
[Flags]
private enum DeviceNotify
{
WindowHandle = 0x00000000,
ServiceHandle = 0x00000001,
AllInterfaceClasses = 0x00000004,
}
private enum ServiceControl
{
Stop = 0x00000001,
Pause = 0x00000002,
Continue = 0x00000003,
Interrogate = 0x00000004,
Shutdown = 0x00000005,
ParamChange = 0x00000006,
NetBindAdd = 0x00000007,
NetBindRemove = 0x00000008,
NetBindEnable = 0x00000009,
NetBindDisable = 0x0000000A,
DeviceEvent = 0x0000000B,
HardwareProfileChange = 0x0000000C,
PowerEvent = 0x0000000D,
SessionEvent = 0x0000000E,
Preshutdown = 0x0000000F,
TimeChange = 0x00000010,
TriggerEvent = 0x00000020,
UserModeReboot = 0x00000040,
}
[Flags]
private enum ServiceType
{
KernelDriver = 0x00000001,
FileSystemDriver = 0x00000002,
Win32OwnProcess = 0x00000010,
Win32ShareProcess = 0x00000020,
InteractiveProcess = 0x00000100,
}
private enum ServiceState
{
Stopped = 0x00000001,
StartPending = 0x00000002,
StopPending = 0x00000003,
Running = 0x00000004,
ContinuePending = 0x00000005,
PausePending = 0x00000006,
Paused = 0x00000007,
}
[Flags]
private enum AcceptedControlCode
{
Stop = 0x00000001,
PauseContinue = 0x00000002,
Shutdown = 0x00000004,
ParamChange = 0x00000008,
NetBindChange = 0x00000010,
HardwareProfileChange = 0x00000020,
PowerEvent = 0x00000040,
SessionChange = 0x00000080,
Preshutdown = 0x00000100,
TimeChange = 0x00000200,
TriggerEvent = 0x00000400,
UserModeReboot = 0x00000800,
}
[Flags]
private enum AcceptedControlCodeEx
{
Stop = 0x00000001,
PauseContinue = 0x00000002,
Shutdown = 0x00000004,
ParamChange = 0x00000008,
NetBindChange = 0x00000010,
HardwareProfileChange = 0x00000020,
PowerEvent = 0x00000040,
SessionChange = 0x00000080,
Preshutdown = 0x00000100,
TimeChange = 0x00000200,
TriggerEvent = 0x00000400,
UserModeReboot = 0x00000800,
Continue = 0x00010000,
Pause = 0x00020000,
DeviceEvent = 0x00040000,
CustomCommand = 0x00080000,
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct ServiceStatus
{
public ServiceType ServiceType;
public volatile ServiceState CurrentState;
public AcceptedControlCode ControlsAccepted;
public volatile int Win32ExitCode;
public int ServiceSpecificExitCode;
public uint CheckPoint;
public uint WaitHint;
}
#if DEBUG
private static readonly object debugLock = new object();
private static readonly ManualResetEvent debugReady = new ManualResetEvent(false);
private static IntPtr debugStatusHandle = IntPtr.Zero;
private static LPSERVICE_MAIN_FUNCTION debugMain = null;
private static LPHANDLER_FUNCTION_EX debugHandler;
private static IntPtr debugContext;
private static ServiceStatus debugOldStatus;
private const int ERROR_PROCESS_ABORTED = 1067;
private const int ERROR_SERVICE_NOT_IN_EXE = 1083;
private const int ERROR_INVALID_HANDLE = 6;
private const int ERROR_SERVICE_ALREADY_RUNNING = 1056;
private const int ERROR_SERVICE_START_HANG = 1070;
private const int ERROR_INVALID_DATA = 13;
private const int ERROR_SERVICE_NOT_ACTIVE = 1062;
private const int DBT_DEVTYP_DEVICEINTERFACE = 5;
private const int DBT_DEVTYP_HANDLE = 6;
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)]
private static extern void SetLastError([In] int dwErrCode);
private class DebugExitException : Exception { }
#region console handling
private static readonly List<string> debugStringHistory = new List<string>();
private static readonly Dictionary<Type, Array> debugEnumHistory = new Dictionary<Type, Array>();
private static readonly bool[] debugBoolHistory = new bool[] { true, false };
private static readonly List<Guid> debugGuidHistory = new List<Guid>() { Guid.Empty, Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
private static readonly List<DateTime> debugDateTimeHistory = new List<DateTime>() { DateTime.Now, DateTime.Today, DateTime.UtcNow };
private static readonly List<IntPtr> debugPointerHistory = new List<IntPtr>() { IntPtr.Zero };
private static readonly List<int> debugIntegerHistory = new List<int>() { int.MinValue, int.MaxValue };
private static readonly List<ushort> debugUnsignedShortHistory = new List<ushort>() { ushort.MinValue, ushort.MaxValue };
private static readonly List<uint> debugUnsignedIntegerHistory = new List<uint>() { uint.MinValue, uint.MaxValue };
private static readonly List<byte[]> debugBinaryHistory = new List<byte[]>();
private static readonly System.Text.StringBuilder debugQueryInput = new System.Text.StringBuilder();
private static string debugQueryText = null;
private static int debugQueryPosition;
private delegate bool DebugQueryParser<T>(string input, out T result);
private static void DebugWriteQueryWithinLock()
{
// calculate the current scroll window (and ensure it's at least one plus two scrollers)
var scrollWindow = Console.BufferWidth - debugQueryText.Length;
if (scrollWindow < 3)
Console.BufferWidth = debugQueryText.Length + (scrollWindow = 3);
// write the query text
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write(debugQueryText);
// write the input
Console.ForegroundColor = ConsoleColor.Gray;
if (debugQueryInput.Length < scrollWindow)
{
// everything fits
Console.Write(debugQueryInput);
Console.CursorLeft = debugQueryText.Length + debugQueryPosition;
}
else
{
if (debugQueryInput.Length == scrollWindow)
{
// only scroll if the cursor is outside the scroll window (which results in a left scroller)
if (debugQueryPosition < scrollWindow)
{
Console.Write(debugQueryInput);
Console.CursorTop--;
Console.CursorLeft = debugQueryText.Length + debugQueryPosition;
}
else
{
Console.Write('◄');
Console.Write(debugQueryInput.ToString(2, scrollWindow - 2));
}
}
else if (debugQueryInput.Length - debugQueryPosition < scrollWindow)
{
// align to the right if the end and cursor fit in the scroll window (minus the left scroller)
Console.Write('◄');
if (debugQueryPosition < debugQueryInput.Length)
{
Console.Write(debugQueryInput.ToString(debugQueryInput.Length - scrollWindow + 1, scrollWindow - 1));
Console.CursorTop--;
Console.CursorLeft = debugQueryText.Length + scrollWindow - (debugQueryInput.Length - debugQueryPosition);
}
else
Console.Write(debugQueryInput.ToString(debugQueryInput.Length - scrollWindow + 2, scrollWindow - 2));
}
else if (debugQueryPosition < (scrollWindow - 1))
{
// align to the left if the start and cursor fit into the scroll window (minus the right scroller)
Console.Write(debugQueryInput.ToString(0, scrollWindow - 1));
Console.Write('►');
Console.CursorTop--;
Console.CursorLeft = debugQueryText.Length + debugQueryPosition;
}
else
{
// nothing fits nicely, just move the cursor to the right-most position in the scroll window (minus both scrollers)
Console.Write('◄');
Console.Write(debugQueryInput.ToString(debugQueryPosition - scrollWindow + 3, scrollWindow - 2));
Console.Write('►');
Console.CursorTop--;
Console.CursorLeft = debugQueryText.Length + scrollWindow - 2;
}
}
}
private static void DebugRemoveQueryWithinLock()
{
// remove the previous line
Console.CursorLeft = 0;
Console.ForegroundColor = ConsoleColor.White;
Console.Write(new string(' ', Console.BufferWidth));
Console.CursorTop--;
}
private static void DebugWriteLine(string what, ConsoleColor color)
{
// write something in pretty colors
lock (debugLock)
{
if (debugQueryText != null)
DebugRemoveQueryWithinLock();
Console.ForegroundColor = color;
Console.WriteLine(what);
if (debugQueryText != null)
DebugWriteQueryWithinLock();
}
}
private static string DebugQueryInternal(string what, bool allowNull, string[] choices)
{
// query the user
var msg = new ArgumentNullException().Message;
while (true)
{
// start the read process
lock (debugLock)
{
if (debugQueryText != null)
throw new InvalidOperationException();
debugQueryText = what + ": ";
debugQueryPosition = 0;
debugQueryInput.Length = 0;
DebugWriteQueryWithinLock();
}
// prepare to read the input
if (choices != null && choices.Length == 0)
choices = null;
var choicePosition = -1;
var tabChoices = (string[])null;
var tabChoicePosition = 0;
while (true)
{
// handle the next console event
var key = Console.ReadKey(true);
lock (debugLock)
{
switch (key.Key)
{
// remove char
case ConsoleKey.Backspace: if (debugQueryPosition > 0) { debugQueryInput.Remove(--debugQueryPosition, 1); tabChoices = null; } break;
case ConsoleKey.Delete: if (debugQueryPosition < debugQueryInput.Length) { debugQueryInput.Remove(debugQueryPosition, 1); tabChoices = null; } break;
case ConsoleKey.Escape: debugQueryPosition = 0; debugQueryInput.Length = 0; tabChoices = null; break;
// insert char
default:
if (!char.IsControl(key.KeyChar))
{
debugQueryInput.Insert(debugQueryPosition++, key.KeyChar);
tabChoices = null;
}
break;
// quit on control-c
case ConsoleKey.C:
if (key.Modifiers == ConsoleModifiers.Control)
throw new DebugExitException();
goto default;
// move the cursor
case ConsoleKey.Home: debugQueryPosition = 0; break;
case ConsoleKey.End: debugQueryPosition = debugQueryInput.Length; break;
case ConsoleKey.LeftArrow: if (debugQueryPosition > 0) debugQueryPosition--; break;
case ConsoleKey.RightArrow: if (debugQueryPosition < debugQueryInput.Length) debugQueryPosition++; break;
// navigate through choices
case ConsoleKey.PageUp:
if (choices == null)
break;
choicePosition = choices.Length - 1;
goto case ConsoleKey.F5;
case ConsoleKey.UpArrow:
if (choices == null || choicePosition == choices.Length - 1)
break;
choicePosition++;
goto case ConsoleKey.F5;
case ConsoleKey.DownArrow:
if (choices == null || choicePosition < 1)
break;
choicePosition--;
goto case ConsoleKey.F5;
case ConsoleKey.PageDown:
if (choices == null)
break;
choicePosition = 0;
goto case ConsoleKey.F5;
case ConsoleKey.F5:
if (choices == null || choicePosition == -1)
break;
debugQueryInput.Length = 0;
debugQueryInput.Append(choices[choicePosition]);
debugQueryPosition = debugQueryInput.Length;
tabChoices = null;
break;
// provide auto-completion
case ConsoleKey.Tab:
if (tabChoices == null)
{
// try to find tab choices
if (choices == null)
break;
var input = debugQueryInput.ToString().Trim();
tabChoices = Array.FindAll(choices, choice => choice.StartsWith(input, StringComparison.InvariantCultureIgnoreCase));
if (tabChoices.Length == 0)
{
tabChoices = null;
break;
}
tabChoicePosition = 0;
}
else
{
// navigate through the tab choices
if ((key.Modifiers & ConsoleModifiers.Shift) == 0)
{
tabChoicePosition++;
if (tabChoicePosition >= tabChoices.Length)
tabChoicePosition = 0;
}
else
{
tabChoicePosition--;
if (tabChoicePosition < 0)
tabChoicePosition = tabChoices.Length - 1;
}
}
debugQueryInput.Length = 0;
debugQueryInput.Append(tabChoices[tabChoicePosition]);
debugQueryPosition = debugQueryInput.Length;
break;
}
// quit the loop on enter
if (key.Key == ConsoleKey.Enter)
{
Console.WriteLine();
break;
}
// correct for resized buffers and rewrite the query
if (Console.CursorLeft == 0)
Console.CursorTop--;
DebugRemoveQueryWithinLock();
DebugWriteQueryWithinLock();
}
}
// end the read process
lock (debugLock)
{
debugQueryText = null;
var result = debugQueryInput.ToString().Trim();
if (result.Length > 0)
return result;
if (allowNull)
return null;
DebugWriteLine(msg, ConsoleColor.Magenta);
}
}
}
private static T DebugQueryInternal<T>(string what, DebugQueryParser<T> parser, IList<T> history, bool allowNull = false, Converter<T, string> formatter = null)
{
// prepare the message for invalid input
var msg = new ArgumentException().Message;
T result = default(T);
// query the user until we get a valid result
string s;
while ((s = DebugQueryInternal(what, allowNull, Array.ConvertAll(history.ToArray(), formatter ?? (t => t.ToString())))) != null && !parser(s, out result))
DebugWriteLine(msg, ConsoleColor.Magenta);
// adjust the history if possible
if (s != null && !((System.Collections.IList)history).IsReadOnly)
{
var index = history.IndexOf(result);
switch (index)
{
case 0:
break;
case -1:
if (!((System.Collections.IList)history).IsFixedSize)
history.Insert(0, result);
break;
default:
for (var i = index; i > 0; i--)
history[i] = history[i - 1];
history[0] = result;
break;
}
}
// return the result
return result;
}
private static T DebugQueryInternal<T>(string what, DebugQueryParser<T> parser, IList<T> history, T min, T max, Converter<T, string> formatter = null) where T : struct, IComparable<T>
{
// repeatedly query the user until we get something within bounds
var msg = new ArgumentOutOfRangeException().Message;
while (true)
{
var result = DebugQueryInternal<T>(what, parser, history, false, formatter);
if (min.CompareTo(result) <= 0 && max.CompareTo(result) >= 0)
return result;
DebugWriteLine(msg, ConsoleColor.Magenta);
}
}
private static string DebugQueryString(string name, bool allowNull = false)
{
return DebugQueryInternal<string>
(
name,
(string input, out string result) =>
{
// allow the use of two double quotes as emtpy string
result = input == "\"\"" ? string.Empty : input;
return true;
},
debugStringHistory,
allowNull
);
}
private static DateTime DebugQueryDateTime(string name)
{
return DebugQueryInternal<DateTime>(name, DateTime.TryParse, debugDateTimeHistory);
}
private static int DebugQueryInteger(string name, int min = int.MinValue, int max = int.MaxValue)
{
return DebugQueryInternal<int>(name, int.TryParse, debugIntegerHistory, min, max);
}
private static uint DebugQueryUnsignedInteger(string name, uint min = uint.MinValue, uint max = uint.MaxValue)
{
return DebugQueryInternal<uint>(name, uint.TryParse, debugUnsignedIntegerHistory, min, max);
}
private static ushort DebugQueryUnsignedShort(string name, ushort min = ushort.MinValue, ushort max = ushort.MaxValue)
{
return DebugQueryInternal<ushort>(name, ushort.TryParse, debugUnsignedShortHistory, min, max);
}
private static Guid DebugQueryGuid(string name)
{
return DebugQueryInternal<Guid>
(
name,
(string input, out Guid result) =>
{
if (input != null)
{
try
{
result = new Guid(input);
return true;
}
catch (FormatException) { }
catch (OverflowException) { }
}
result = Guid.Empty;
return false;
},
debugGuidHistory
);
}
private static bool DebugQueryBool(string name)
{
return DebugQueryInternal<bool>(name, bool.TryParse, debugBoolHistory);
}
private static byte[] DebugQueryBinary(string name, bool allowNull = false)
{
return DebugQueryInternal
(
name,
(string input, out byte[] result) =>
{
// make a crude check
if (input == null)
goto InvalidArray;
// check if the input is string that should be converted to binary
input = input.Trim();
if (input.EndsWith("\""))
{
if (input.StartsWith("L\""))
result = System.Text.Encoding.Unicode.GetBytes(input);
else if (input.StartsWith("\""))
result = System.Text.Encoding.Default.GetBytes(input);
else
goto InvalidArray;
return true;
}
// remove the leading hex specifier
if (input.StartsWith("0x"))
input = input.Substring(2);
// enumerate over every char
var lastNumber = -1;
var list = new List<byte>(input.Length / 2);
foreach (char ch in input)
{
// skip spaces
if (ch == ' ' || ch == '\t')
continue;
// get and check the digit
int currentNumber;
if (ch >= '0' && ch <= '9')
currentNumber = ch - '0';
else if (ch >= 'A' && ch <= 'F')
currentNumber = ch - 'A';
else if (ch >= 'a' && ch <= 'f')
currentNumber = ch - 'a';
else
goto InvalidArray;
// either append the two digits as byte or remember the current digit
if (lastNumber != -1)
{
list.Add((byte)(lastNumber << 4 | currentNumber));
lastNumber = -1;
}
else
lastNumber = currentNumber;
}
// if everything was read properly store the array and return success
if (lastNumber == -1)
{
result = list.ToArray();
return true;
}
InvalidArray:
// in case something went wrong
result = null;
return false;
},
debugBinaryHistory,
allowNull,
binary => binary.Aggregate("0x", (s, b) => s + b.ToString("X2"))
);
}
private static IntPtr DebugQueryPointer(string name, bool allowZero = false)
{
// query the user for a pointer that may not be null
var msg = new ArgumentNullException().Message;
IntPtr checkedResult;
while
(
IntPtr.Zero ==
(
checkedResult = DebugQueryInternal<IntPtr>
(
name,
(string input, out IntPtr result) =>
{
// check the input
if (input != null)
{
// remove the leading hex specifier
input = input.TrimStart();
if (input.StartsWith("0x"))
input = input.Substring(2);
// parse the input as hex number
long val;
if (long.TryParse(input, System.Globalization.NumberStyles.HexNumber, null, out val))
{
// convert and return the number
result = new IntPtr(val);
return true;
}
}
// in case something went wrong
result = IntPtr.Zero;
return false;
},
debugPointerHistory,
false,
p => "0x" + p.ToString("X" + IntPtr.Size)
)
) &&
!allowZero
)
DebugWriteLine(msg, ConsoleColor.Magenta);
return checkedResult;
}
private static T DebugQueryEnum<T>(string name, bool allowAnyValue = false) where T : struct
{
// retrieve the cached history
Array values;
if (!debugEnumHistory.TryGetValue(typeof(T), out values))
debugEnumHistory.Add(typeof(T), values = Enum.GetValues(typeof(T)));
// query the enum value
return DebugQueryInternal<T>
(
name,
(string s, out T t) =>
{
// try to parse the string
if (s != null)
{
try
{
t = (T)Enum.Parse(typeof(T), s, true);
return true;
}
catch (ArgumentException)
{
// if any value is allowed, try to parse the integer
if (allowAnyValue)
{
int i;
if (int.TryParse(s, out i))
{
t = (T)Enum.ToObject(typeof(T), i);
return true;
}
};
}
}
// invalid input
t = default(T);
return false;
},
(T[])values
);
}
#endregion
#region registration handling
private static readonly Dictionary<IntPtr, KeyValuePair<DebugNotificationType, object>> debugNotifications = new Dictionary<IntPtr, KeyValuePair<DebugNotificationType, object>>();
private static int debugNextNotificationHandle = 1;
private enum DebugNotificationType
{
Device,
PowerSetting,
}
private static IntPtr DebugRegisterNotification(bool invalid, IntPtr recipient, DeviceNotify flags, DebugNotificationType type, object o)
{
// check the input parameters
if (invalid || ((flags & DeviceNotify.WindowHandle) != 0 && (flags & DeviceNotify.ServiceHandle) != 0))
{
SetLastError(ERROR_INVALID_DATA);
return IntPtr.Zero;
}
if ((flags & DeviceNotify.ServiceHandle) == 0 || recipient == IntPtr.Zero || recipient != debugStatusHandle)
{
SetLastError(ERROR_INVALID_HANDLE);
return IntPtr.Zero;
}
// add the notification to the list
lock (debugNotifications)
{
var handle = new IntPtr(debugNextNotificationHandle++);
debugNotifications.Add(handle, new KeyValuePair<DebugNotificationType, object>(type, o));
return handle;
}
}
private static bool DebugUnregisterNotification(IntPtr handle, DebugNotificationType type)
{
// remove the notification if the handle exists
KeyValuePair<DebugNotificationType, object> entry;
if (handle != IntPtr.Zero)
lock (debugNotifications)
if (debugNotifications.TryGetValue(handle, out entry) && entry.Key == type)
return debugNotifications.Remove(handle);
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
#endregion
private static IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, DeviceNotify Flags)
{
return DebugRegisterNotification
(
PowerSettingGuid == Guid.Empty,
hRecipient,
Flags,
DebugNotificationType.PowerSetting,
PowerSettingGuid
);
}
private static bool UnregisterPowerSettingNotification(IntPtr Handle)
{
return DebugUnregisterNotification(Handle, DebugNotificationType.PowerSetting);
}
private static IntPtr RegisterDeviceNotification(IntPtr hRecipient, ref DEV_BROADCAST_HANDLE NotificationFilter, DeviceNotify Flags)
{
return DebugRegisterNotification
(
(
(Flags & DeviceNotify.AllInterfaceClasses) != 0 ||
NotificationFilter.dbcc_size != Marshal.SizeOf(typeof(DEV_BROADCAST_HANDLE)) ||
NotificationFilter.dbcc_devicetype != DBT_DEVTYP_HANDLE ||
NotificationFilter.dbch_handle == IntPtr.Zero
),
hRecipient,
Flags,
DebugNotificationType.Device,
NotificationFilter.dbch_handle
);
}
private static IntPtr RegisterDeviceNotification(IntPtr hRecipient, ref DEV_BROADCAST_DEVICEINTERFACE NotificationFilter, DeviceNotify Flags)
{
return DebugRegisterNotification
(
(
NotificationFilter.dbcc_size != Marshal.SizeOf(typeof(DEV_BROADCAST_DEVICEINTERFACE)) ||
NotificationFilter.dbcc_devicetype != DBT_DEVTYP_DEVICEINTERFACE ||
((Flags & DeviceNotify.AllInterfaceClasses) == 0 && NotificationFilter.dbcc_classguid == Guid.Empty)
),
hRecipient,
Flags,
DebugNotificationType.Device,
(Flags & DeviceNotify.AllInterfaceClasses) == 0 ? NotificationFilter.dbcc_classguid : Guid.Empty
);
}
private static bool UnregisterDeviceNotification(IntPtr Handle)
{
return DebugUnregisterNotification(Handle, DebugNotificationType.Device);
}
private static bool SetServiceStatus(IntPtr hServiceStatus, ref ServiceStatus lpServiceStatus)
{
// check the input data
if (hServiceStatus == IntPtr.Zero || hServiceStatus != debugStatusHandle)
{
SetLastError(ERROR_INVALID_HANDLE);
return false;
}
if
(
(lpServiceStatus.ServiceType & ServiceType.Win32OwnProcess) == 0 ||
(lpServiceStatus.ServiceType & ServiceType.Win32ShareProcess) != 0 ||
!Enum.IsDefined(typeof(ServiceState), lpServiceStatus.CurrentState)
)
{
SetLastError(ERROR_INVALID_DATA);
return false;
}
// make sure that once the service enters stopped nothing else is allowed and copy the old status
ServiceStatus oldStatus;
lock (debugLock)
{
if (debugOldStatus.CurrentState == ServiceState.Stopped)
{
SetLastError(ERROR_SERVICE_NOT_ACTIVE);
return false;
}
oldStatus = debugOldStatus;
debugOldStatus = lpServiceStatus;
}
// log the difference between the old and new status
if (oldStatus.CurrentState != lpServiceStatus.CurrentState)
LogEvent(EventLogEntryType.Information, "Status: {0}", lpServiceStatus.CurrentState);
if (oldStatus.ControlsAccepted != lpServiceStatus.ControlsAccepted)
LogEvent(EventLogEntryType.Information, "Accepted Controls: {0}", lpServiceStatus.ControlsAccepted);
if (oldStatus.Win32ExitCode != lpServiceStatus.Win32ExitCode)
LogEvent(EventLogEntryType.Warning, "Exit Code: {0}", lpServiceStatus.Win32ExitCode);
return true;
}
private static IntPtr RegisterServiceCtrlHandlerEx(string lpServiceName, LPHANDLER_FUNCTION_EX lpHandlerProc, IntPtr lpContext)
{
// check the input data
if (lpHandlerProc == null)
{
SetLastError(ERROR_INVALID_DATA);
return IntPtr.Zero;
}
// complete work
lock (debugLock)
{
// ensure that StartServiceCtrlDispatcher has been called
if (debugMain == null)
{
SetLastError(ERROR_SERVICE_NOT_IN_EXE);
return IntPtr.Zero;
}
// ensure that this method hasn't already been called
if (debugStatusHandle != IntPtr.Zero)
{
SetLastError(ERROR_SERVICE_ALREADY_RUNNING);
return IntPtr.Zero;
}
// generate the handle
debugStatusHandle = new IntPtr(new Random().Next(int.MaxValue - 1) + 1);
debugContext = lpContext;
debugHandler = lpHandlerProc;
debugReady.Set();
}
// return the handle
return debugStatusHandle;
}
private static bool StartServiceCtrlDispatcher(SERVICE_TABLE_ENTRY[] lpServiceTable)
{
// check the input data
if
(
lpServiceTable == null ||
lpServiceTable.Length != 2 ||
lpServiceTable[1].lpServiceName != null ||
lpServiceTable[1].lpServiceProc != null ||
lpServiceTable[0].lpServiceName == null ||
lpServiceTable[0].lpServiceProc == null
)
{
SetLastError(ERROR_INVALID_DATA);
return false;
}
// ensure that we haven't been started before
lock (debugLock)
{
if (debugMain != null)
{
SetLastError(ERROR_SERVICE_ALREADY_RUNNING);
return false;
}
debugMain = lpServiceTable[0].lpServiceProc;
}
// start the main thread
var mainThread = new Thread(arg => debugMain(arg == null ? 0 : ((string[])arg).Length, (string[])arg));
var args = Environment.GetCommandLineArgs();
mainThread.Start(args.Length > 1 ? args.Skip(1).ToArray() : null);
if (!debugReady.WaitOne(30000))
Environment.Exit(ERROR_SERVICE_START_HANG);
// enter the dispatching loop
try
{
while (true)
{
// get the next control
var control = DebugQueryEnum<ServiceControl>("Control", true);
// set the eventType and eventData
int eventType;
IntPtr eventData;
bool cleanUp;
switch (control)
{
// get the native device event
case ServiceControl.DeviceEvent:
switch (DebugQueryEnum<DeviceEventDeviceType>("Device Type"))
{
case DeviceEventDeviceType.Interface:
cleanUp = new InterfaceDeviceEventArgs
(
DebugQueryEnum<DeviceEventType>("Event Type"),
DebugQueryGuid("Class"),
DebugQueryString("Name", true) ?? string.Empty
).ToNative(out eventType, out eventData);
break;
case DeviceEventDeviceType.Handle:
cleanUp = new HandleDeviceEventArgs
(
DebugQueryEnum<DeviceEventType>("Event Type"),
DebugQueryPointer("Handle", false),
DebugQueryGuid("Event Guid"),
DebugQueryString("Name", true),
DebugQueryBinary("Data", true)
).ToNative(out eventType, out eventData);
break;
case DeviceEventDeviceType.Oem:
cleanUp = new OemDeviceEventArgs
(
DebugQueryEnum<DeviceEventType>("Event Type"),
DebugQueryUnsignedInteger("Identifier"),
DebugQueryUnsignedInteger("Supplied Function")
).ToNative(out eventType, out eventData);
break;
case DeviceEventDeviceType.Port:
cleanUp = new PortDeviceEventArgs
(
DebugQueryEnum<DeviceEventType>("Event Type"),
DebugQueryString("Name")
).ToNative(out eventType, out eventData);
break;
case DeviceEventDeviceType.Volume:
cleanUp = new VolumeDeviceEventArgs
(
DebugQueryEnum<DeviceEventType>("Event Type"),
DebugQueryUnsignedInteger("Unit Mask"),
DebugQueryBool("Is Media Event"),
DebugQueryBool("Is Network Event")
).ToNative(out eventType, out eventData);
break;
default:
cleanUp = new UnknownDeviceEventArgs(DebugQueryEnum<DeviceEventType>("Event Type")).ToNative(out eventType, out eventData);
break;
}
break;
// get the native hardware profile event
case ServiceControl.HardwareProfileChange:
cleanUp = new HardwareProfileEventArgs(DebugQueryEnum<HardwareProfileEventType>("Type")).ToNative(out eventType, out eventData);
break;
// get the native power event
case ServiceControl.PowerEvent:
var powerEventType = DebugQueryEnum<PowerEventType>("Type");
switch (powerEventType)
{
case PowerEventType.OemEvent:
cleanUp = new OemPowerEventArgs(DebugQueryUnsignedShort("Code", 0x0200, 0x20FF)).ToNative(out eventType, out eventData);
break;
case PowerEventType.PowerSettingChange:
cleanUp = new SettingChangePowerEventArgs(DebugQueryGuid("Setting"), DebugQueryBinary("Data")).ToNative(out eventType, out eventData);
break;
default:
cleanUp = new SimplePowerEventArgs(powerEventType).ToNative(out eventType, out eventData);
break;
}
break;
// get the native session event
case ServiceControl.SessionEvent:
cleanUp = new SessionChangedEventArgs(DebugQueryEnum<SessionChangedReason>("Reason"), DebugQueryInteger("Session Id")).ToNative(out eventType, out eventData);
break;
// get the native time change event
case ServiceControl.TimeChange:
cleanUp = new TimeChangedEventArgs(DebugQueryDateTime("New Time"), DebugQueryDateTime("Old Time")).ToNative(out eventType, out eventData);
break;
// control codes that don't require any additional params
case ServiceControl.Interrogate:
case ServiceControl.NetBindAdd:
case ServiceControl.NetBindDisable:
case ServiceControl.NetBindEnable:
case ServiceControl.NetBindRemove:
case ServiceControl.Continue:
case ServiceControl.ParamChange:
case ServiceControl.Pause:
case ServiceControl.Preshutdown:
case ServiceControl.Shutdown:
case ServiceControl.Stop:
case ServiceControl.TriggerEvent:
case ServiceControl.UserModeReboot:
default:
eventType = 0;
eventData = IntPtr.Zero;
cleanUp = false;
break;
}
// call the handler and cleanup the event data afterwards if there is any
var result = debugHandler((int)control, eventType, eventData, debugContext);
LogEvent(result == NO_ERROR ? EventLogEntryType.Information : result == ERROR_CANCELLED || result == ERROR_CALL_NOT_IMPLEMENTED ? EventLogEntryType.Warning : EventLogEntryType.Error, "Result: {0}", new Win32Exception(result).Message);
if (cleanUp)
Marshal.FreeHGlobal(eventData);
}
}
catch (DebugExitException)
{
// if the service has exited, return true
lock (debugLock)
if (debugOldStatus.CurrentState == ServiceState.Stopped)
return true;
// otherwise abort the process
Environment.Exit(ERROR_PROCESS_ABORTED);
return false;
}
}
#else
private static EventLog log;
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterPowerSettingNotification([In] IntPtr hRecipient, [In] ref Guid PowerSettingGuid, [In] DeviceNotify Flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool UnregisterPowerSettingNotification([In] IntPtr Handle);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification([In] IntPtr hRecipient, [In] ref DEV_BROADCAST_HANDLE NotificationFilter, [In] DeviceNotify Flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification([In] IntPtr hRecipient, [In] ref DEV_BROADCAST_DEVICEINTERFACE NotificationFilter, [In] DeviceNotify Flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool UnregisterDeviceNotification([In] IntPtr Handle);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetServiceStatus([In] IntPtr hServiceStatus, [In] ref ServiceStatus lpServiceStatus);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr RegisterServiceCtrlHandlerEx([In] string lpServiceName, [In] LPHANDLER_FUNCTION_EX lpHandlerProc, [In, Optional] IntPtr lpContext);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool StartServiceCtrlDispatcher([In] SERVICE_TABLE_ENTRY[] lpServiceTable);
#endif
#endregion
private static readonly object initializeLock = new object();
private static readonly object serviceLock = new object();
private static readonly LPHANDLER_FUNCTION_EX handlerExDelegate = new LPHANDLER_FUNCTION_EX(HandlerEx);
private static readonly AutoResetEvent serviceMainSignal = new AutoResetEvent(false);
private static readonly NotificationCounter<Guid> powerSettingNotifications = new NotificationCounter<Guid>();
private static readonly NotificationCounter<IntPtr> deviceHandleNotifications = new NotificationCounter<IntPtr>();
private static ServiceStatus status = new ServiceStatus();
private static bool initialized = false;
private static bool exitPrematurely = false;
private static volatile bool quitServiceMain = false;
private static IntPtr statusHandle = IntPtr.Zero;
private static WaitEventArgs currentWaitEventArgs = null;
private static EventHandler<PendingOperationEventArgs> pendingEventHandler = null;
private static string mainServiceName = null;
private static IntPtr deviceNotificationHandle = IntPtr.Zero;
private static EventHandler<PendingOperationEventArgs> continueHandler;
private static EventHandler<NetworkBindingChangedEventArgs> networkBindingChangedHandler;
private static EventHandler<EventArgs> parametersChangedHandler;
private static EventHandler<PendingOperationEventArgs> pauseHandler;
private static EventHandler<PendingOperationEventArgs> preshutdownHandler;
private static EventHandler<PendingOperationEventArgs> shutdownHandler;
private static EventHandler<PendingOperationEventArgs> stopHandler;
private static EventHandler<DeviceEventArgs> deviceEventHandler;
private static EventHandler<HardwareProfileEventArgs> hardwareProfileEventHandler;
private static EventHandler<PowerEventArgs> powerEventHandler;
private static EventHandler<SessionChangedEventArgs> sessionChangedHandler;
private static EventHandler<TimeChangedEventArgs> timeChangedHandler;
private static EventHandler<EventArgs> triggerEventHandler;
private static EventHandler<EventArgs> userModeRebootHandler;
private static EventHandler<CustomCommandEventArgs> customCommandHandler;
private static EventHandler<StartEventArgs> startHandler;
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class WaitEventArgs : EventArgs
{
internal WaitEventArgs() { }
/// <summary>
/// Requests additional time for a pending operation.
/// </summary>
/// <param name="requiredTime">The requested time.</param>
/// <exception cref="InvalidOperationException">The method is called after the event has been handled.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="requiredTime"/> is not positive.</exception>
public void RequestAdditionalTime(TimeSpan requiredTime)
{
// check the input param
if (requiredTime <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException("requiredTime");
// ensure that this is the current pending action and update the check point
lock (serviceLock)
{
if (currentWaitEventArgs != this)
throw new InvalidOperationException();
status.CheckPoint++;
status.WaitHint = (uint)requiredTime.TotalMilliseconds;
if (!SetServiceStatus(statusHandle, ref status))
throw new Win32Exception();
}
}
}
private class NotificationCounter<T>
{
private class HandleRef
{
private uint refCount = 1;
public HandleRef(IntPtr handle) { Handle = handle; }
public IntPtr Handle { get; private set; }
public void AddRef() { if (refCount == 0) throw new InvalidOperationException(); refCount++; }
public bool Release() { if (refCount == 0) throw new InvalidOperationException(); return --refCount == 0; }
}
public delegate IntPtr RegisterFunction<U>(IntPtr recipient, ref U filter, DeviceNotify flags);
public delegate bool UnregisterFunction(IntPtr notificationHandle);
private readonly Dictionary<T, HandleRef> handles = new Dictionary<T, HandleRef>();
public void Register<U>(T filter, Converter<T, U> converter, RegisterFunction<U> register, Version requiredWindowsVersion)
{
if (Environment.OSVersion.Version < requiredWindowsVersion)
throw new PlatformNotSupportedException();
Register(filter, converter, register);
}
public void Register<U>(T filter, Converter<T, U> converter, RegisterFunction<U> register)
{
// make sure the service is initialized
if (statusHandle == IntPtr.Zero)
throw new InvalidOperationException();
// create the handle reference or increase the reference counter
lock (handles)
{
HandleRef handleRef;
if (handles.TryGetValue(filter, out handleRef))
{
handleRef.AddRef();
return;
}
var convertedFilter = converter(filter);
var handle = register(statusHandle, ref convertedFilter, DeviceNotify.ServiceHandle);
if (handle == IntPtr.Zero)
throw new Win32Exception();
handles.Add(filter, new HandleRef(handle));
}
}
public bool Unregister(T filter, UnregisterFunction unregister)
{
// release the notification handle or decrease the reference counter
lock (handles)
{
HandleRef handleRef;
if (!handles.TryGetValue(filter, out handleRef) || !handleRef.Release())
return false;
handles.Remove(filter);
return unregister(handleRef.Handle);
}
}
}
internal static void CheckEnum<T>(string paramName, T value)
{
// ensure that the enum value is defined
if (!Enum.IsDefined(typeof(T), value))
throw new InvalidEnumArgumentException(paramName, Convert.ToInt32(value), typeof(T));
}
private static void SetAcceptedControlsAndReportStatusWithinLock()
{
// generate the bitmask
status.ControlsAccepted = 0;
switch (status.CurrentState)
{
// always allow the continue command when paused in addition to everything else
case ServiceState.Paused:
status.ControlsAccepted |= AcceptedControlCode.PauseContinue;
goto case ServiceState.Running;
// allow state changing commands in addition to other commands
case ServiceState.Running:
if (stopHandler != null) status.ControlsAccepted |= AcceptedControlCode.Stop;
if (pauseHandler != null || continueHandler != null) status.ControlsAccepted |= AcceptedControlCode.PauseContinue;
if (shutdownHandler != null) status.ControlsAccepted |= AcceptedControlCode.Shutdown;
if (preshutdownHandler != null) status.ControlsAccepted |= AcceptedControlCode.Preshutdown;
goto default;
// clean up the device notification and don't allow any further commands
case ServiceState.Stopped:
if (deviceNotificationHandle != IntPtr.Zero)
{
UnregisterDeviceNotification(deviceNotificationHandle);
deviceNotificationHandle = IntPtr.Zero;
}
break;
// add all non-state changing commands and register or unregister for device notifications
default:
if (parametersChangedHandler != null) status.ControlsAccepted |= AcceptedControlCode.ParamChange;
if (networkBindingChangedHandler != null) status.ControlsAccepted |= AcceptedControlCode.NetBindChange;
if (hardwareProfileEventHandler != null) status.ControlsAccepted |= AcceptedControlCode.HardwareProfileChange;
if (powerEventHandler != null) status.ControlsAccepted |= AcceptedControlCode.PowerEvent;
if (sessionChangedHandler != null) status.ControlsAccepted |= AcceptedControlCode.SessionChange;
if (timeChangedHandler != null) status.ControlsAccepted |= AcceptedControlCode.TimeChange;
if (triggerEventHandler != null) status.ControlsAccepted |= AcceptedControlCode.TriggerEvent;
if (userModeRebootHandler != null) status.ControlsAccepted |= AcceptedControlCode.UserModeReboot;
if (deviceEventHandler == null ^ deviceNotificationHandle == IntPtr.Zero)
{
if (deviceEventHandler == null)
{
var result = UnregisterDeviceNotification(deviceNotificationHandle);
deviceNotificationHandle = IntPtr.Zero;
if (!result)
throw new Win32Exception();
}
else
{
var filter = new DEV_BROADCAST_DEVICEINTERFACE()
{
dbcc_size = Marshal.SizeOf(typeof(DEV_BROADCAST_DEVICEINTERFACE)),
dbcc_devicetype = (int)DeviceEventDeviceType.Interface,
};
deviceNotificationHandle = RegisterDeviceNotification(statusHandle, ref filter, DeviceNotify.ServiceHandle | DeviceNotify.AllInterfaceClasses);
if (deviceNotificationHandle == IntPtr.Zero)
{
deviceEventHandler = null;
throw new Win32Exception();
}
}
}
break;
}
// notify the SCM
if (!SetServiceStatus(statusHandle, ref status))
throw new Win32Exception();
}
private static void AddEventHandler<T>(ref EventHandler<T> currentHandlers, EventHandler<T> newHandler, Version requiredWindowsVersion) where T : EventArgs
{
// ensure that the OS is supporting this event
if (Environment.OSVersion.Version < requiredWindowsVersion)
throw new PlatformNotSupportedException();
AddEventHandler(ref currentHandlers, newHandler);
}
private static void AddEventHandler<T>(ref EventHandler<T> currentHandlers, EventHandler<T> newHandler) where T : EventArgs
{
// add the event handler and update the status
lock (serviceLock)
{
currentHandlers += newHandler;
if (statusHandle != IntPtr.Zero && status.CurrentState != ServiceState.Stopped)
SetAcceptedControlsAndReportStatusWithinLock();
}
}
private static void RemoveEventHandler<T>(ref EventHandler<T> currentHandlers, EventHandler<T> oldHandler) where T : EventArgs
{
// remove the event handler and update the status
lock (serviceLock)
{
currentHandlers -= oldHandler;
if (statusHandle != IntPtr.Zero && status.CurrentState != ServiceState.Stopped)
SetAcceptedControlsAndReportStatusWithinLock();
}
}
private static void CallEventHandler<T>(EventHandler<T> handler, T args) where T : EventArgs
{
// invoke the handler and forward exceptions to the thread exception handler
try { handler(null, args); }
catch (Exception e)
{
var threadExceptionHandler = ThreadException;
if (threadExceptionHandler != null)
{
var exArgs = new ThreadExceptionEventArgs(e);
threadExceptionHandler(null, exArgs);
if (exArgs.Cancel)
return;
}
throw;
}
}
private static void CallWaitEventHandler<T>(EventHandler<T> handler, T args) where T : WaitEventArgs
{
// set the current event args
lock (serviceLock)
{
// this should never happen since the method isn't reentrant, but check it anyway
if (currentWaitEventArgs != null)
throw new InvalidOperationException();
currentWaitEventArgs = args;
}
// call the handler
try { CallEventHandler(handler, args); }
finally
{
// clear the event args
lock (serviceLock)
currentWaitEventArgs = null;
}
}
private static int SetTargetStateFromSystem(ref EventHandler<PendingOperationEventArgs> handler, ServiceState state, AcceptedControlCodeEx code)
{
// enter the service lock
lock (serviceLock)
{
// check if we can perform the operation or if we even can skip the pending action
switch (code)
{
case AcceptedControlCodeEx.Continue:
// ensure that we can continue and are not already running
if ((status.ControlsAccepted & AcceptedControlCode.PauseContinue) == 0)
return ERROR_CALL_NOT_IMPLEMENTED;
if (status.CurrentState == ServiceState.Running)
return NO_ERROR;
// skip the pending and go straight to running if the handler isn't set
if (continueHandler == null)
{
status.CurrentState = ServiceState.Running;
SetAcceptedControlsAndReportStatusWithinLock();
return NO_ERROR;
}
break;
case AcceptedControlCodeEx.Pause:
// ensure that we can pause and are not already paused
if ((status.ControlsAccepted & AcceptedControlCode.PauseContinue) == 0)
return ERROR_CALL_NOT_IMPLEMENTED;
if (status.CurrentState == ServiceState.Paused)
return NO_ERROR;
// skip the pending and go straight to paused if the handler isn't set
if (pauseHandler == null)
{
status.CurrentState = ServiceState.Paused;
SetAcceptedControlsAndReportStatusWithinLock();
return NO_ERROR;
}
break;
default:
// ensure that the operation is supported
if ((status.ControlsAccepted & (AcceptedControlCode)code) == 0)
return ERROR_CALL_NOT_IMPLEMENTED;
break;
}
// set the pending event handler and the new pending state and signal the main thread
pendingEventHandler = handler;
status.CurrentState = state;
SetAcceptedControlsAndReportStatusWithinLock();
serviceMainSignal.Set();
}
return NO_ERROR;
}
private static int CallEventHandlerFromSystem<T>(ref EventHandler<T> handler, T args, AcceptedControlCodeEx code) where T : EventArgs
{
// perform checks within the lock
EventHandler<T> handlerCopy;
lock (serviceLock)
{
// ensure that the handler is available
switch (code)
{
case AcceptedControlCodeEx.CustomCommand:
if (customCommandHandler == null)
return ERROR_CALL_NOT_IMPLEMENTED;
break;
case AcceptedControlCodeEx.DeviceEvent:
if (deviceNotificationHandle == IntPtr.Zero)
return ERROR_CALL_NOT_IMPLEMENTED;
break;
default:
if ((status.ControlsAccepted & (AcceptedControlCode)code) == 0)
return ERROR_CALL_NOT_IMPLEMENTED;
break;
}
// make a copy of the handler
handlerCopy = handler;
}
// handle cancelable events immediately
if (args is CancelEventArgs && (args as CancelEventArgs).CanBeCanceled)
{
CallEventHandler(handlerCopy, args);
return (args as CancelEventArgs).Cancel ? ERROR_CANCELLED : NO_ERROR;
}
// queue the handler for later
ThreadPool.QueueUserWorkItem(_ => CallEventHandler(handlerCopy, args));
return NO_ERROR;
}
private static int HandlerEx(int control, int eventType, IntPtr eventData, IntPtr context)
{
// this should never happen, but check it anyway
if (!initialized)
throw new InvalidOperationException();
// make sure RegisterServiceCtrlHandlerEx has returned
if (statusHandle == IntPtr.Zero)
return ERROR_NOT_READY;
// handle custom command codes
if (128 <= control && control <= 255)
return CallEventHandlerFromSystem(ref customCommandHandler, new CustomCommandEventArgs(control), AcceptedControlCodeEx.CustomCommand);
// handle other known control codes
switch ((ServiceControl)control)
{
case ServiceControl.Interrogate:
lock (serviceLock)
return SetServiceStatus(statusHandle, ref status) ? NO_ERROR : Marshal.GetLastWin32Error();
case ServiceControl.Stop:
return SetTargetStateFromSystem(ref stopHandler, ServiceState.StopPending, AcceptedControlCodeEx.Stop);
case ServiceControl.Pause:
return SetTargetStateFromSystem(ref pauseHandler, ServiceState.PausePending, AcceptedControlCodeEx.Pause);
case ServiceControl.Continue:
return SetTargetStateFromSystem(ref continueHandler, ServiceState.ContinuePending, AcceptedControlCodeEx.Continue);
case ServiceControl.Shutdown:
return SetTargetStateFromSystem(ref shutdownHandler, ServiceState.StopPending, AcceptedControlCodeEx.Shutdown);
case ServiceControl.Preshutdown:
return SetTargetStateFromSystem(ref preshutdownHandler, ServiceState.StopPending, AcceptedControlCodeEx.Preshutdown);
case ServiceControl.ParamChange:
return CallEventHandlerFromSystem(ref parametersChangedHandler, EventArgs.Empty, AcceptedControlCodeEx.ParamChange);
case ServiceControl.NetBindAdd:
return CallEventHandlerFromSystem(ref networkBindingChangedHandler, new NetworkBindingChangedEventArgs(NetworkBindingChangedReason.Add), AcceptedControlCodeEx.NetBindChange);
case ServiceControl.NetBindRemove:
return CallEventHandlerFromSystem(ref networkBindingChangedHandler, new NetworkBindingChangedEventArgs(NetworkBindingChangedReason.Remove), AcceptedControlCodeEx.NetBindChange);
case ServiceControl.NetBindEnable:
return CallEventHandlerFromSystem(ref networkBindingChangedHandler, new NetworkBindingChangedEventArgs(NetworkBindingChangedReason.Enable), AcceptedControlCodeEx.NetBindChange);
case ServiceControl.NetBindDisable:
return CallEventHandlerFromSystem(ref networkBindingChangedHandler, new NetworkBindingChangedEventArgs(NetworkBindingChangedReason.Disable), AcceptedControlCodeEx.NetBindChange);
case ServiceControl.DeviceEvent:
return CallEventHandlerFromSystem(ref deviceEventHandler, DeviceEventArgs.FromNative(eventType, eventData), AcceptedControlCodeEx.DeviceEvent);
case ServiceControl.HardwareProfileChange:
return CallEventHandlerFromSystem(ref hardwareProfileEventHandler, HardwareProfileEventArgs.FromNative(eventType, eventData), AcceptedControlCodeEx.HardwareProfileChange);
case ServiceControl.PowerEvent:
return CallEventHandlerFromSystem(ref powerEventHandler, PowerEventArgs.FromNative(eventType, eventData), AcceptedControlCodeEx.PowerEvent);
case ServiceControl.SessionEvent:
return CallEventHandlerFromSystem(ref sessionChangedHandler, SessionChangedEventArgs.FromNative(eventType, eventData), AcceptedControlCodeEx.SessionChange);
case ServiceControl.TimeChange:
return CallEventHandlerFromSystem(ref timeChangedHandler, TimeChangedEventArgs.FromNative(eventType, eventData), AcceptedControlCodeEx.TimeChange);
case ServiceControl.TriggerEvent:
return CallEventHandlerFromSystem(ref triggerEventHandler, EventArgs.Empty, AcceptedControlCodeEx.TriggerEvent);
case ServiceControl.UserModeReboot:
return CallEventHandlerFromSystem(ref userModeRebootHandler, EventArgs.Empty, AcceptedControlCodeEx.UserModeReboot);
}
// must be something new
return ERROR_CALL_NOT_IMPLEMENTED;
}
private static void ServiceMain(int argc, string[] argv)
{
// this should never happen, but check it anyway
if (!initialized)
throw new InvalidOperationException();
// initialize the service itself
lock (serviceLock)
{
// again, shouldn't happen, but what the heck
if (statusHandle != IntPtr.Zero)
throw new InvalidOperationException();
// initialize the status structure
status.ServiceType = ServiceType.Win32OwnProcess;
status.CurrentState = quitServiceMain ? ServiceState.Stopped : ServiceState.StartPending;
status.CheckPoint = 0;
status.WaitHint = 0;
// register the handle
statusHandle = RegisterServiceCtrlHandlerEx(mainServiceName, handlerExDelegate, IntPtr.Zero);
if (statusHandle == IntPtr.Zero)
throw new Win32Exception();
// report the status
SetAcceptedControlsAndReportStatusWithinLock();
// if we're already stopped, return immediately
if (quitServiceMain)
return;
}
// handle state changes
do
{
// shutdown the service if requested
if (quitServiceMain)
{
lock (serviceLock)
{
status.CurrentState = ServiceState.Stopped;
SetAcceptedControlsAndReportStatusWithinLock();
}
break;
}
// get the current state within a lock
ServiceState current;
lock (serviceLock)
current = status.CurrentState;
// call the necessary event handler or continue with the loop if nothing needs to be done
switch (status.CurrentState)
{
case ServiceState.StartPending:
if (startHandler != null) // NOTE: no copy necessary since initialized = true
CallWaitEventHandler(startHandler, new StartEventArgs(argv ?? new string[] { mainServiceName }));
current = ServiceState.Running;
break;
case ServiceState.PausePending:
CallWaitEventHandler(pendingEventHandler, new PendingOperationEventArgs());
current = ServiceState.Paused;
break;
case ServiceState.ContinuePending:
CallWaitEventHandler(pendingEventHandler, new PendingOperationEventArgs());
current = ServiceState.Running;
break;
case ServiceState.StopPending:
CallWaitEventHandler(pendingEventHandler, new PendingOperationEventArgs());
current = ServiceState.Stopped;
break;
default:
continue;
}
lock (serviceLock)
{
// set and report the new status
status.CurrentState = current;
status.CheckPoint = 0;
status.WaitHint = 0;
SetAcceptedControlsAndReportStatusWithinLock();
// quit if there's nothing left to do
if (current == ServiceState.Stopped)
break;
}
}
while (serviceMainSignal.WaitOne());
}
/// <summary>
/// Gets the native status handle.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public static IntPtr StatusHandle { get { return statusHandle; } }
/// <summary>
/// Registers the service to receive power setting notifications for the specific power setting event.
/// </summary>
/// <param name="powerSettingGuid">The unique power setting identifier.</param>
/// <exception cref="InvalidOperationException">If the service hasn't been started yet.</exception>
/// <exception cref="PlatformNotSupportedException">On Windows Server 2003 and Windows XP.</exception>
public static void RegisterPowerSettingNotification(Guid powerSettingGuid)
{
powerSettingNotifications.Register(powerSettingGuid, _ => _, RegisterPowerSettingNotification, new Version(6, 0));
}
/// <summary>
/// Unregisters the power setting notification.
/// </summary>
/// <param name="powerSettingGuid">The unique power setting identifier.</param>
/// <returns><c>true</c> if the last notification was successfully unregistered, <c>false</c> otherwise.</returns>
public static bool UnregisterPowerSettingNotification(Guid powerSettingGuid)
{
return powerSettingNotifications.Unregister(powerSettingGuid, UnregisterPowerSettingNotification);
}
/// <summary>
/// Registers the device handle for which the service will receive notifications.
/// </summary>
/// <param name="deviceHandle">A handle to the device.</param>
/// <exception cref="InvalidOperationException">If the service hasn't been started yet.</exception>
public static void RegisterDeviceHandleNotification(IntPtr deviceHandle)
{
deviceHandleNotifications.Register
(
deviceHandle,
handle => new DEV_BROADCAST_HANDLE()
{
dbcc_size = Marshal.SizeOf(typeof(DEV_BROADCAST_HANDLE)),
dbcc_devicetype = (int)DeviceEventDeviceType.Handle,
dbch_handle = handle
},
RegisterDeviceNotification
);
}
/// <summary>
/// Unregisters the device handle notification.
/// </summary>
/// <param name="deviceHandle">A handle to the device.</param>
/// <returns><c>true</c> if the last notification was successfully unregistered, <c>false</c> otherwise.</returns>
public static bool UnregisterDeviceHandleNotification(IntPtr deviceHandle)
{
return deviceHandleNotifications.Unregister(deviceHandle, UnregisterDeviceNotification);
}
/// <summary>
/// Logs the given event.
/// </summary>
/// <param name="type">The severity of the event.</param>
/// <param name="message">A <see cref="string"/> describing the event.</param>
/// <exception cref="InvalidOperationException">If <see cref="ServiceApplication.Run"/> hasn't been called yet.</exception>
/// <exception cref="InvalidEnumArgumentException">If <paramref name="type"/> is invalid.</exception>
/// <exception cref="ArgumentNullException">If <paramref name="message"/> is <c>null</c>.</exception>
/// <exception cref="StackOverflowException"/>
/// <exception cref="OutOfMemoryException"/>
/// <exception cref="ThreadAbortException"/>
public static void LogEvent(EventLogEntryType type, string message)
{
// check the input
CheckEnum("type", type);
if (message == null)
throw new ArgumentNullException("message");
// check if initialized
if (!initialized)
throw new InvalidOperationException();
// ellipse the message if necessary
const string ellipse = "...";
const int maxLen = 0x7ffe;
if (message.Length > maxLen)
message = message.Substring(0, maxLen - ellipse.Length) + ellipse;
#if DEBUG
// determine the color
var color = ConsoleColor.Green;
switch (type)
{
case EventLogEntryType.FailureAudit:
color = ConsoleColor.Red;
goto case EventLogEntryType.SuccessAudit;
case EventLogEntryType.SuccessAudit:
message = "[AUDIT] " + message;
break;
case EventLogEntryType.Error:
color = ConsoleColor.Red;
break;
case EventLogEntryType.Warning:
color = ConsoleColor.Yellow;
break;
}
#endif
// always catch and swallow any non-fatal exceptions
try
{
#if DEBUG
DebugWriteLine(message, color);
#else
log.WriteEntry(message, type);
#endif
}
catch (StackOverflowException) { throw; }
catch (OutOfMemoryException) { throw; }
catch (ThreadAbortException) { throw; }
catch { }
}
/// <summary>
/// Logs the given event after formatting its input string.
/// </summary>
/// <param name="type">The severity of the event.</param>
/// <param name="format">The input format.</param>
/// <param name="args">All parameters referenced in the <paramref name="format"/> string.</param>
/// <exception cref="FormatException"><paramref name="format"/> is invalid or the index of a format item is less than zero, or greater than or equal to the length of the <paramref name="args"/> array.</exception>
/// <exception cref="InvalidOperationException">If <see cref="ServiceApplication.Run"/> hasn't been called yet.</exception>
/// <exception cref="StackOverflowException"/>
/// <exception cref="OutOfMemoryException"/>
/// <exception cref="ThreadAbortException"/>
public static void LogEvent(EventLogEntryType type, string format, params object[] args)
{
LogEvent(type, string.Format(format, args));
}
/// <summary>
/// Initializes the service and forwards control to the service dispatcher.
/// </summary>
/// <param name="serviceName">The optional custom name for this service. If omitted, the entry <see cref="Assembly"/>'s name will be used. Also see the remarks.</param>
/// <exception cref="InvalidOperationException">This method has already been called.</exception>
/// <remarks>
/// If the service is registered as own-process (which it has to be), <paramref name="serviceName"/> is ignored by Windows and only used for logging.
/// All event handlers must be hooked up before this method is called.
/// </remarks>
public static void Run(string serviceName = null)
{
// acquire the initialize lock
lock (initializeLock)
{
// make sure this function hasn't already been called
if (initialized)
throw new InvalidOperationException();
// if exit was called, bail
if (exitPrematurely)
return;
// prepare the service name
mainServiceName = serviceName ?? Assembly.GetEntryAssembly().GetName().Name;
#if DEBUG
// clear the console
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.White;
Console.Clear();
Console.TreatControlCAsInput = true;
#else
// open (and create if necessary) the event log soure
const string logName = "Application";
if (!EventLog.SourceExists(mainServiceName))
EventLog.CreateEventSource(mainServiceName, logName);
log = new EventLog(logName, ".", mainServiceName);
#endif
// set the initialize flag
initialized = true;
}
// log all unhandled exceptions
AppDomain.CurrentDomain.UnhandledException += (sender, e) => LogEvent(EventLogEntryType.Error, e.ExceptionObject.ToString());
// run the service dispatcher
if
(
!StartServiceCtrlDispatcher
(
new SERVICE_TABLE_ENTRY[]
{
new SERVICE_TABLE_ENTRY(mainServiceName, ServiceMain),
new SERVICE_TABLE_ENTRY(null, null)
}
)
)
throw new Win32Exception();
}
/// <summary>
/// Shuts down the <see cref="ServiceApplication"/> in a controlled fashion.
/// </summary>
/// <param name="exitCode">An optional Win32 error code.</param>
/// <remarks>
/// This will not trigger Windows fault handling, even if <paramref name="exitCode"/> isn't <c>0</c>.
/// </remarks>
public static void Exit(int exitCode = 0)
{
// if the service has not been started yet, shutdown the entire runtime
lock (initializeLock)
{
if (!initialized)
{
exitPrematurely = true;
Environment.ExitCode = exitCode;
}
}
// otherwise set the exit code and tell the service main to quit
status.Win32ExitCode = exitCode;
quitServiceMain = true;
serviceMainSignal.Set();
}
/// <summary>
/// Occurs when the service is resumed.
/// </summary>
public static event EventHandler<PendingOperationEventArgs> Continue
{
add { AddEventHandler(ref continueHandler, value); }
remove { RemoveEventHandler(ref continueHandler, value); }
}
/// <summary>
/// Occurs when a networking binding has changed.
/// </summary>
public static event EventHandler<NetworkBindingChangedEventArgs> NetworkBindingChanged
{
add { AddEventHandler(ref networkBindingChangedHandler, value); }
remove { RemoveEventHandler(ref networkBindingChangedHandler, value); }
}
/// <summary>
/// Occurs when the service's service-specific startup parameters have changed.
/// </summary>
public static event EventHandler<EventArgs> ParametersChanged
{
add { AddEventHandler(ref parametersChangedHandler, value); }
remove { RemoveEventHandler(ref parametersChangedHandler, value); }
}
/// <summary>
/// Occurs the service is suspended.
/// </summary>
public static event EventHandler<PendingOperationEventArgs> Pause
{
add { AddEventHandler(ref pauseHandler, value); }
remove { RemoveEventHandler(ref pauseHandler, value); }
}
/// <summary>
/// Occurs when the system will be shutting down.
/// Services that need additional time to perform cleanup tasks beyond the tight time restriction at system shutdown can use this notification.
/// </summary>
/// <exception cref="PlatformNotSupportedException">On Windows Server 2003 and Windows XP.</exception>
/// <exception cref="InvalidOperationException">If <see cref="ServiceApplication.Shutdown"/> has already been registered.</exception>
public static event EventHandler<PendingOperationEventArgs> Preshutdown
{
add
{
lock (serviceLock)
{
if (shutdownHandler != null)
throw new InvalidOperationException();
AddEventHandler(ref preshutdownHandler, value, new Version(6, 0));
}
}
remove { RemoveEventHandler(ref preshutdownHandler, value); }
}
/// <summary>
/// Occurs when the machine is about to shut down.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="ServiceApplication.Preshutdown"/> has already been registered.</exception>
public static event EventHandler<PendingOperationEventArgs> Shutdown
{
add
{
lock (serviceLock)
{
if (preshutdownHandler != null)
throw new InvalidOperationException();
AddEventHandler(ref shutdownHandler, value);
}
}
remove { RemoveEventHandler(ref shutdownHandler, value); }
}
/// <summary>
/// Occurs when the service should be stopped.
/// </summary>
public static event EventHandler<PendingOperationEventArgs> Stop
{
add { AddEventHandler(ref stopHandler, value); }
remove { RemoveEventHandler(ref stopHandler, value); }
}
/// <summary>
/// Occurs when a device is plugged in or unplugged.
/// </summary>
public static event EventHandler<DeviceEventArgs> DeviceEvent
{
add { AddEventHandler(ref deviceEventHandler, value); }
remove { RemoveEventHandler(ref deviceEventHandler, value); }
}
/// <summary>
/// Occurs when the computer's hardware profile has changed.
/// </summary>
public static event EventHandler<HardwareProfileEventArgs> HardwareProfileEvent
{
add { AddEventHandler(ref hardwareProfileEventHandler, value); }
remove { RemoveEventHandler(ref hardwareProfileEventHandler, value); }
}
/// <summary>
/// Occurs when the machine is suspended or resumed.
/// </summary>
public static event EventHandler<PowerEventArgs> PowerEvent
{
add { AddEventHandler(ref powerEventHandler, value); }
remove { RemoveEventHandler(ref powerEventHandler, value); }
}
/// <summary>
/// Occurs when a users logs on or off.
/// </summary>
public static event EventHandler<SessionChangedEventArgs> SessionChanged
{
add { AddEventHandler(ref sessionChangedHandler, value); }
remove { RemoveEventHandler(ref sessionChangedHandler, value); }
}
/// <summary>
/// Occurs when the system time has changed.
/// </summary>
/// <exception cref="PlatformNotSupportedException">On Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP.</exception>
public static event EventHandler<TimeChangedEventArgs> TimeChanged
{
add { AddEventHandler(ref timeChangedHandler, value, new Version(6, 1)); }
remove { RemoveEventHandler(ref timeChangedHandler, value); }
}
/// <summary>
/// Occurs when a service trigger event that the event has occurred.
/// </summary>
/// <exception cref="PlatformNotSupportedException">On Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP.</exception>
public static event EventHandler<EventArgs> TriggerEvent
{
add { AddEventHandler(ref triggerEventHandler, value, new Version(6, 1)); }
remove { RemoveEventHandler(ref triggerEventHandler, value); }
}
/// <summary>
/// Occurs when the user has initiated a reboot.
/// </summary>
/// <exception cref="PlatformNotSupportedException">On Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP.</exception>
public static event EventHandler<EventArgs> UserModeReboot
{
add { AddEventHandler(ref userModeRebootHandler, value, new Version(6, 2)); }
remove { RemoveEventHandler(ref userModeRebootHandler, value); }
}
/// <summary>
/// Occurs when an application sends a custom command to the service.
/// </summary>
public static event EventHandler<CustomCommandEventArgs> CustomCommand
{
add { AddEventHandler(ref customCommandHandler, value); }
remove { RemoveEventHandler(ref customCommandHandler, value); }
}
/// <summary>
/// Occurs after the service has been started.
/// </summary>
/// <exception cref="InvalidOperationException">If <see cref="ServiceApplication.Run"/> has already been called.</exception>
public static event EventHandler<StartEventArgs> Start
{
add
{
lock (initializeLock)
{
if (initialized)
throw new InvalidOperationException();
startHandler += value;
}
}
remove
{
lock (initializeLock)
{
if (initialized)
throw new InvalidOperationException();
startHandler -= value;
}
}
}
/// <summary>
/// Occurs after an exception has been thrown.
/// </summary>
public static event EventHandler<ThreadExceptionEventArgs> ThreadException;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment