Win32_Process.Create fails if user profile is not loaded
Hi all, welcome back,
The other day I worked on an issue which happened when using WMI method Win32_Process.Create to spawn a process from an ASP.NET application. This method was returning an UNKNOWN FAILURE (8) and the new process wasn't created. The application was running as Network Service and WMI was impersonating a special user when launching the new process.
We could also reproduce the issue with a VBScript that we launched as the special user with runas command and its /noprofile option. When we used /profile option instead, the script worked fine, and so did the ASP.NET app! So it seems that this WMI method is dependant of the user profile being loaded.
After some tests and some debugging, I confirmed this:
Win32_Process.Create always tries to access the registry of the user which will launch the process (the one we are impersonating). This behavior is by design and can't be modified.
If the user profile is loaded, there is no problem as Create can access the registry keys it needs. But if the profile is not loaded, it will try to load the registry hive of the user (NTUSER.DAT file) into the registry. For doing that it uses RegLoadKey API, which in our case is failing with ERROR_PRIVILEGE_NOT_HELD error. This internal error will cause the misleading ERROR_NOT_ENOUGH_MEMORY that we get from Create method.
I verified the privileges this API requires in its documentation: "The calling process must have the SE_RESTORE_NAME and SE_BACKUP_NAME privileges on the computer in which the registry resides."
Summing up, Win32_Process.Create will work if the user we are impersonating has SE_RESTORE_NAME (a.k.a. SeRestorePrivilege a.k.a. "Restore files and directories") and SE_BACKUP_NAME (a.k.a. SeBackupPrivilege a.k.a."Backup files and directories") privileges in the local machine where the WMI code is running. This way it can load the registry hive if the profile for that user is not loaded.
An alternate solution may be to load the profile before calling Create. IIS won’t load it automatically, and WMI neither. We may load it by calling LoadUserProfile API, for instance. But the user calling this API needs high privileges. So if we know the user we are impersonating, we may have an easier way to load the profile: Service Control Manager (SCM) automatically loads the user profile of the user running a Windows service. We may create a dummy service which does nothing but always stay alive and which runs as our special user, so its profile gets loaded. Once the profile of the user gets loaded by SCM, every other app which requires it will have access to it, like a web app or a web service.
I hope this helps.
Cheers,
Alex (Alejandro Campos Magencio)
Comments
- Anonymous
August 29, 2008
Hi Alex,First of all thank you very much for this post.I have been struggling with an issue with Win32_Process "Create" method.If I try to map network drive on remote machine using "net share" it fails, I think it has same issue as you mentioned here.Will you please provide me that dummy service or let me know how I can do that?Thanks,Amit - Anonymous
August 31, 2008
Hi Amit,Here you have a good sample which shows how to create a simple service which uses a Timer to live, how to set it up, etc.:Writing a Useful Windows Service in .NET in Five Minuteshttp://blogs.msdn.com/bclteam/archive/2005/03/15/396428.aspxYou shouldn't need anything else on your dummy service. Another option is to use a thread instead of a timer, something like this:Imports System.ThreadingPublic Class Service1 Protected m_bRun As Boolean Protected Overrides Sub OnStart(ByVal args() As String) ' Add code here to start your service. This method should set things ' in motion so your service can do its work. m_bRun = True Dim t As Thread = New Thread(AddressOf DummyThread) t.Start() End Sub Protected Overrides Sub OnStop() ' Add code here to perform any tear-down necessary to stop your service. m_bRun = False End Sub Protected Sub DummyThread() While (m_bRun) Thread.Sleep(1000) End While End SubEnd ClassI hope this helps.Cheers,Alex - Anonymous
March 31, 2009
I have been struggling with this in a C# application for a couple of days. Everything in the call to "Create" looked right, the user was a domain admin and a process Id was always returned from the call to Win32_Process.Create, but the command never ran. I had no trouble querying the processes on the target machine; it was just "Create" that failed.This post was the best lead-in I've found. Once I logged in to the console on the remote machine as the user, thus creating a profile, "Create" worked exactly as planned.Your blood is worth bottling.As an aside, I am using a ManagementObjectSearcher to monitor the process on the remote machine after the call to "Create" to simulate synchronous behaviour. I'm doing a database restore on the remote machine and I don't want the application to start the client services on other machines until the restore completes.GaryHere is the class I use:sealed public class WMIWrapper { /// <summary> /// These flags can be combined in OR operations to build a set of /// flags for submission to the Win32_ProcessStartup class. /// </summary> enum Win32_ProcessFlags : uint { Debug_Process = 0x1, Debug_Only_This_Process = 0x2, Create_Suspended = 0x4, Detached_Process = 0x8, Create_New_Console = 0x10, Create_New_Process_Group = 0x200, Create_Unicode_Environment = 0x400, CREATE_BREAKAWAY_FROM_JOB = 0x1000000, Create_Default_Error_Mode = 0x4000000 }; public uint RunRemoteProcess(string[] Win32ProcessStartupEnvironment, string command, string workingDirectory, string remoteComputer) { // string[] Win32ProcessStartupEnvironment is the array of environment variables that are // required for the remote task. Some examples might be (if invoking db2cmd.exe): /* * * DB2INSTANCE=DB2 * DB2PATH=C:Program FilesIBMSQLLIB * DB2TEMPDIR=C:PROGRA~1IBMSQLLIB * * */ uint returnCode = 0; uint remoteProcessId = 0; int counter = 0; bool processRunning = false; string queryCommand = "Select * from Win32_PerfFormattedData_PerfProc_Process"; PerfProcProcess perfClassMembers = new PerfProcProcess(); // Instantiating an instance of a Win32_Process. ManagementClass Win32Process; // Win32_Process.Create needs a Win32_ProcessStartup instance as a parameter ManagementClass Win32ProcessStartup; // Win32_Process.Create needs a set of input parameters. ManagementBaseObject Win32ProcessInputParams; // Win32_Process.Create returns a set of output parameters. ManagementBaseObject Win32ProcessOutputParams; // Need to set some connection options. ConnectionOptions Win32ConnectionOptions; // Need scope (namespace) for the process. Normally runs in the "rootcimv2" space on a machine. ManagementScope Win32ProcessScope; // After the Win32_Process is created, a property set is returned in Win32ProcessOutputParams // that needs to be examined. So, an enumerator is needed. PropertyDataCollection OutputParamsProperties; PropertyDataCollection.PropertyDataEnumerator OutputParamsPropertiesEnumerator; PropertyData currentProperty; // Something to use to query remote processes ManagementObjectSearcher remoteProcessSearcher; ManagementObjectCollection queryCollection; ObjectQuery remoteQueryObject = new ObjectQuery(queryCommand); // Because the WMI methods are not directly exposed in C#, the ManagementClass and methods are needed // to encapsulate the WMI classes and invoke them indirectly. Win32Process = new ManagementClass("Win32_Process"); using (Win32Process) { // Create the Win32_Process. This invokes, for example, the command shell on the remote server. // The Win32_Process.Create method: // uint32 Create( // [in] string CommandLine, // [in] string CurrentDirectory, // [in] Win32_ProcessStartup ProcessStartupInformation, // [out] uint32 ProcessId // ); // // InvokeMethod() returns an object containing whatever is returned from the method invoked asynchronously. // Win32_Process.Create returns a uint (in the output property set with the name "ReturnValue", which // does not appear to be documented in the WMI documentation). // Win32_Process.Create also returns a uint ProcessId in an out parameter. Win32ProcessStartup = new ManagementClass("Win32_ProcessStartup"); // Setup the Win32ProcessStartup properties to pass to the Win32_Process.Create method. // The Win32_ProcessStartup properties (Win32_ProcessStartup has no methods): // class Win32_ProcessStartup : Win32_MethodParameterClass // { // uint32 CreateFlags; // string EnvironmentVariables[]; // uint16 ErrorMode; // uint32 FillAttribute; // uint32 PriorityClass; // uint16 ShowWindow; // string Title; // string WinstationDesktop; // uint32 X; // uint32 XCountChars; // uint32 XSize; // uint32 Y; // uint32 YCountChars; // uint32 YSize; // }; Win32ProcessStartup.Properties["CreateFlags"].Value = Win32_ProcessFlags.CREATE_BREAKAWAY_FROM_JOB | Win32_ProcessFlags.Detached_Process; Win32ProcessStartup.Properties["EnvironmentVariables"].Value = Win32ProcessStartupEnvironment; // Get the set of process input arguments for the Win32_Process.Create() method. These can then be modified // and submitted. // GetMethodParameters() is inherited from the ManagementClass base class. Win32ProcessInputParams = Win32Process.GetMethodParameters("Create"); Win32ProcessInputParams["CommandLine"] = command; Win32ProcessInputParams["CurrentDirectory"] = workingDirectory; Win32ProcessInputParams["ProcessStartupInformation"] = Win32ProcessStartup; // Initialise the connection options Win32ConnectionOptions = new ConnectionOptions(); Win32ConnectionOptions.Impersonation = ImpersonationLevel.Impersonate; Win32ConnectionOptions.Authentication = AuthenticationLevel.Unchanged; // Set the Scope Win32ProcessScope = new ManagementScope(@"" + remoteComputer + @"rootcimv2", Win32ConnectionOptions); // Associate the scope to the process to be invoked Win32Process.Scope = Win32ProcessScope; // Establish a connection to the remote machine Win32ProcessScope.Connect(); Win32ProcessOutputParams = Win32Process.InvokeMethod("Create", Win32ProcessInputParams, null); if (Win32ProcessOutputParams != null) { try { remoteProcessId = UInt32.Parse(Win32ProcessOutputParams["ProcessId"].ToString()); returnCode = UInt32.Parse(Win32ProcessOutputParams["ReturnValue"].ToString()); } catch { Console.WriteLine("{0}: {1}: Invalid command "{2}" submitted to the remote computer.", DateTime.Now, this.ToString(), command); Environment.Exit(0); } Console.WriteLine("ProcessId = {0}nrReturnValue = {1}", remoteProcessId.ToString(), returnCode.ToString()); // Need a method to monitor the progress of the process. This needs a scope because it's on a remote machine, // so re-use the existing scope. remoteProcessSearcher = new ManagementObjectSearcher(Win32ProcessScope, remoteQueryObject); queryCollection = remoteProcessSearcher.Get(); if (remoteProcessId != 0) { foreach (ManagementObject processPerformanceObject in queryCollection) { if (UInt32.Parse(processPerformanceObject["IDProcess"].ToString()) == remoteProcessId || UInt32.Parse(processPerformanceObject["CreatingProcessID"].ToString()) == remoteProcessId) { processRunning = true; while (processRunning == true) { processRunning = false; queryCollection = remoteProcessSearcher.Get(); foreach (ManagementObject processPerformanceObjectInner in queryCollection) { if (UInt32.Parse(processPerformanceObjectInner["IDProcess"].ToString()) == remoteProcessId || UInt32.Parse(processPerformanceObjectInner["CreatingProcessID"].ToString()) == remoteProcessId) { processRunning = true; Thread.Sleep(10000); counter += 1; Console.Write("rCount: {0} ", counter.ToString()); if (counter > 360) { processRunning = false; Console.WriteLine(); } } } } } } } } else Console.WriteLine("{0}: {1}: No process data was returned from the InvokeMethod call.", DateTime.Now.ToString(), this.ToString()); } // End using Win32Process if (counter == 0) { Console.WriteLine("{0}: {1}: The command "{2}" terminated more quickly than would be expected. " + "This may occur if the user running the command has no profile on the target system. In this case, " + "the command will probably have silently terminated. Log in as the user at the console on the target system to create a profile.", DateTime.Now.ToString(), this.ToString(), command); } return returnCode; } /// <summary> /// Fetches the value of a given environment variable from the specified computer /// </summary> /// <param name="remoteComputer">The name of the remote computer</param> /// <param name="environmentVariable">The name of the environment variable whose value should be returned</param> /// <param name="variableValue">The returned value of the environment variable</param> /// <returns></returns> public uint GetRemoteEnvironmentVariable(string remoteComputer, string environmentVariable, out string variableValue) { // Need a mthod to return an environment variable from the remote computer, such as DB2TEMP or TEMP uint returnCode = 0; string command = "Select * from Win32_Environment"; variableValue = ""; // Need a WMI searcher ManagementObjectSearcher WMISearcher; // and a place to store the results ManagementObjectCollection WMICollection; WMISearcher = new ManagementObjectSearcher(command); WMICollection = WMISearcher.Get(); foreach (ManagementObject WMIObject in WMICollection) { &nbs - Anonymous
January 21, 2010
Actually, the error you're getting from Win32_Process.Create isn't ERROR_NOT_ENOUGH_MEMORY but "Unknown Failure." The Create method has it's own error codes that don't map to Windows error codes:http://msdn.microsoft.com/en-us/library/aa389388(VS.85).aspx - Anonymous
January 21, 2010
Thx Adam! You are just right! Error means "Unknown failure" for that API. I'll change the post. Thx again! - Anonymous
January 26, 2010
Great post!Once I logged on with my account I could create an instance on the remote machine.Do I have to logon to all remote machines once to have it work then? I guess.. :(Robin - Anonymous
June 18, 2010
We are getting an error connecting to XP machines in a domain. We are using the Win32_Process.create in order to run processes on remote machines. The code works fine on Windows 7 but is failing on XP.Additionally we recently discovered that nearly all of our lab computers have run out of registry space due to the software load we're required to run (patton-tech.com/.../registrysizelimitwho-knew).Maybe I've answered my own question, well I suppose I should ask it first. Is it possible that the cryptic return code 8 (Unknown Failure) is in fact pointing at a lack of registry space? Can that be checked? - Anonymous
June 20, 2010
Well, if I had to take a look to this issue, I would debug the problematic call to check why we get that error 8. That is a very cryptic error, and it could be caused by anything... including problems with the registry, like in my post. Now, I don't know if you would be able to do that without our source code and private symbols... I suggest you open a case with us, Microsoft Technical Support, as we will be able to assist.Regards,Alex - Anonymous
February 25, 2011
You can do a basic impersonate, see if the profile exists, if not, create the profile folder and copy the ntuser.dat from the default profile. worked for me. - Anonymous
August 23, 2011
I just had this issue, and it was occurring on just 1 user account. Security was ok. AD account was ok. Turned out it was a problem with the user's local profile on the server. Funny thing is, the user profile wasn't listed in the user profile tab. I went to HKLMSOFTWAREMicrosoftWindows NTCurrentVersionProfileList and deleted the key corresponding to the SID of the user having a problem. That took care of the issue. - Anonymous
November 24, 2015
Here at CT DOT, we just remove the remote user's profile key from the registry and the system thinks he has no profile, and allows the create. remove: "SOFTWAREMicrosoftWindows NTCurrentVersionProfileList{SID of User}" But that's only if a normal create fails. -Steve