diff --git a/src/Lithnet.IdleLogoff/EventLogging.cs b/src/Lithnet.IdleLogoff/EventLogging.cs
index 350137a..6a0ccc7 100644
--- a/src/Lithnet.IdleLogoff/EventLogging.cs
+++ b/src/Lithnet.IdleLogoff/EventLogging.cs
@@ -10,6 +10,7 @@ public class EventLogging
internal const int EVT_LOGOFFEVENT = 3;
internal const int EVT_TIMERINTERVALCHANGED = 4;
internal const int EVT_LOGOFFFAILED = 5;
+ internal const int EVT_RESTARTFAILED = 6;
internal static string evtSource = "Lithnet.idlelogoff";
internal static bool LogEnabled = true;
diff --git a/src/Lithnet.IdleLogoff/IdleTimeoutAction.cs b/src/Lithnet.IdleLogoff/IdleTimeoutAction.cs
new file mode 100644
index 0000000..a6f59be
--- /dev/null
+++ b/src/Lithnet.IdleLogoff/IdleTimeoutAction.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Lithnet.idlelogoff
+{
+ public enum IdleTimeoutAction
+ {
+ Logoff = 0,
+ Reboot = 1,
+ Shutdown = 2,
+ }
+}
diff --git a/src/Lithnet.IdleLogoff/NativeMethods.cs b/src/Lithnet.IdleLogoff/NativeMethods.cs
index f2e06e1..ad926aa 100644
--- a/src/Lithnet.IdleLogoff/NativeMethods.cs
+++ b/src/Lithnet.IdleLogoff/NativeMethods.cs
@@ -6,6 +6,11 @@ namespace Lithnet.idlelogoff
{
public class NativeMethods
{
+ internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
+ internal const int TOKEN_QUERY = 0x00000008;
+ internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
+ internal const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
+
[DllImport("powrprof.dll")]
private static extern int CallNtPowerInformation(
POWER_INFORMATION_LEVEL informationLevel,
@@ -15,12 +20,28 @@ private static extern int CallNtPowerInformation(
int nOutputBufferSize
);
+
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr GetCurrentProcess();
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ private static extern int OpenProcessToken(IntPtr processHandle, int desiredAccess, ref IntPtr tokenHandle);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ private static extern int LookupPrivilegeValue(string systemName, string name, ref long luid);
+
+ [DllImport("advapi32.dll", SetLastError = true)]
+ private static extern int AdjustTokenPrivileges(IntPtr tokenHandle, bool disableAllPrivileges, ref TokenPrivileges newState, int bufferLength, IntPtr previousState, IntPtr length);
+
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetLastInputInfo(ref LastInputInfo lastInputInfo);
[DllImport("User32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool ExitWindowsEx(ShutdownFlags flags, int reason);
+ [DllImport("kernel32", SetLastError = true)]
+ private static extern bool CloseHandle(IntPtr handle);
+
public static bool IsDisplayRequested()
{
EXECUTION_STATE state;
@@ -50,12 +71,83 @@ public static int GetLastInputTime()
public static void LogOffUser()
{
- ShutdownFlags flags = ShutdownFlags.Logoff | ShutdownFlags.Force;
-
+ ShutdownFlags flags;
+ bool elevated = false;
+
+ if (Settings.Action == IdleTimeoutAction.Reboot || Settings.Action == IdleTimeoutAction.Shutdown)
+ {
+ try
+ {
+ ElevatePrivileges();
+ elevated = true;
+ }
+ catch (Exception ex)
+ {
+ EventLogging.TryLogEvent($"Could not get workstation shutdown permissions. Logging off instead\n{ex}", EventLogging.EVT_RESTARTFAILED, System.Diagnostics.EventLogEntryType.Error);
+ }
+ }
+
+ if (elevated && Settings.Action == IdleTimeoutAction.Shutdown)
+ {
+ flags = ShutdownFlags.Shutdown | ShutdownFlags.Force;
+ }
+ else if (elevated && Settings.Action == IdleTimeoutAction.Reboot)
+ {
+ flags = ShutdownFlags.Reboot | ShutdownFlags.Force;
+ }
+ else {
+ flags = ShutdownFlags.Logoff | ShutdownFlags.Force;
+ }
+
if (!ExitWindowsEx(flags, 0))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
+
+ private static void ElevatePrivileges()
+ {
+ IntPtr currentProcess = GetCurrentProcess();
+ IntPtr tokenHandle = IntPtr.Zero;
+
+ try
+ {
+ int result = OpenProcessToken(currentProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref tokenHandle);
+
+ if (result == 0)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ TokenPrivileges tokenPrivileges;
+ tokenPrivileges.PrivilegeCount = 1;
+ tokenPrivileges.Luid = 0;
+ tokenPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
+
+ result = LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tokenPrivileges.Luid);
+ if (result == 0)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ result = AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero);
+ if (result == 0)
+ {
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+ }
+ finally
+ {
+ if (currentProcess != IntPtr.Zero)
+ {
+ CloseHandle(currentProcess);
+ }
+
+ if (tokenHandle != IntPtr.Zero)
+ {
+ CloseHandle(tokenHandle);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Lithnet.IdleLogoff/PolicyDefinitions/en-US/lithnet.idlelogoff.adml b/src/Lithnet.IdleLogoff/PolicyDefinitions/en-US/lithnet.idlelogoff.adml
index d51fd60..0655c91 100644
--- a/src/Lithnet.IdleLogoff/PolicyDefinitions/en-US/lithnet.idlelogoff.adml
+++ b/src/Lithnet.IdleLogoff/PolicyDefinitions/en-US/lithnet.idlelogoff.adml
@@ -8,6 +8,9 @@
Lithnet
IdleLogoff
Log off inactive users
+ Log off
+ Reboot
+ Shutdown
At least Microsoft Windows
At least Microsoft Windows XP
@@ -15,10 +18,12 @@
Idle timeout (minutes)
Ignore sleep prevention requests from applications such as media playback
+ Action
Idle timeout (minutes)
Ignore sleep prevention requests from applications such as media playback
+ Action
diff --git a/src/Lithnet.IdleLogoff/PolicyDefinitions/lithnet.idlelogoff.admx b/src/Lithnet.IdleLogoff/PolicyDefinitions/lithnet.idlelogoff.admx
index c2c6e01..d2443ae 100644
--- a/src/Lithnet.IdleLogoff/PolicyDefinitions/lithnet.idlelogoff.admx
+++ b/src/Lithnet.IdleLogoff/PolicyDefinitions/lithnet.idlelogoff.admx
@@ -31,6 +31,23 @@
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
@@ -45,7 +62,25 @@
-
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Lithnet.IdleLogoff/Program.cs b/src/Lithnet.IdleLogoff/Program.cs
index ca250cd..ca143af 100644
--- a/src/Lithnet.IdleLogoff/Program.cs
+++ b/src/Lithnet.IdleLogoff/Program.cs
@@ -44,11 +44,11 @@ private static void InitTimer()
if (Settings.Enabled)
{
- EventLogging.TryLogEvent("The application has started. User " + Environment.UserDomainName + "\\" + Environment.UserName + " will be logged off after being idle for " + Settings.IdleLimit + " minutes", EventLogging.EVT_TIMERSTARTED);
+ EventLogging.TryLogEvent($"The application has started. {Settings.Action} will be performed for user {Environment.UserDomainName}\\{Environment.UserName} after being idle for {Settings.IdleLimit} minutes", EventLogging.EVT_TIMERSTARTED);
}
else
{
- EventLogging.TryLogEvent("The application has started, but is not enabled. User " + Environment.UserDomainName + "\\" + Environment.UserName + " will not be logged off automatically", EventLogging.EVT_TIMERSTARTED);
+ EventLogging.TryLogEvent($"The application has started, but is not enabled. User {Environment.UserDomainName}\\{Environment.UserName} will not be logged off automatically", EventLogging.EVT_TIMERSTARTED);
}
Program.eventTimer = new Timer();
@@ -122,7 +122,7 @@ private static void EventTimer_Tick(object sender, EventArgs e)
if (initialTime != logoffidletime)
{
- EventLogging.TryLogEvent($"Idle timeout limit has changed. User {Environment.UserDomainName}\\{Environment.UserName} will now be logged off after {Settings.IdleLimit} minutes", EventLogging.EVT_TIMERINTERVALCHANGED);
+ EventLogging.TryLogEvent($"Idle timeout limit has changed. {Settings.Action} will be performed for user {Environment.UserDomainName}\\{Environment.UserName} after {Settings.IdleLimit} minutes", EventLogging.EVT_TIMERINTERVALCHANGED);
initialTime = logoffidletime;
}
@@ -147,7 +147,7 @@ private static void EventTimer_Tick(object sender, EventArgs e)
if (DateTime.Now.Subtract(Program.lastDateTime).TotalMilliseconds > logoffidletime)
{
- EventLogging.TryLogEvent($"User {Environment.UserName} has passed the idle time limit of {Settings.IdleLimit} minutes. Initiating logoff.", EventLogging.EVT_LOGOFFEVENT);
+ EventLogging.TryLogEvent($"User {Environment.UserName} has passed the idle time limit of {Settings.IdleLimit} minutes. Initiating {Settings.Action}.", EventLogging.EVT_LOGOFFEVENT);
if (!Settings.Debug)
{
@@ -160,7 +160,7 @@ private static void EventTimer_Tick(object sender, EventArgs e)
}
catch (Exception ex)
{
- EventLogging.TryLogEvent("An error occurred trying to log off the user\n" + ex.Message, EventLogging.EVT_LOGOFFFAILED);
+ EventLogging.TryLogEvent($"An error occurred trying to perform the {Settings.Action} operation\n" + ex.Message, EventLogging.EVT_LOGOFFFAILED);
}
}
else
diff --git a/src/Lithnet.IdleLogoff/Settings.cs b/src/Lithnet.IdleLogoff/Settings.cs
index 3524d63..a1c96a6 100644
--- a/src/Lithnet.IdleLogoff/Settings.cs
+++ b/src/Lithnet.IdleLogoff/Settings.cs
@@ -289,6 +289,33 @@ public static bool Enabled
}
}
+ public static IdleTimeoutAction Action
+ {
+ get
+ {
+ int retval = 0;
+
+ object regvalue = Settings.GetPolicyOrSetting("Action");
+ if (regvalue != null)
+ {
+ try
+ {
+ retval = (int)regvalue;
+ }
+ catch
+ {
+ //unable to cast from an object to a string
+ }
+ }
+
+ return (IdleTimeoutAction)retval;
+ }
+ set
+ {
+ SaveSetting("Action", (int)value, RegistryValueKind.DWord);
+ }
+ }
+
public static bool IgnoreDisplayRequested
{
get
diff --git a/src/Lithnet.IdleLogoff/TokenPrivileges.cs b/src/Lithnet.IdleLogoff/TokenPrivileges.cs
new file mode 100644
index 0000000..62f6130
--- /dev/null
+++ b/src/Lithnet.IdleLogoff/TokenPrivileges.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Lithnet.idlelogoff
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct TokenPrivileges
+ {
+ public int PrivilegeCount;
+ public long Luid;
+ public int Attributes;
+ }
+}
diff --git a/src/Lithnet.IdleLogoff/frmSettings.Designer.cs b/src/Lithnet.IdleLogoff/frmSettings.Designer.cs
index 16fc202..08d5518 100644
--- a/src/Lithnet.IdleLogoff/frmSettings.Designer.cs
+++ b/src/Lithnet.IdleLogoff/frmSettings.Designer.cs
@@ -38,6 +38,8 @@ private void InitializeComponent()
this.lbGPControlled = new System.Windows.Forms.Label();
this.lbHiddenRefresh = new System.Windows.Forms.Label();
this.ckIgnoreDisplayRequested = new System.Windows.Forms.CheckBox();
+ this.cbAction = new System.Windows.Forms.ComboBox();
+ this.label2 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.udMinutes)).BeginInit();
this.SuspendLayout();
//
@@ -84,7 +86,7 @@ private void InitializeComponent()
//
// btOK
//
- this.btOK.Location = new System.Drawing.Point(344, 142);
+ this.btOK.Location = new System.Drawing.Point(344, 181);
this.btOK.Name = "btOK";
this.btOK.Size = new System.Drawing.Size(71, 24);
this.btOK.TabIndex = 4;
@@ -95,7 +97,7 @@ private void InitializeComponent()
// btCancel
//
this.btCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
- this.btCancel.Location = new System.Drawing.Point(267, 142);
+ this.btCancel.Location = new System.Drawing.Point(267, 181);
this.btCancel.Name = "btCancel";
this.btCancel.Size = new System.Drawing.Size(71, 24);
this.btCancel.TabIndex = 3;
@@ -106,7 +108,7 @@ private void InitializeComponent()
// lbProductName
//
this.lbProductName.AutoSize = true;
- this.lbProductName.Location = new System.Drawing.Point(7, 144);
+ this.lbProductName.Location = new System.Drawing.Point(7, 183);
this.lbProductName.Name = "lbProductName";
this.lbProductName.Size = new System.Drawing.Size(99, 13);
this.lbProductName.TabIndex = 4;
@@ -114,7 +116,7 @@ private void InitializeComponent()
//
// lbProductVersion
//
- this.lbProductVersion.Location = new System.Drawing.Point(6, 158);
+ this.lbProductVersion.Location = new System.Drawing.Point(7, 196);
this.lbProductVersion.Name = "lbProductVersion";
this.lbProductVersion.Size = new System.Drawing.Size(119, 13);
this.lbProductVersion.TabIndex = 5;
@@ -147,13 +149,33 @@ private void InitializeComponent()
this.ckIgnoreDisplayRequested.Text = "Ignore sleep prevention requests from applications such as media playback";
this.ckIgnoreDisplayRequested.UseVisualStyleBackColor = true;
//
+ // cbAction
+ //
+ this.cbAction.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cbAction.FormattingEnabled = true;
+ this.cbAction.Location = new System.Drawing.Point(154, 131);
+ this.cbAction.Name = "cbAction";
+ this.cbAction.Size = new System.Drawing.Size(166, 21);
+ this.cbAction.TabIndex = 10;
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(97, 131);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(40, 13);
+ this.label2.TabIndex = 11;
+ this.label2.Text = "Action";
+ //
// frmSettings
//
this.AcceptButton = this.btOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btCancel;
- this.ClientSize = new System.Drawing.Size(427, 178);
+ this.ClientSize = new System.Drawing.Size(427, 217);
+ this.Controls.Add(this.label2);
+ this.Controls.Add(this.cbAction);
this.Controls.Add(this.ckIgnoreDisplayRequested);
this.Controls.Add(this.lbHiddenRefresh);
this.Controls.Add(this.lbGPControlled);
@@ -189,5 +211,7 @@ private void InitializeComponent()
private System.Windows.Forms.Label lbGPControlled;
private System.Windows.Forms.Label lbHiddenRefresh;
private System.Windows.Forms.CheckBox ckIgnoreDisplayRequested;
+ private System.Windows.Forms.ComboBox cbAction;
+ private System.Windows.Forms.Label label2;
}
}
\ No newline at end of file
diff --git a/src/Lithnet.IdleLogoff/frmSettings.cs b/src/Lithnet.IdleLogoff/frmSettings.cs
index b53ecc5..ec75cb5 100644
--- a/src/Lithnet.IdleLogoff/frmSettings.cs
+++ b/src/Lithnet.IdleLogoff/frmSettings.cs
@@ -7,41 +7,49 @@ public partial class frmSettings : Form
{
public frmSettings()
{
- InitializeComponent();
+ this.InitializeComponent();
}
private void frmSettings_Load(object sender, EventArgs e)
{
-
- RefreshUI();
+ this.RefreshUI();
}
private void RefreshUI()
{
+ this.lbProductName.Text = "Lithnet.idlelogoff";
+ this.lbProductVersion.Text = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
+
+ this.ckEnableIdleLogoff.Checked = Settings.Enabled;
+ this.ckEnableIdleLogoff.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.Enabled));
- lbProductName.Text = "Lithnet.idlelogoff";
- lbProductVersion.Text = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
+ this.ckIgnoreDisplayRequested.Checked = Settings.IgnoreDisplayRequested;
+ this.ckIgnoreDisplayRequested.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.IgnoreDisplayRequested));
- ckEnableIdleLogoff.Checked = Settings.Enabled;
- ckEnableIdleLogoff.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.Enabled));
+ this.cbAction.Items.Clear();
+
+ foreach (string item in Enum.GetNames(typeof(IdleTimeoutAction)))
+ {
+ this.cbAction.Items.Add(item);
+ }
- ckIgnoreDisplayRequested.Checked = Settings.IgnoreDisplayRequested;
- ckIgnoreDisplayRequested.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.IgnoreDisplayRequested));
+ this.cbAction.SelectedItem = Settings.Action.ToString();
+ this.cbAction.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.Action));
try
{
- udMinutes.Value = Settings.IdleLimit;
+ this.udMinutes.Value = Settings.IdleLimit;
}
catch
{
- udMinutes.Value = 60;
+ this.udMinutes.Value = 60;
}
- udMinutes.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.IdleLimit));
+ this.udMinutes.Enabled = !Settings.IsSettingFromPolicy(nameof(Settings.IdleLimit));
- if (!udMinutes.Enabled | !ckEnableIdleLogoff.Enabled | !ckIgnoreDisplayRequested.Enabled)
+ if (!this.udMinutes.Enabled | !this.ckEnableIdleLogoff.Enabled | !this.ckIgnoreDisplayRequested.Enabled)
{
- lbGPControlled.Visible = true;
+ this.lbGPControlled.Visible = true;
}
}
@@ -49,19 +57,24 @@ private void btOK_Click(object sender, EventArgs e)
{
try
{
- if (ckEnableIdleLogoff.Enabled)
+ if (this.ckEnableIdleLogoff.Enabled)
+ {
+ Settings.Enabled = this.ckEnableIdleLogoff.Checked;
+ }
+
+ if (this.udMinutes.Enabled)
{
- Settings.Enabled = ckEnableIdleLogoff.Checked;
+ Settings.IdleLimit = (int) this.udMinutes.Value;
}
- if (udMinutes.Enabled)
+ if (this.ckIgnoreDisplayRequested.Enabled)
{
- Settings.IdleLimit = (int) udMinutes.Value;
+ Settings.IgnoreDisplayRequested = this.ckIgnoreDisplayRequested.Checked;
}
- if (ckIgnoreDisplayRequested.Enabled)
+ if (this.cbAction.Enabled)
{
- Settings.IgnoreDisplayRequested = ckIgnoreDisplayRequested.Checked;
+ Settings.Action = (IdleTimeoutAction)Enum.Parse(typeof(IdleTimeoutAction), (string)this.cbAction.SelectedItem, true);
}
}
catch (Exception ex)
@@ -96,7 +109,7 @@ private void btOK_Click(object sender, EventArgs e)
private void lbHiddenRefresh_Click(object sender, EventArgs e)
{
- RefreshUI();
+ this.RefreshUI();
}
private void btCancel_Click(object sender, EventArgs e)
diff --git a/src/Lithnet.IdleLogoff/lithnet.idlelogoff.csproj b/src/Lithnet.IdleLogoff/lithnet.idlelogoff.csproj
index a9c6bba..7e35364 100644
--- a/src/Lithnet.IdleLogoff/lithnet.idlelogoff.csproj
+++ b/src/Lithnet.IdleLogoff/lithnet.idlelogoff.csproj
@@ -97,6 +97,7 @@
+
@@ -111,6 +112,7 @@
+
@@ -145,6 +147,9 @@
+
+ "C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe" sign /sha1 5ce168989b1359309dddee460c10e96fa60fa78b /t http://time.certum.pl /v $(TargetFileName)
+