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;
}
}
Related
I work with emergency services and they have an application that uses map files to let them know where they need to go and it uses GPS to let them know where they are. We have to update the map files as things change and before I started here they were being done through VB scripts which started to fail. I decided to code my own app in C# to do this which works fine.
I created a package in SCCM 2012 that caches all of the files locally and then it compares the files in the cache to what is on the machine and then replaces any older files. This all works fine but the application they use called MobileCAD locks the files so I have to kill this process and then do the file copy and start the application again. We never know when an emergency happens so this update may start when they are on the road so it is important that it starts the application again as soon as possible. If it does not start the application then the emergency services people may try to do so manually but if core files are being updated then it may not start or cause issues.
I coded my application which uses an app manifest to force it to run as an administrator for the file copy. This application is run through SCCM which uses the local 'System' account to do all of the work and killing MobileCAD and copying files which works great. What I originally found was that it does start MobileCAD but it does so under the System account and the process would be there but it was not visible. I think this is the same problem they were originally having so the emergency services people would need to reboot the computer and wait for it to log back in and then start the wireless service so they could get back into MobileCAD.
To address this issue I did research and found that I could use the ProcessStartInfo in .NET and force it to use another account. As we use an automatic logon for these machines the users name, password, and domain are all in the registry so it was easy to pull it out and inject it into the code. Awesome, looks like it is easy enough so I code it up and sure enough it works perfectly when run under my admin account. In my basic testing everything worked perfectly until I try the same in SCCM, now it fails with the following error message.
System.ComponentModel.Win32Exception (0x80004005): Access is denied
at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
at System.Diagnostics.Process.Start()
at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
at UpdateFDM.Program.StartProcess(String processName)
I am sorry for all of the words but I believe it helps if you have a good understanding of the issue and what I am trying to do. I have also hard coded the user information into the code instead of pulling it from the registry but I get the same error. Again, this works fine under my admin account but fails when it is pushed through SCCM and it is only launching MobileCAD that fails.
This is the code I am using for launching MobleCAD, do you see where my issue may lie? I know SCCM confuses it but SCCM basically runs things just as you would from the command line but it uses the local System account.
Thanks for any help.
// Declares the new start instance
ProcessStartInfo process = new ProcessStartInfo();
// Gets the process to start
process.FileName = processName;
// Maximizes the process windows at start-up
process.WindowStyle = ProcessWindowStyle.Maximized;
// Gets the user name from the autologon information
process.UserName = GetDefaultUserInfo("DefaultUserName");
// Gets the domain for the user
process.Domain = GetDefaultUserInfo("DefaultDomainName");
// Holds the password for the default user
SecureString password = new SecureString();
// Gets the raw password from the registry
string rawPassword = GetDefaultUserInfo("DefaultPassword");
// Copies the password in a secure string
foreach (char ch in rawPassword)
{
password.AppendChar(ch);
}
// Sets the password
process.Password = password;
// Needed to launch the app as the logged on user
process.LoadUserProfile = true;
process.UseShellExecute = false;
// Starts the process
Process.Start(process);
// Process started, return true
return true;
I apologize for how open ended I'm sure this will end up. I'll try to break this up in manageable chunks.
I'm writing a program that deals with SCCM. This program is locked down by security groups.
If you are not a member of the 'Server Operators' group, you don't get in. Simple. I've been testing for awhile on my dev machine (a Dell that is on wireless) with no issues.
When I released a beta, I found that under certain circumstances the program will not pick up on the user's security group membership and therefore deny access.
I was able to reproduce the issue, it seems machines on wireless tend to have this issue. Though, it's more complicated than that.
-Freshly imaged machines seem to have this issue
-Not all wireless machines; my dev machine is wireless
-One desktop (no wireless) has this issue. (It's at a remote site, so I can't really pick that one apart) I think it's a fresh image as well. I did however test on another computer at the same site - worked fine.
-Connecting to ethernet seems to have an effect - 75% of the time it fixes the issue somehow - after a bit of a wait. (Works on both ethernet and wireless at that point)
I've been trying to get a breakpoint set on one of these machines so I can see what is going on. Problem is, by the time I get VS.net installed the problem solves itself. I know (very little) about remote debugging - currently looking into that. This scenario makes me wonder if it's update related (the image is fairly up to date, maybe a month or two out?)
I also wrote a small utility that tests the login procedure (using the same code) and it finds the security group every time. Wat.
Code for finding security group of a user:
(courtesy of Stack :) )
static bool IsUserMemberOf_(string user, string group)
{ // (I realize the user parameter is superfluous in this case)
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + TC.act_Domain, TC.act_AD_User, TC.enc_GetADPassword());
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(|(cn=" + TC.act_AD_User + ")(sAMAccountName=" + TC.act_AD_User + ")))";
SearchResult result = mySearcher.FindOne();
foreach (string GroupPath in result.Properties["memberOf"])
{
if (GroupPath.Contains(group))
{
return true;
}
}
}
catch (DirectoryServicesCOMException)
{
}
return false;
}
I've also tried another method (using Principal.IsMemberOf() ), which had the same result. I've also investigated DNS/Network problems. Not ruling it out, but doesn't seem to be a factor in the testing I've done.
I'm at a loss. If anyone has any thoughts, by all means please lay them on me.
Thanks
Yeah, it was the .net version. Our image starts with .net 4.0. Throwing 4.5.2 at it clears it right up. I would have expected an error at runtime, but I guess not.
... I'll show myself out
I have an application that I have just added ClickOnce to as an update method. I'm about to pull it and do something else, even after working through all the gotchas of dealing with ClickOnce in a moderately complex application. Well, it's not even a complex application, but it's going onto dedicated hardware, so I have a few odd requirements, like completely transparent and automatic updates, no odd little pup-up windows, etc. The main one is that the application starts and takes over the system at boot.
Where this causes trouble for ClickOnce is that when the system first boots, there is no network - the Wi-Fi is still getting started and connecting. The application handles this, checking for the network to get started and then connecting to our server. ClickOnce is a different matter. If there is no network when the application starts, then all the ApplicationDeployment functions will not work, even after the network is started.
So, for example, I use something like this to get the version:
if (ApplicationDeployment.IsNetworkDeployed)
Version = ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
else
Version = "unknown";
If I run the application at boot (that is, before the network is working), this code will return "unknown" for the rest of the application run, even after the network is up. If I shut down the application and restart it, it shows the deployed version. So technically, the IsNetworkDeployed is returning an incorrect value. The application WAS network deployed; it's just not on a network NOW. I'll probably post this as a bug over on MSDN.
BTW, the application does not actually require a network to run, so at startup, I can't take the path of "wait until the network is ready, then restart the application automatically". The hardware can move around, and may be in the middle of nowhere with no available network at all. I still have to deal with that (and I don't actually return "unknown" for that case, I just pull the version from the assembly). And if the problem was just getting a version, I wouldn't care, but this means that there is no way to ever update the application, since it always starts with no network, and it will never get to my code to check for, download, and auto-update the application.
Before I write off all my ClickOnce work, I was wondering if anyone knew of a way to reinitialize ApplicationDeployment, so that it will figure out that there is a network and enable all that ClickOnce goodness.
This is basically what that check is doing:
private static bool _isNetworkDeployed;
private static bool _isNetworkDeployedChecked;
public static bool IsNetworkDeployed
{
get
{
if (!_isNetworkDeployedChecked)
{
_isNetworkDeployed = (
AppDomain.CurrentDomain != null &&
AppDomain.CurrentDomain.ActivationContext != null &&
AppDomain.CurrentDomain.ActivationContext.Identity != null &&
AppDomain.CurrentDomain.ActivationContext.Identity.FullName != null);
//_isNetworkDeployed = ApplicationDeployment.IsNetworkDeployed;
_isNetworkDeployedChecked = true;
}
return _isNetworkDeployed;
}
}
We ran into the same issue with ClickOnce and reverse engineered the check. You could modify this to do your own checking prior to calling the .NET version.
I have this code:
[PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
public List<WinInfo> GetWindows()
{
try
{
var isFullTrust = Assembly.GetExecutingAssembly().IsFullyTrusted;
if (isFullTrust)
{
return Process.GetProcesses().Where(z => !string.IsNullOrEmpty(z.MainWindowTitle))
.Select(z => new WinInfo
{
ProcessID = z.Id,
ProcessName = z.ProcessName,
WinID = z.MainWindowHandle,
WindowTitle = z.MainWindowTitle
}).ToList();
}
else
return null;
}
catch (Exception ex)
{
Trace.Write(ex.Message);
return null;
}
}
When I test in on my local computer under my current user (with admin rights) it works ok, displaying all the processes, that have windows. But when I call this code from a windows service, run under "Local Service" account, then the list is empty. I attached to the process, and through debug I found that "Process.GetProcesses()" returns all the processes, but all of them have MainWindowHandle as 0 and MainWindowTitle as empty, even when they do have windows. So what is wrong with my code?
Edit I edited code, so that it checks the assembly for full trust and have PemmissionSet that should grant the code the neccessary rights. Still the result is the same. When I debug, I can see, that "isFullTrust" is "True" and code executes with no exceptions. Still the list is empty, because none of the processes contains not-empty MainWindowTitle
According to this thread :
The problem you're seeing is because by default service don't have access to any interactive desktops. I don't recommend interacting with the desktop from a service (#1, there may not be any desktop, #2 there may be multiple desktops, #3 interacting with the desktop from service in Vista is not implemented) but, you can check the "Interace with desktop" in your services properties.
maybe you can try to create an hidden form?
Surely you need to run that under the user account! Why would applications with open windows be running under the local system account? That's for windows services etc
It could also be related to your process requiring full trust
From MSDN: The Process class has a LinkDemand and an
InheritenceDemand for FullTrust on it. This means that if your
assembly is not fully trusted, it will be unable to kick off new
Processes or get information about running processes
Maybe this is a question of priviliges.
According to this link LocalService has minimum privileges on the local computer.
you should use Local system Account
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).