I am currently writing a C# service to basically make sure a group of exes are running and if they aren't then relaunch them.
The service starts up automatically so I run in to the problem that it attempts to launch the exe when no one is logged in. I have a solution that works for non-VM systems as shown below:
ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT UserName FROM Win32_ComputerSystem");
ManagementObjectCollection collection = searcher.Get();
string loggedIn = (string)collection.Cast<ManagementBaseObject>().First()["UserName"];
I then check this against null or whitespace and then just don't check the process list if no user is logged in.
However, while running on a VM this section of code returns null when a user is logged in. So I need a way of checking for a logged in user on both a standard machine and VM. I also need it to work for XP and windows 7, and both 32bit and 64bit. I am using .Net 4.
Official documentation to list sessions prescribes to use LsaLogOnSessions to enumerate them, refer to MSDN or to this CodeProject article for more examples.
That said if you just need to be sure there is at least one logged in user then you can simply check for default shell process. Default shell will always be loaded for each session:
bool loggedInUserExist = Process.GetProcessesByName("explorer").Any();
In this example I hard-coded value but you must read it from string value Shell in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinLogon\, in case default shell has been overridden (or for Server Core installation when GUI isn't present).
If you don't need anything else then you're done. If you have to filter specific users then you have to get process' owner. It's not such easy in C# unless you use WMI to query Win32_Process, search for explorer in Name property and then call GetOwner() method of the ManagementObject (see this post here on SO). Note that you may use same query both to determine if there is any logged in user and who he is. As alternative you may P/Invoke for Windows API functions as described in this post (but in this case I'd go back to LsaLogOnSessions).
Related
We are trying to build an installer, in Wix, for a product. Part of this product requires the installation of elasticsearch as a service, and getting it running as a service. The service should run under a separate user account.
The first step, getting the user account set up, has been successful. However, in order to allow elasticsearch to run correctly, we need to set up certain Environment variables. In order to reduce the chance of inadvertent user error, we will have to have these variables set under the elasticsearch user, not as machine-wide.
To this end, we need a way to create the variables under the specified user only. However, we have not yet been able to work out a way to do this, either using Wix or a C# extension.
The closest we have come is to query the ManagementObject in C# and discover the SID for our ELASTICUSER. We should, in theory, be able to write another environment variable to the Registry, under "HKEY_USERS\<SID>\Environment", as shown.
var query = new SelectQuery("Win32_UserAccount");
var manObjSearch = new ManagementObjectSearcher(query);
var stringToWrite = string.Empty;
foreach (var envVar in manObjSearch.Get())
{
if (envVar["Name"].ToString().ToUpperInvariant() == "ELASTICUSER")
{
elasticUserSid = envVar["SID"].ToString();
}
}
using (var key = Registry.Users.OpenSubKey(elasticUserSid + "\\Environment"))
{
if (key == null)
{
return;
}
key.SetValue("OurVariable", "Value");
}
However, it appears that the SID is not created in the Registry until the user first logs in.
So, is there any way to use either Wix, or in C# to create an environment variable for a newly created user that has never logged in?
Yes, from C# you can P/Invoke the LoadUserProfile Win32 API function. This will create the user's profile if it does not already exist, and load the user's registry hive.
I would not recommend this approach for an account that will be used for interactive logons, but it should be safe enough for a service account.
I have a messy collection of Windows platforms in various labs. Everything from XP to the latest. I have a simple C#/.NET app that runs at user login and logout. I need to differentiate between true Console logins and RDP sessions. I am using
System.Diagnostics.Process.GetCurrentProcess().SessionId
to get a sessionId but I don't see anything that says "you are on the Console" or "you are in an RDP session". Is there such a thing?
For my needs it turns out that the windows environment variable %sessionname% holds the info I am looking for. In my C# code I can say:
string sessionName = Environment.GetEnvironmentVariable("SESSIONNAME")
?? "CONSOLE";
%sessionname% seems to hold either the string "console" or "rdp-(something)#(number)", ex "rdt-tcp#1234". I don't know the whole range of possible values but it looks like it will work for my needs.
You can determine which session ID (if any) is currently attached to the physical console by calling the WTSGetActiveConsoleSessionId() function.
The code I am currently working on runs on Windows Server 2003, but needs to be able to write to EventLogs on multiple machines. I am coding in C#, using VS2008 Pro, and .NET Framework 3.5.
The code itself is relatively simple (thanks to the framework):
using (EventLog remoteEvtLog = new EventLog(LogName, HostName, EventSource))
{
remoteEvtLog.WriteEntry(Body);
}
"LogName" is a string containing the name of the log to write to - in most cases "Application".
"HostName" is a string containing the NetBIOS Name of the machine where the log entry should be written.
"EventSource" is a string containing the name of the event sender (this is a utility used by multiple apps, so usually it will have the name of the consuming application).
"Body" is a string containing the text to be written to the event log.
In most cases this works fine, but when the machine being written to uses UAC, any write which creates a new EventSource fails. This occurs even though the Security credentials used are members of the Administrators group - and I have not been able to find a way to specify the elevated priviledge level. Apparently, members of the Administrators goroup get two tokens - one limited, and one elevated, but as far as I can tell, the only way to specifiy the elevated token is through the UI - which is obviously a problem when remotely accessing the Logs.
Any ideas out there?
Your code is not supposed to create new event sources (the legacy auto-create behavior is unfortunate, but still wrong). If you need a separate event source for your application, then the installer for that application - which runs with elevated administrative privileges - should create it.
I am trying to find a way to get a list of Windows sessions? I need the same information as the one displayed in the Task Manager on the User tab. I need to know if the user is active or not and if s/he is logged on in the Remote Desktop session.
Any idea on how to do that with C# / Windows XP Pro?
As a starting point you can get a list of users logged on by running the command
qwinsta
From the command prompt.
This will give output like
C:\WINDOWS\system32>qwinsta
SESSIONNAME USERNAME ID STATE TYPE DEVICE
>console me 0 Active wdcon
rdp-tcp 65536 Listen rdpwd
and will list any remote sessions.
Use LsaEnumerateLogonSessions via P/Invoke. You'll also need LsaFreeReturnBuffer to cleanup after enumerating.
I believe you'll need to use P/Invoke to retrieve this information.
The relevant APIs are documented in this MSDN page.
Another way is to use the Logonsessions utility from Sysinternals:
http://technet.microsoft.com/en-us/sysinternals/bb896769.aspx
You do not need to use Pinvoke. WMI does it, and well: "select Name, SessionId from Win32_Process" in the root\cimv2 namespace. And, it can be called from a remote machine. Simpler. Add in a where clause in the select to fine tune what you get back.
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).