Introduction
This is a (Unix LR) Regular Expression to parse results from quser.exe, in any .NET language (including C#, F# and VB.NET).
(?m)^\s?(?<username>[\w\/.\/_\-]{2,64})\s\s+(?<sessionname>\w?.{0,22})\s\s+(?<sessionid>\d{1,5})\s\s+(?<state>.{0,12})\s\s+(?<idletime>.{0,20})\s\s+(?<logontime>.{0,32})$
Background
A recent project required the ability to remotely disconnect users (using logoff.exe, which requires a remote SessionID
, which you can get from quser.exe). At the time of writing, the only other quser parser I've found is written for Powershell, and I wanted to use it inside of a .NET\WPF application without having to reference Powershell\System.Management
assembly.
Tested on Windows 10, Server 2012 and Server 2008 R2.
Using the Code
I created the following sample, including a simple interface declaration, to hold our data.
Note that I've removed the multi-line flag (?m) from the above regular expression, in favor of .NET's RegexOptions.MultiLine
enumeration. Here is a module for querying and parsing quser data:
F# Example
namespace Vivrant.Parsers
module QUser =
open System
open System.Diagnostics
open System.Text.RegularExpressions
type IQUserInformation =
abstract member Username:String with get
abstract member SessionName:String with get
abstract member SessionID:String with get
abstract member State:String with get
abstract member IdleTime:String with get
abstract member LogonTime:DateTime with get
let QueryUsers(serverName:string) =
let args = String.Format(" /SERVER:{0}", serverName)
let cmdQuser = new Process()
cmdQuser.StartInfo.WorkingDirectory <- Environment.CurrentDirectory
cmdQuser.StartInfo.FileName <- "lib\quser.exe"
cmdQuser.StartInfo.Arguments <- args
cmdQuser.StartInfo.UseShellExecute <- false
cmdQuser.StartInfo.RedirectStandardOutput <- true
cmdQuser.StartInfo.CreateNoWindow <- true
cmdQuser.Start()
|> ignore
let stdOut = cmdQuser.StandardOutput.ReadToEnd()
while(not <| cmdQuser.HasExited) do
cmdQuser.WaitForExit()
stdOut
let ParseQUserResults(stdOut) =
let userResults = new ResizeArray<IQUserInformation>()
let quserPattern = @"^\s?(?<username>[\w\/.\/_\-]{2,64})\s\s+(?<sessionname>\w?.{0,22})\s\s+(?<sessionid>\d{1,5})\s\s+(?<state>.{0,12})\s\s+(?<idletime>.{0,20})\s\s+(?<logontime>.{0,32})$"
let regex = new Regex(quserPattern, RegexOptions.Multiline)
let mutable result = regex.Match(stdOut)
while result.Success do
let success, parsedLogonTime = DateTime.TryParse(result.Groups.["logontime"].Value.ToString().Trim())
userResults.Add
{ new IQUserInformation with
member this.Username = result.Groups.["username"].Value.ToString().Trim()
member this.SessionName = result.Groups.["sessionname"].Value.ToString().Trim()
member this.SessionID = result.Groups.["sessionid"].Value.ToString().Trim()
member this.State = result.Groups.["state"].Value.ToString().Trim()
member this.IdleTime = result.Groups.["idletime"].Value.ToString().Trim()
member this.LogonTime =
match success with
| true -> parsedLogonTime
| false -> DateTime.MinValue }
result <- result.NextMatch()
userResults
Now we can call it like this:
module Sample =
let userInfo = QUser.ParseQUserResults <| QUser.QueryUsers "myPC"
do ( )
Points of Interest
Depending on your architecture (ie 32bit vs 64bit Processor, OS and Project Build settings), your application may not be able to see the file c:\Windows\system32\quser.exe, because Windows virtualizes access to the System directory.
In order for your program to see `quser.exe`, you may need to have a copy of it OUTSIDE of the SYSTEM32 folder. Browse to it in Explorer, copy, and paste it somewhere else. For the sake of this example, I placed it in a subfolder of my project folder, "MyProject\lib", and set the build options to "copy always".
History