Logoff Interactive Users in Windows from a Service

Logoff interactive users in Windows from a service

You could use following P/Invoke calls to achieve this. Below sample works only with Admin Rights

        [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);

Here is a sample implementation to lookup all the users and their sessions, and then logging off one of the user.

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

namespace ConsoleApplication1
{
[StructLayout(LayoutKind.Sequential)]
internal struct WTS_SESSION_INFO
{
public Int32 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
}

class Program
{
[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 bool LogOffUser(string userName, IntPtr server)
{

userName = userName.Trim().ToUpper();
List<int> sessions = GetSessionIDs(server);
Dictionary<string, int> userSessionDictionary = GetUserSessionDictionary(server, sessions);
if (userSessionDictionary.ContainsKey(userName))
return WTSLogoffSession(server, userSessionDictionary[userName], true);
else
return false;
}

private static Dictionary<string, int> GetUserSessionDictionary(IntPtr server, List<int> sessions)
{
Dictionary<string, int> userSession = new Dictionary<string, int>();

foreach (var sessionId in sessions)
{
string uName = GetUserName(sessionId, server);
if (!string.IsNullOrWhiteSpace(uName))
userSession.Add(uName, sessionId);
}
return userSession;
}

internal static string GetUserName(int sessionId, IntPtr server)
{
IntPtr buffer = IntPtr.Zero;
uint count = 0;
string userName = string.Empty;
try
{
WTSQuerySessionInformation(server, sessionId, WTS_INFO_CLASS.WTSUserName, out buffer, out count);
userName = Marshal.PtrToStringAnsi(buffer).ToUpper().Trim();
}
finally
{
WTSFreeMemory(buffer);
}
return userName;
}

static void Main(string[] args)
{
string input = string.Empty;
Console.Write("Enter ServerName<Enter 0 to default to local>:");
input = Console.ReadLine();
IntPtr server = WTSOpenServer(input.Trim()[0] == '0' ? Environment.MachineName : input.Trim());
try
{
do
{
Console.WriteLine("Please Enter L => list sessions, G => Logoff a user, END => exit.");
input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
continue;
else if (input.ToUpper().Trim()[0] == 'L')
{
Dictionary<string, int> userSessionDict = GetUserSessionDictionary(server, GetSessionIDs(server));
foreach (var userSession in userSessionDict)
{
Console.WriteLine(string.Format("{0} is logged in {1} session", userSession.Key, userSession.Value));
}
}
else if (input.ToUpper().Trim()[0] == 'G')
{
Console.Write("Enter UserName:");
input = Console.ReadLine();
LogOffUser(input, server);
}

} while (input.ToUpper() != "END");
}
finally
{
WTSCloseServer(server);
}
}
}
}

C# Logoff out a user via Windows Service

Check out WTSLogoffSession. It does precisely what you want and is pretty easy to call via PInvoke. To get the session ID use WTSGetActiveConsoleSessionId or WTSEnumerateSessions and WTSQuerySessionInformation.

How to make a .NET Windows Service detect Logon, Logoff and Switch User events?

If you're making a service, your class is derived from ServiceBase. Consider investigating the OnSessionChange method. Override this method to detect different types of session changes. It provides both a reason for the session change, and the new session identifier. Be sure in your constructor to set CanHandleSessionChangeEvent to true, otherwise your override will not be called.

Determining if user is actually logged off in a domain - Windows Active Directory

You might want to look at event 4647, which is logged whenever a user logs off. If interactive sessions are all you're interested in, you should also limit yourself to relevant logon types (2, 7, 10, 11) in the 4624 event.

Log off users remotely

Iterate through the collection and logoff all the Id present:

$ScriptBlock = {
$Sessions = quser /server:$Computer 2>&1 | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
# If session is disconnected different fields will be selected
If ($CurrentLine[2] -eq 'Disc') {
[pscustomobject]@{
UserName = $CurrentLine[0];
Id = $CurrentLine[1]
}
}
Else {
[pscustomobject]@{
UserName = $CurrentLine[0];
Id = $CurrentLine[2]
}
}
}
$Sessions | ForEach-Object {
logoff $_.Id
}
}

Invoke-Command -ComputerName gmwin10test -ScriptBlock $ScriptBlock

How to get the current number of interactive user sessions in Windows?

I ended up with the following approach: count the number of interactive sessions which have at least one process running.

1) Get the logon session id for each interactive session.

  • LsaEnumerateLogonSessions (secur32.dll)
  • LsaGetLogonSessionData (secur32.dll)
  • sessionData.LogonType = SECURITY_LOGON_TYPE.Interactive or sessionData.LogonType = SECURITY_LOGON_TYPE.RemoteInteractive
  • sessionData.LoginID <- Keep this value in a LUID set.
  • LsaFreeReturnBuffer (secur32.dll)

2) Get the logon session id for each running process.

[First we need to enable the SeDebugPrivilege to the current application.]

  • GetCurrentProcess (kernel32.dll)
  • OpenProcessToken TOKEN_ADJUST_PRIVILEGES (advapi32.dll)
  • LookupPrivilegeValue SE_DEBUG_NAME (advapi32.dll)
  • AdjustTokenPrivileges (advapi32.dll)
  • CloseHandle (kernel32.dll)

[Then retrieve the data we want.]

  • EnumProcesses (psapi.dll)
  • OpenProcess PROCESS_QUERY_INFORMATION (kernel32.dll)
  • OpenProcessToken TOKEN_QUERY (advapi32.dll)
  • GetTokenInformation TOKEN_INFORMATION_CLASS.TokenStatistics (advapi32.dll)
  • accessTokenStatistics.AuthenticationId <- Keep this value in a LUID set.
  • CloseHandle (kernel32.dll)

3) Sets intersection cardinality

interactiveSessionsCount = | { sessionData.LoginID } ∩ { accessTokenStatistics.AuthenticationId } |

Obs: sessionData.LoginID and accessTokenStatistics.AuthenticationId are both of type LUID.



Related Topics



Leave a reply



Submit