Remotely determine interactive/active user on Windows 7 machine - c#

Anyone else had to determine who the currently logged on user is remotely in a Windows 7 environment? I am using .NET 4.0 and C#. The environment is mixed XP and 7.
WMI queries involving sessions result in all active sessions, but not the session that is interactive.
UserName from ComputerSystem (WMI) returns null exception if user is connected via Remote Desktop, which is common enough that this method cannot be used.
PsLoggedOn takes too long for my purposes (yes, 300 ms is too long) and is surprisingly not accurate 100% of the time
Using p/invoke for WTSGetActiveConsoleSessionID or LsaEnumerateLogonSessions is too complicated and prone to memory leaks (from what I've read)
tasklist /S <computername> will return information for XP systems, but Windows 7 won't be agreeable thanks to that lovely UAC.
HKCU (win registry) is inaccessible remotely due to permissions restrictions, HKU is accessible, but Volatile Environment doesn't appear to have a tag for "active"
So far, the most reliable way is using PsExec to remotely execute qwinsta from the command line and traverse the output to text remotely. This is annoying and takes time (more than PsLoggedOn), but I'm running out of ideas here for reliability. Reliability before speed, but speed is very important in terms of cost benefit.
Third party tools are not an option, has to be a script, preferably WMI and C#. I delved into hitting the DC using Principal objects, but I'm afraid I might have confused myself more. Also, all user accounts are administrators.
I've done a lot of research over Google, but I am thinking that maybe I am looking in the wrong place.
Any takers?

you can achieve this by browsing Win32_ComputerSystem class's UserName Propperty :
ConnectionOptions con = new ConnectionOptions();
con.Username = "Administrator";
con.Password = "********";
ManagementScope scope = new ManagementScope(#"\\" + strIPAddress + #"\root\cimv2", con);
scope.Connect();
//check for scope.IsConnected, then process
ManagementObjectSearcher searcher =new ManagementObjectSearcher(#"\\" + strIPAddress + #"\root\cimv2", "SELECT * FROM Win32_ComputerSystem");
foreach (ManagementObject queryObj in searcher.Get())
{
Console.WriteLine("-----------------------------------");
Console.WriteLine("Win32_ComputerSystem instance");
Console.WriteLine("-----------------------------------");
Console.WriteLine("UserName: {0}", queryObj["UserName"]);
}

Related

Windows - Check if computer is locked/logged out before running tests

I have a suite of UI tests that I run locally/remotely depending on the situation. Occasionally, the remote computers are automatically restarted whenever there is an important update to apply. This causes all the tests to time out because the remote computers they are running on are locked out and no longer have access to the GUI.
I am wondering how I can do a quick check to see if the computer is locked or not, that way I can fail the tests quickly and log that they are offline.
I found this solution online but it seems more catered to development and not testing.
https://bytes.com/topic/net/answers/770957-get-computer-state-locked-stand-c
Really just want a clean way to check if the current machine is locked or not using C# libraries and put it in a method like the one below.
public bool IsWindowsLocked(){
// Check if the current machine is in a locked state
}
Unfortunately there aren't really any clean ways to do this, at least not that I could find, unless you are willing to use something like query user with PsExec to remotely execute it on each PC as a sub-process, and then parse the results. Even then you don't get a direct answer as to locked status, you would have to go by Idle time, as Status shows one of the users as Active when no one is using the computer.
Then there is the problem of multiple users being logged on a computer, using the Switch User functionality in Windows 7 or higher. In my environment, a PC might have 3 or 4 background users and one console user. In some cases, PCs are used by RDP users. And it turns out there is a special case when you RDP to a computer then later logon to the console or do the opposite, as LogonSession LogonType isn't updated in these cases. Unfortunately, it is also possible to catch a user just logging into a computer, in which case my function will incorrectly say the computer isn't in use.
On my PC and network, this function takes about 0.2 seconds to run, if the PC is on. On some PCs, it may take much longer (up to 20 seconds), as it loads a perfmon provider on the PC. If the PC is off, the timeout is quite long, and if that is a possibility, I would recommend doing a ping check first.
Basically the function uses WMI to get LogonSession and Interactive Desktops information, and Process to get LogonUI and explorer processes. Since LogonSession returns old sessions that have logged out, and sessions for UAC Admin programs and other (Windows 10) background processes (DWM/UMFD), we only count LogonSessions that have an explorer.exe process (desktop).
It then combines the information into different cases:
If the number of LogonUI processes is greater than or equal to the number of interactive desktops, the PC is either logged off or locked. If there are any LogonSessions (with explorer) on the PC, it is locked, otherwise it is logged off.
If the number of LogonUI processes is less than the number of interactive desktops, then the PC is in use.
Here is the code:
enum PCUserStatuses {
Locked, // all users are locked
LoggedOff, // No users are logged in
InUse, // A user is using this computer
Unknown // unable to connect to computer / other error
}
PCUserStatuses GetPCUserStatus(string machineName) {
try {
var scope = GetManagementScope(machineName);
scope.Connect();
var explorerProcesses = Process.GetProcessesByName("explorer", machineName)
.Select(p => p.Id.ToString())
.ToHashSet();
var REprocessid = new Regex(#"(?<=Handle="").*?(?="")", RegexOptions.Compiled);
var numberOfLogonSessionsWithExplorer = new ManagementObjectSearcher(scope, new SelectQuery("SELECT * FROM Win32_SessionProcess")).Get()
.Cast<ManagementObject>()
.Where(mo => explorerProcesses.Contains(REprocessid.Match(mo["Dependent"].ToString()).Value))
.Select(mo => mo["Antecedent"].ToString())
.Distinct()
.Count();
var numberOfUserDesktops = new ManagementObjectSearcher(scope, new SelectQuery("select * from win32_Perfrawdata_TermService_TerminalServicesSession")).Get().Count - 1; // don't count Service desktop
var numberOflogonUIProcesses = Process.GetProcessesByName("LogonUI", machineName).Length;
if (numberOflogonUIProcesses >= numberOfUserDesktops) {
if (numberOfLogonSessionsWithExplorer > 0)
return PCUserStatuses.Locked;
else
return PCUserStatuses.LoggedOff;
}
else
return PCUserStatuses.InUse;
}
catch {
return PCUserStatuses.Unknown;
}
}

identify a machine not by using IP

Suppose I'm programming a game and I want the option of banning people from it after violating the terms. I can ban them per account, I can also ban their IP but it's not going to ban them from my game forever. They will just have to create a new account and change their IP.
Is there something like a machine-ID that is unique for every machine in the world? If there is, is it possible to read it using a program language? Is it possible for the user to change this machine-ID?
Bulletproof solution?
I would say no solution will be 100% secure, even with huge invested money..
What you can do, is to make it as hard as possible for normal users, and this way minimize the cheaters.
You could make a hash id of the pc machine id + the mac address + the motherboard id + the harddisk id, etc. But a clever cracker/hacker could get around this. I mean take a look at companies like Microsoft that uses millions in making a secure way and still people can get around the activation in some way... ;)
In windows there is a hardware id, that you might be intrested in.
HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\MachineGuid
Take a look at WMI too, there are examples of how to use it with c++
Windows Management Instrumentation (WMI) is a set of extensions to the Windows Driver Model that provides an operating system interface through which instrumented components.
see WMI c++ exmaples
How about hard disk serial number which you can get in WMI Win32_DiskDrive.
ManagementObjectSearcher searcher = null;
try
{
searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_DiskDrive");
foreach (ManagementObject queryObj in searcher.Get())
{
Console.WriteLine("SerialNumber: {0}", queryObj["SerialNumber"]);
queryObj.Dispose();
}
}
finally
{
if (searcher != null)
searcher.Dispose();
}

how many times software has been installed and uninstalled on a computer

Is there any way (in C#, using WMI classes) to find out that how many times a particular software has been installed and uninstalled?
I want to run it on remote computer. I am getting software list by following code:
ManagementScope scope = new ManagementScope(#"\\" + ipAddress + #"\root\cimv2");
ObjectQuery query = new ObjectQuery("Select * from Win32_Product");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection queryCollection = searcher.Get();
foreach (ManagementObject m in queryCollection)
{
Console.Write(m["Caption"]+"\t");
Console.WriteLine(m["installDate"]);
}
Normally not.
When a program will be uninstalled every bit of the program should be removed from the machine like it was never their. Unfortunately nearly every program doesn't make a perfect job at this point leaving some artifacts on the machine.
Nevertheless the desired behavior is that after a uninstall everything is gone (including some kind of counter) so that it is only possible to check if a program is currently installed or not.
On the other site nothing permits a program to save somewhere some counter (e.g. registry) which will increased everytime a installation is started, but that's something specific for each program and no common mechanism exists where this counter should reside.

Network Authentication when running exe from WMI

I have a C# exe that needs to be run using WMI and access a network share. However, when I access the share I get an UnauthorizedAccessException. If I run the exe directly the share is accessible. I am using the same user account in both cases.
There are two parts to my application, a GUI client that runs on a local PC and a backend process that runs on a remote PC. When the client needs to connect to the backend it first launches the remote process using WMI (code reproduced below). The remote process does a number of things including accessing a network share using Directory.GetDirectories() and reports back to the client.
When the remote process is launched automatically by the client using WMI, it cannot access the network share. However, if I connect to the remote machine using Remote Desktop and manually launch the backend process, access to the network share succeeds.
The user specifed in the WMI call and the user logged in for the Remote Desktop session are the same, so the permissions should be the same, shouldn't they?
I see in the MSDN entry for Directory.Exists() it states "The Exists method does not perform network authentication. If you query an existing network share without being pre-authenticated, the Exists method will return false." I assume this is related? How can I ensure the user is authenticated correctly in a WMI session?
ConnectionOptions opts = new ConnectionOptions();
opts.Username = username;
opts.Password = password;
ManagementPath path = new ManagementPath(string.Format("\\\\{0}\\root\\cimv2:Win32_Process", remoteHost));
ManagementScope scope = new ManagementScope(path, opts);
scope.Connect();
ObjectGetOptions getOpts = new ObjectGetOptions();
using (ManagementClass mngClass = new ManagementClass(scope, path, getOpts))
{
ManagementBaseObject inParams = mngClass.GetMethodParameters("Create");
inParams["CommandLine"] = commandLine;
ManagementBaseObject outParams = mngClass.InvokeMethod("Create", inParams, null);
}
Having followed the link suggested by Isalamon above (thanks) I followed Jestro's advice and have rewritten using psexec.exe (which can be downloaded from http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx) instead of WMI. It feels like a bit of a kludge to do it this way, but it seems to work.
New code for anyone who is experiencing similar problems:
Process proc = new Process();
proc.StartInfo.FileName = "PsExec.exe";
proc.StartInfo.Arguments = string.Format("\\\\{0} -d -u {1}\\{2} -p {3} {4}",
remoteHost,
domain,
username,
password,
commandLine);
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.UseShellExecute = false;
proc.Start();
WMI just uses impersonation when executing the remote process, which does not give you network access. If you are ok going outside managed code, you can just map a UNC path in the remote process WMI started using whatever credentials you want. Then, you have the network access you want. I use NetUseAdd and NetUseDel from netapi32.dll to map the UNC path. See http://pinvoke.net/ for details on the using the APIs.
I know you've sorted it by using PSEXEC, which is a fantastic program, but if you did want to go back to WMI, have you tried enabling the following in your ConnectionOptions:
The flag EnablePrivileges
setting the Impersonation to ImpersonationLevel.Impersonate
Which does the following:
Gets or sets a value indicating whether user privileges need to be
enabled for the connection operation. This property should only be
used when the operation performed requires a certain user privilege to
be enabled (for example, a machine restart).
http://msdn.microsoft.com/en-us/library/system.management.connectionoptions.enableprivileges.aspx
Gets or sets the COM impersonation level to be used for operations in this connection.
http://msdn.microsoft.com/en-us/library/system.management.connectionoptions.impersonation.aspx
I think they should tell your WMI to actually allow the program to have the correct credentials and thus access your network share
You can write all you commands to batch file to the remote machine which includes net use (no need to use a drive letter) to do an authentication. Works fine that way. I am still working on an alternative.

Process.StartTime Access Denied

My code needs to determine how long a particular process has been running. But it continues to fail with an access denied error message on the Process.StartTime request. This is a process running with a User's credentials (ie, not a high-privilege process). There's clearly a security setting or a policy setting, or something that I need to twiddle with to fix this, as I can't believe the StartTime property is in the Framework just so that it can fail 100% of the time.
A Google search indicated that I could resolve this by adding the user whose credentials the querying code is running under to the "Performance Log Users" group. However, no such user group exists on this machine.
I've read something similar to what you said in the past, Lars. Unfortunately, I'm somewhat restricted with what I can do with the machine in question (in other words, I can't go creating user groups willy-nilly: it's a server, not just some random PC).
Thanks for the answers, Will and Lars. Unfortunately, they didn't solve my problem.
Ultimate solution to this is to use WMI:
using System.Management;
String queryString = "select CreationDate from Win32_Process where ProcessId='" + ProcessId + "'";
SelectQuery query = new SelectQuery(queryString);
ManagementScope scope = new System.Management.ManagementScope(#"\\.\root\CIMV2");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection processes = searcher.Get();
//... snip ... logic to figure out which of the processes in the collection is the right one goes here
DateTime startTime = ManagementDateTimeConverter.ToDateTime(processes[0]["CreationDate"].ToString());
TimeSpan uptime = DateTime.Now.Subtract(startTime);
Parts of this were scraped from Code Project:
http://www.codeproject.com/KB/system/win32processusingwmi.aspx
And "Hey, Scripting Guy!":
http://www.microsoft.com/technet/scriptcenter/resources/qanda/jul05/hey0720.mspx
Process of .Net 1.1 uses the Performance Counters to get the information. Either they are disabled or the user does not have administrative rights. Making sure the Performance Counters are enabled and the user is an administrator should make your code work.
Actually the "Performance Counter Users Group" should enough. The group doesn't exist by default. So you should create it yourself.
Process of .Net 2.0 is not depended on the Performance Counters.
See http://weblogs.asp.net/nunitaddin/archive/2004/11/21/267559.aspx
The underlying code needs to be able to call OpenProcess, for which you may require SeDebugPrivilege.
Is the process you're doing the StartTime request on running as a different user to your own process?
OK, sorry that didn't work... I am no expert on ASP.NET impersonation, I tend to use app pools which I don't think you can do on W2K Have you tried writing a tiny little test app which does the same query, and then running that as various users?
I am reluctant to post a chunk of MS framework code here, but you could use either Reflector or this: http://www.codeplex.com/NetMassDownloader to get the source code for the relevant bits of the framework so that you could try implementing various bits to see where it fails.
Can you get any other info about the process without getting Access Denied?
I can enumerate the process (ie, the GetProcessById function works), and we have other code that gets the EXE name and other bits of information.
I will give the test app a try. I'm also going to attempt to use WMI to get this information if I can't get the C# implementation working properly in short order (this is not critical functionality, so I can't spend days on it).

Categories

Resources