Skip to content

Commit

Permalink
Adds the ability to set the type of action to peform after the idle t…
Browse files Browse the repository at this point in the history
…imeout. Supports logoff, reboot, and shutdown actions
  • Loading branch information
ryannewington committed Aug 12, 2017
1 parent d131260 commit f6f80d5
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/Lithnet.IdleLogoff/EventLogging.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
15 changes: 15 additions & 0 deletions src/Lithnet.IdleLogoff/IdleTimeoutAction.cs
Original file line number Diff line number Diff line change
@@ -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,
}
}
96 changes: 94 additions & 2 deletions src/Lithnet.IdleLogoff/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@
<string id="CAT_300017F2_090D_4E59_A535_01350C2E0428">Lithnet</string>
<string id="CAT_78275968_599A_4E3F_BAD6_07C70894AF09">IdleLogoff</string>
<string id="POL_978C1C26_0621_4860_A000_61A23A440151">Log off inactive users</string>
<string id="POL_F3930A2B-33F8-4CE6-B6AC-14F188507B77">Log off</string>
<string id="POL_D61A5346-B53C-42FB-99FF-6519F18B281C">Reboot</string>
<string id="POL_582D5654-4165-42D2-BF83-6A182FF67587">Shutdown</string>
<string id="SUPPORTED_WindowsVISTA">At least Microsoft Windows</string>
<string id="TS_SUPPORTED_WindowsXP">At least Microsoft Windows XP</string>
</stringTable>
<presentationTable>
<presentation id="POL_978C1C26_0621_4860_A000_61A23A440151">
<decimalTextBox refId="DXT_7E914D9D_203C_40DF_8C49_93B29308A6B2" defaultValue="60">Idle timeout (minutes)</decimalTextBox>
<checkBox refId="DXT_B357DAA7-0013-45D9-94DC-E91206837C29" defaultChecked="false">Ignore sleep prevention requests from applications such as media playback</checkBox>
<dropdownList refId="DXT_3E1833A6-D761-40B5-8BF9-5C03F320C86B" defaultItem ="0">Action</dropdownList>
</presentation>
<presentation id="POL_1D519699-D182-4B28-A489-1F7DE9DB6DE5">
<decimalTextBox refId="DXT_B29D2480-E131-4C23-B4D4-F55CAA13C2FF" defaultValue="60">Idle timeout (minutes)</decimalTextBox>
<checkBox refId="DXT_CFF2EAC7-2034-47F9-B3CE-8D10C73FCA81" defaultChecked="false">Ignore sleep prevention requests from applications such as media playback</checkBox>
<dropdownList refId="DXT_6C79F5D5-6DB7-4194-BCB5-7CF6752263EA" defaultItem ="0">Action</dropdownList>
</presentation>
</presentationTable>
</resources>
Expand Down
37 changes: 36 additions & 1 deletion src/Lithnet.IdleLogoff/PolicyDefinitions/lithnet.idlelogoff.admx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@
<decimal value="0" />
</falseValue>
</boolean>
<enum id="DXT_3E1833A6-D761-40B5-8BF9-5C03F320C86B" key="Software\Policies\Lithnet\IdleLogoff" valueName="Action">
<item displayName="$(string.POL_F3930A2B-33F8-4CE6-B6AC-14F188507B77)">
<value>
<decimal value="0" />
</value>
</item>
<item displayName="$(string.POL_D61A5346-B53C-42FB-99FF-6519F18B281C)">
<value>
<decimal value="1" />
</value>
</item>
<item displayName="$(string.POL_582D5654-4165-42D2-BF83-6A182FF67587)">
<value>
<decimal value="2" />
</value>
</item>
</enum>
</elements>
</policy>
<policy name="POL_1D519699-D182-4B28-A489-1F7DE9DB6DE5" class="User" displayName="$(string.POL_978C1C26_0621_4860_A000_61A23A440151)" presentation="$(presentation.POL_1D519699-D182-4B28-A489-1F7DE9DB6DE5)" key="Software\Policies\Lithnet\IdleLogoff" valueName="Enabled">
Expand All @@ -45,7 +62,25 @@
<falseValue>
<decimal value="0" />
</falseValue>
</boolean> </elements>
</boolean>
<enum id="DXT_6C79F5D5-6DB7-4194-BCB5-7CF6752263EA" key="Software\Policies\Lithnet\IdleLogoff" valueName="Action">
<item displayName="$(string.POL_F3930A2B-33F8-4CE6-B6AC-14F188507B77)">
<value>
<decimal value="0" />
</value>
</item>
<item displayName="$(string.POL_D61A5346-B53C-42FB-99FF-6519F18B281C)">
<value>
<decimal value="1" />
</value>
</item>
<item displayName="$(string.POL_582D5654-4165-42D2-BF83-6A182FF67587)">
<value>
<decimal value="2" />
</value>
</item>
</enum>
</elements>
</policy>
</policies>
</policyDefinitions>
10 changes: 5 additions & 5 deletions src/Lithnet.IdleLogoff/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}

Expand All @@ -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)
{
Expand All @@ -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
Expand Down
27 changes: 27 additions & 0 deletions src/Lithnet.IdleLogoff/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/Lithnet.IdleLogoff/TokenPrivileges.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit f6f80d5

Please sign in to comment.