commit fd56c2e93ce9c6054e8f65ee083f917b21d354a3 Author: lsecqt Date: Thu Jan 1 15:43:34 2026 +0200 init diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..7d876a3 --- /dev/null +++ b/Readme.md @@ -0,0 +1,139 @@ +# SessionView + +A lightweight C# console application for enumerating and displaying all user sessions on Windows Server, including session GUIDs. + +## Features + +- **Complete Session Enumeration**: Lists all active sessions on the Windows server +- **Detailed Session Information**: Displays Session ID, Station Name, Connection State, Username, and Domain +- **Session GUID Retrieval**: Extracts the unique GUID identifier for each session from the Windows registry +- **Connection State Detection**: Shows whether sessions are Active, Disconnected, Idle, etc. +- **Clean Console Output**: Easy-to-read formatted output for quick session analysis + +## Requirements + +- Windows Server (2012 R2 or later) or Windows Client (Windows 10/11) +- .NET Framework 4.7.2+ +- Administrator privileges (required for accessing session information and registry) + +## Installation + +### Option 1: Build from Source + +1. Clone or download the repository +2. Open the solution in Visual Studio 2019 or later +3. Build the solution (Ctrl+Shift+B) +4. The executable will be located in `bin\Debug` or `bin\Release` + +### Option 2: Command Line Build + +# Using MSBuild (Developer Command Prompt for Visual Studio) +msbuild SessionView.csproj /p:Configuration=Release + +# Or using CSC directly +csc /out:SessionView.exe /platform:anycpu Program.cs + +## Usage + +1. **Run as Administrator**: This toold needs elevated token +2. The application will automatically enumerate all sessions and display the information + +### Example Output + +``` +=== Windows Server Session Enumerator === + +Found 3 session(s) + +Session 1: + Session ID: 0 + Station Name: Services + State: WTSDisconnected + Username: (No user logged in) + Session GUID: (Not available) + +Session 2: + Session ID: 1 + Station Name: Console + State: WTSActive + Username: Administrator + Domain: MYSERVER + Session GUID: {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + +Session 3: + Session ID: 2 + Station Name: RDP-Tcp#1 + State: WTSDisconnected + Username: JohnDoe + Domain: COMPANY + Session GUID: {B2C3D4E5-F6A7-8901-BCDE-F12345678901} + +Press any key to exit... +``` + +## Session States + +The application can display the following connection states: + +- **WTSActive**: User is actively using the session +- **WTSConnected**: Session is connected +- **WTSDisconnected**: Session is disconnected but still running +- **WTSIdle**: Session is idle +- **WTSListen**: Session is listening for connections +- **WTSReset**: Session is being reset +- **WTSDown**: Session is down +- **WTSInit**: Session is initializing + +## Technical Details + +### APIs Used + +- **WTS API (wtsapi32.dll)**: For enumerating sessions and querying session information +- **Windows Registry API (advapi32.dll)**: For retrieving session GUIDs from the registry + +### Registry Location + +Session GUIDs are stored at: +``` +HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Remote\Terminal Services\Session\{SessionId}\SessionGuid +``` + +### Known Limitations + +- Session 0 (Services session) typically does not have a GUID +- Requires administrator privileges to access session information +- Some sessions may not have all information available depending on their state + +## Troubleshooting + +### "Access Denied" Error +- Ensure you're running the application as Administrator +- Check that you have proper permissions on the server + +### Missing Session GUIDs +- This is normal for certain system sessions (like Session 0) +- Some disconnected sessions may not retain their GUID in the registry + +### No Sessions Found +- Verify you're running on a Windows Server with Terminal Services enabled +- Check that the Remote Desktop Services role is installed + +## Use Cases + +- **System Administration**: Monitor active and disconnected user sessions +- **Remote Desktop Management**: Track RDP connections and their states +- **Security Auditing**: Identify user sessions and their unique identifiers +- **Session Management Scripts**: Integrate with automation tools for session cleanup +- **Troubleshooting**: Diagnose session-related issues on Terminal Servers + +## License + +This project is provided as-is for educational and administrative purposes. + +## Contributing + +Feel free to submit issues, fork the repository, and create pull requests for any improvements. + +## Author + +Created for Windows Server administration and session management tasks. \ No newline at end of file diff --git a/SessionView/.vs/SessionView/CopilotIndices/17.14.1091.29919/CodeChunks.db b/SessionView/.vs/SessionView/CopilotIndices/17.14.1091.29919/CodeChunks.db new file mode 100644 index 0000000..57997c7 Binary files /dev/null and b/SessionView/.vs/SessionView/CopilotIndices/17.14.1091.29919/CodeChunks.db differ diff --git a/SessionView/.vs/SessionView/CopilotIndices/17.14.1091.29919/SemanticSymbols.db b/SessionView/.vs/SessionView/CopilotIndices/17.14.1091.29919/SemanticSymbols.db new file mode 100644 index 0000000..797f446 Binary files /dev/null and b/SessionView/.vs/SessionView/CopilotIndices/17.14.1091.29919/SemanticSymbols.db differ diff --git a/SessionView/.vs/SessionView/FileContentIndex/84b9d9d5-4ab5-4c04-ab39-dc3e06c908ef.vsidx b/SessionView/.vs/SessionView/FileContentIndex/84b9d9d5-4ab5-4c04-ab39-dc3e06c908ef.vsidx new file mode 100644 index 0000000..6763bb0 Binary files /dev/null and b/SessionView/.vs/SessionView/FileContentIndex/84b9d9d5-4ab5-4c04-ab39-dc3e06c908ef.vsidx differ diff --git a/SessionView/.vs/SessionView/v17/.suo b/SessionView/.vs/SessionView/v17/.suo new file mode 100644 index 0000000..105a5c7 Binary files /dev/null and b/SessionView/.vs/SessionView/v17/.suo differ diff --git a/SessionView/.vs/SessionView/v17/DocumentLayout.backup.json b/SessionView/.vs/SessionView/v17/DocumentLayout.backup.json new file mode 100644 index 0000000..09310a2 --- /dev/null +++ b/SessionView/.vs/SessionView/v17/DocumentLayout.backup.json @@ -0,0 +1,41 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\lsec\\source\\SessionView\\SessionView\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{E865CBCC-40D2-48F3-BE98-31EBC6094E98}|SessionView\\SessionView.csproj|c:\\users\\lsec\\source\\sessionview\\sessionview\\sessionview\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{E865CBCC-40D2-48F3-BE98-31EBC6094E98}|SessionView\\SessionView.csproj|solutionrelative:sessionview\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Program.cs", + "DocumentMoniker": "C:\\Users\\lsec\\source\\SessionView\\SessionView\\SessionView\\Program.cs", + "RelativeDocumentMoniker": "SessionView\\Program.cs", + "ToolTip": "C:\\Users\\lsec\\source\\SessionView\\SessionView\\SessionView\\Program.cs", + "RelativeToolTip": "SessionView\\Program.cs", + "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-01T13:37:10.009Z", + "EditorCaption": "" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/SessionView/.vs/SessionView/v17/DocumentLayout.json b/SessionView/.vs/SessionView/v17/DocumentLayout.json new file mode 100644 index 0000000..a2576df --- /dev/null +++ b/SessionView/.vs/SessionView/v17/DocumentLayout.json @@ -0,0 +1,41 @@ +{ + "Version": 1, + "WorkspaceRootPath": "C:\\Users\\lsec\\source\\SessionView\\SessionView\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{E865CBCC-40D2-48F3-BE98-31EBC6094E98}|SessionView\\SessionView.csproj|c:\\users\\lsec\\source\\sessionview\\sessionview\\sessionview\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{E865CBCC-40D2-48F3-BE98-31EBC6094E98}|SessionView\\SessionView.csproj|solutionrelative:sessionview\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": 0, + "Children": [ + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "Program.cs", + "DocumentMoniker": "C:\\Users\\lsec\\source\\SessionView\\SessionView\\SessionView\\Program.cs", + "RelativeDocumentMoniker": "SessionView\\Program.cs", + "ToolTip": "C:\\Users\\lsec\\source\\SessionView\\SessionView\\SessionView\\Program.cs", + "RelativeToolTip": "SessionView\\Program.cs", + "ViewState": "AgIAAPMAAAAAAAAAAAAmwA0BAAABAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2026-01-01T13:37:10.009Z", + "EditorCaption": "" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/SessionView/SessionView.sln b/SessionView/SessionView.sln new file mode 100644 index 0000000..4801459 --- /dev/null +++ b/SessionView/SessionView.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SessionView", "SessionView\SessionView.csproj", "{E865CBCC-40D2-48F3-BE98-31EBC6094E98}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E865CBCC-40D2-48F3-BE98-31EBC6094E98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E865CBCC-40D2-48F3-BE98-31EBC6094E98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E865CBCC-40D2-48F3-BE98-31EBC6094E98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E865CBCC-40D2-48F3-BE98-31EBC6094E98}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {174D77B3-218B-4604-B102-FF9B3F187140} + EndGlobalSection +EndGlobal diff --git a/SessionView/SessionView/App.config b/SessionView/SessionView/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/SessionView/SessionView/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SessionView/SessionView/Program.cs b/SessionView/SessionView/Program.cs new file mode 100644 index 0000000..4499741 --- /dev/null +++ b/SessionView/SessionView/Program.cs @@ -0,0 +1,270 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using System.ComponentModel; + +namespace SessionView +{ + class Program + { + // Constants + const int WTS_CURRENT_SERVER_HANDLE = 0; + + // Enums + enum WTS_INFO_CLASS + { + WTSSessionId = 4, + WTSUserName = 5, + WTSDomainName = 7, + WTSConnectState = 8, + WTSSessionInfo = 24 + } + + enum WTS_CONNECTSTATE_CLASS + { + WTSActive, + WTSConnected, + WTSConnectQuery, + WTSShadow, + WTSDisconnected, + WTSIdle, + WTSListen, + WTSReset, + WTSDown, + WTSInit + } + + // Structs + [StructLayout(LayoutKind.Sequential)] + struct WTS_SESSION_INFO + { + public int SessionId; + public IntPtr pWinStationName; + public WTS_CONNECTSTATE_CLASS State; + } + + [StructLayout(LayoutKind.Sequential)] + struct WTSINFO + { + public int SessionId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 33)] + public string WinStationName; + public WTS_CONNECTSTATE_CLASS State; + // Other fields omitted for brevity + } + + // P/Invoke declarations + [DllImport("wtsapi32.dll", SetLastError = true)] + static extern bool WTSEnumerateSessions( + IntPtr hServer, + int Reserved, + int Version, + ref IntPtr ppSessionInfo, + ref int pCount); + + [DllImport("wtsapi32.dll", SetLastError = true)] + static extern bool WTSQuerySessionInformation( + IntPtr hServer, + int sessionId, + WTS_INFO_CLASS wtsInfoClass, + out IntPtr ppBuffer, + out int pBytesReturned); + + [DllImport("wtsapi32.dll")] + static extern void WTSFreeMemory(IntPtr pMemory); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern int ProcessIdToSessionId(int dwProcessId, out int pSessionId); + + // Registry access for session GUID + [DllImport("advapi32.dll", SetLastError = true)] + static extern int RegOpenKeyEx( + IntPtr hKey, + string lpSubKey, + int ulOptions, + int samDesired, + out IntPtr phkResult); + + [DllImport("advapi32.dll", SetLastError = true)] + static extern int RegQueryValueEx( + IntPtr hKey, + string lpValueName, + IntPtr lpReserved, + out uint lpType, + byte[] lpData, + ref int lpcbData); + + [DllImport("advapi32.dll", SetLastError = true)] + static extern int RegCloseKey(IntPtr hKey); + + static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + const int KEY_READ = 0x20019; + const int ERROR_SUCCESS = 0; + + static void Main(string[] args) + { + Console.WriteLine("=== Windows Server Session Enumerator ===\n"); + + IntPtr serverHandle = IntPtr.Zero; + IntPtr sessionInfoPtr = IntPtr.Zero; + int sessionCount = 0; + + try + { + // Enumerate sessions + bool result = WTSEnumerateSessions( + serverHandle, + 0, + 1, + ref sessionInfoPtr, + ref sessionCount); + + if (!result) + { + throw new Win32Exception(Marshal.GetLastWin32Error()); + } + + Console.WriteLine($"Found {sessionCount} session(s)\n"); + + int structSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); + IntPtr currentSession = sessionInfoPtr; + + for (int i = 0; i < sessionCount; i++) + { + WTS_SESSION_INFO sessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure( + currentSession, + typeof(WTS_SESSION_INFO)); + + string stationName = Marshal.PtrToStringAnsi(sessionInfo.pWinStationName); + + Console.WriteLine($"Session {i + 1}:"); + Console.WriteLine($" Session ID: {sessionInfo.SessionId}"); + Console.WriteLine($" Station Name: {stationName}"); + Console.WriteLine($" State: {sessionInfo.State}"); + + // Get username + string username = GetSessionInfo(serverHandle, sessionInfo.SessionId, WTS_INFO_CLASS.WTSUserName); + if (!string.IsNullOrEmpty(username)) + { + Console.WriteLine($" Username: {username}"); + + // Get domain + string domain = GetSessionInfo(serverHandle, sessionInfo.SessionId, WTS_INFO_CLASS.WTSDomainName); + if (!string.IsNullOrEmpty(domain)) + { + Console.WriteLine($" Domain: {domain}"); + } + } + else + { + Console.WriteLine($" Username: (No user logged in)"); + } + + // Get session GUID from registry + string guid = GetSessionGuid(sessionInfo.SessionId); + if (!string.IsNullOrEmpty(guid)) + { + Console.WriteLine($" Session GUID: {guid}"); + } + else + { + Console.WriteLine($" Session GUID: (Not available)"); + } + + Console.WriteLine(); + + currentSession = (IntPtr)((long)currentSession + structSize); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + finally + { + if (sessionInfoPtr != IntPtr.Zero) + { + WTSFreeMemory(sessionInfoPtr); + } + } + + Console.WriteLine("\nPress any key to exit..."); + Console.ReadKey(); + } + + static string GetSessionInfo(IntPtr serverHandle, int sessionId, WTS_INFO_CLASS infoClass) + { + IntPtr buffer = IntPtr.Zero; + int bytesReturned = 0; + + try + { + if (WTSQuerySessionInformation(serverHandle, sessionId, infoClass, out buffer, out bytesReturned)) + { + return Marshal.PtrToStringAnsi(buffer); + } + } + finally + { + if (buffer != IntPtr.Zero) + { + WTSFreeMemory(buffer); + } + } + + return string.Empty; + } + + static string GetSessionGuid(int sessionId) + { + IntPtr keyHandle = IntPtr.Zero; + + try + { + string keyPath = $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Remote\Terminal Services\Session\{sessionId}"; + + int result = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + keyPath, + 0, + KEY_READ, + out keyHandle); + + if (result != ERROR_SUCCESS || keyHandle == IntPtr.Zero) + { + return null; + } + + uint type; + int dataSize = 256; + byte[] data = new byte[dataSize]; + + result = RegQueryValueEx( + keyHandle, + "SessionGuid", + IntPtr.Zero, + out type, + data, + ref dataSize); + + if (result == ERROR_SUCCESS && dataSize > 0) + { + return Encoding.Unicode.GetString(data, 0, dataSize - 2); // -2 to exclude null terminator + } + } + catch + { + // Session might not have a GUID in registry + } + finally + { + if (keyHandle != IntPtr.Zero) + { + RegCloseKey(keyHandle); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/SessionView/SessionView/Properties/AssemblyInfo.cs b/SessionView/SessionView/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..af6a5cf --- /dev/null +++ b/SessionView/SessionView/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SessionView")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SessionView")] +[assembly: AssemblyCopyright("Copyright © 2026")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e865cbcc-40d2-48f3-be98-31ebc6094e98")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SessionView/SessionView/SessionView.csproj b/SessionView/SessionView/SessionView.csproj new file mode 100644 index 0000000..e42c706 --- /dev/null +++ b/SessionView/SessionView/SessionView.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {E865CBCC-40D2-48F3-BE98-31EBC6094E98} + Exe + SessionView + SessionView + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SessionView/SessionView/obj/Debug/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs b/SessionView/SessionView/obj/Debug/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs new file mode 100644 index 0000000..3871b18 --- /dev/null +++ b/SessionView/SessionView/obj/Debug/.NETFramework,Version=v4.7.2.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] diff --git a/SessionView/SessionView/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache b/SessionView/SessionView/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache new file mode 100644 index 0000000..e149263 Binary files /dev/null and b/SessionView/SessionView/obj/Debug/DesignTimeResolveAssemblyReferencesInput.cache differ diff --git a/SessionView/SessionView/obj/Debug/SessionView.csproj.AssemblyReference.cache b/SessionView/SessionView/obj/Debug/SessionView.csproj.AssemblyReference.cache new file mode 100644 index 0000000..25132c6 Binary files /dev/null and b/SessionView/SessionView/obj/Debug/SessionView.csproj.AssemblyReference.cache differ