Automatic logoff at schedule

Shaun VermaakCOG Lead Engineer
CERTIFIED EXPERT
My name is Shaun Vermaak and I have always been fascinated with technology and how we use it to enhance our lives and business.
Published:
Edited by: Andrew Leniart
Over time I have seen a number of questions asking how to logoff users at a specific time. I personally haven't required this but decided to develop a little Windows service that manages this via schedule and not a legacy scheduled task running shutdown /l or via AD logon hours

Introduction


For this project, I used the amazing TopShelf library to host code that essentially triggers the WTSLogoffSession API when the desired schedule occurs.


Setup


1) Download the AutomaticLogoff tool and extract its contents to a folder on a computer that you are planning to install the tool on. (I recommend extracting to C:\Program Files\AutomaticLogoff)


2) Run Configurator.exe (Configurator Editor). On the LogoffSchedules tab, specify the schedules when users should be logged off (+ or INS to add, - or DEL to delete, Enter or double-click to edit).


Please note: Configurator Editor is a generic config file editor I developed and for AutomaticLogoff tool there is no need to adjust anything on the Settings tab or the Encrypt tab. Key values are used internally and are automatically generated.


Here's an example of some valid schedules:

Every Weekday at 09:00 PM

Sundays on 21:00

Thursdays at 19:00:00



3) Run the following command to install the AutomaticLogoff service


AutomaticLogoff.exe install


The Code


using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace AutomaticLogoff
{
[StructLayout(LayoutKind.Sequential)]
internal struct WTS_SESSION_INFO
{
public int SessionID;
[MarshalAs(UnmanagedType.LPStr)]
public string pWinStationName;
public WTS_CONNECTSTATE_CLASS State;
}

internal enum WTS_CONNECTSTATE_CLASS
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}

internal enum WTS_INFO_CLASS
{
WTSInitialProgram,
WTSApplicationName,
WTSWorkingDirectory,
WTSOEMId,
WTSSessionId,
WTSUserName,
WTSWinStationName,
WTSDomainName,
WTSConnectState,
WTSClientBuildNumber,
WTSClientName,
WTSClientDirectory,
WTSClientProductId,
WTSClientHardwareId,
WTSClientAddress,
WTSClientDisplay,
WTSClientProtocolType,
WTSIdleTime,
WTSLogonTime,
WTSIncomingBytes,
WTSOutgoingBytes,
WTSIncomingFrames,
WTSOutgoingFrames,
WTSClientInfo,
WTSSessionInfo
}

internal class Sessions
{
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern bool WTSLogoffSession(IntPtr hServer, int SessionId, bool bWait);

[DllImport("Wtsapi32.dll")]
static extern bool WTSQuerySessionInformation(
System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);

[DllImport("wtsapi32.dll", SetLastError = true)]
static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

[DllImport("wtsapi32.dll")]
static extern void WTSCloseServer(IntPtr hServer);

[DllImport("wtsapi32.dll", SetLastError = true)]
static extern Int32 WTSEnumerateSessions(IntPtr hServer, [MarshalAs(UnmanagedType.U4)] Int32 Reserved, [MarshalAs(UnmanagedType.U4)] Int32 Version, ref IntPtr ppSessionInfo, [MarshalAs(UnmanagedType.U4)] ref Int32 pCount);

[DllImport("wtsapi32.dll")]
static extern void WTSFreeMemory(IntPtr pMemory);

internal static List<int> GetSessionIDs(IntPtr server)
{
List<int> sessionIds = new List<int>();
IntPtr buffer = IntPtr.Zero;
int count = 0;
int retval = WTSEnumerateSessions(server, 0, 1, ref buffer, ref count);
int dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int64 current = (int)buffer;

if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO));
current += dataSize;
sessionIds.Add(si.SessionID);
}
WTSFreeMemory(buffer);
}
return sessionIds;
}

internal static void LogoffAllUsers()
{
string input = string.Empty;
IntPtr server = WTSOpenServer(Environment.MachineName);

try
{
foreach (int sessionID in GetSessionIDs(server))
{
WTSLogoffSession(server, sessionID, true);
}
}
finally
{
WTSCloseServer(server);
}
}
}
}


Conclusion


You can do this installation, change the startup state etc. via Group Policy Preferences for example, here is an article on deploying a simple application https://www.experts-exchange.com/articles/29126/Deploy-single-EXE-applications-without-installers.html


When you deploy the tool via a GPO File Preference, the schedule can be centrally managed by simply deploying a new config file.


I hope you found this tutorial useful. You are encouraged to ask questions, report any bugs or make any other comments about it below.

 

Note: If you need further "Support" about this topic, please consider using the Ask a Question feature of Experts Exchange. I monitor questions asked and would be pleased to provide any additional support required in questions asked in this manner, along with other EE experts...  

 

Please do not forget to press the "Thumb's Up" button if you think this article was helpful and valuable for EE members.


It also provides me with positive feedback. Thank you!

1
1,152 Views
Shaun VermaakCOG Lead Engineer
CERTIFIED EXPERT
My name is Shaun Vermaak and I have always been fascinated with technology and how we use it to enhance our lives and business.

Comments (6)

Shaun VermaakCOG Lead Engineer
CERTIFIED EXPERT
Awarded 2017
Distinguished Expert 2019

Author

Commented:
It works directly with the API and I have more control than a shutdown.exe.
CERTIFIED EXPERT
Distinguished Expert 2019

Commented:
Aha.

for /f "tokens=1" %%a in ('qwinsta ^| findstr Disc') do logoff %%a
for /f "tokens=3" %%a in ('qwinsta ^| findstr Active') do logoff %%a

Isn't that just as much control? I am not sure if I understand what added control I get using yours.
Shaun VermaakCOG Lead Engineer
CERTIFIED EXPERT
Awarded 2017
Distinguished Expert 2019

Author

Commented:
Still limited to logoff.exe implementation of API

I can use any of the options available exposed by the OS via API
CERTIFIED EXPERT
Distinguished Expert 2019

Commented:
Will I understand the limitation once I use yours? I have no idea, what you mean. I want to logoff all users at a given time - I can and I don't need extra tools or schedulers.
Shaun VermaakCOG Lead Engineer
CERTIFIED EXPERT
Awarded 2017
Distinguished Expert 2019

Author

Commented:
Many ways to skin a cat. I prefer to use methods where I can replace a config file an alter all configuration.

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.