HELP! CreateProcessWithLogonW issue
Messages   Related Types
This message was discovered on microsoft.public.dotnet.framework.aspnet.webservices.
Responses highlighted in red are from those people who are likely to be able to contribute good, authoratitive information to this discussion. They include Microsoft employees, MVP's and others who IMHO contribute well to these kinds of discussions.
Post a new message to this list...

charlie@nunya.com
Hi,

I hope someone can help me with this - it's driving us all nuts.

We have an ASP.Net web application that must run an external executable to
accomplish a document merge function. We got this to work on Windows XP
using the following code (low level API calls). Before we call the RunIt
method, we successfully impersonate with the same Domain/UserID/Password as
is passed into the method. As I said, all is well in Win XP, however, when
we try to execute the code in Server 2003, the executable just hangs. It
doesn't get to any of the database processing (I know because I traced it)
so I am assuming it is hanging up almost immediately.

We have set the following permissions on the Server 2003 box:
SE_ASSIGNPRIMARYTOKEN_NAME by modifying the Local Security Policy ("Replace
a process level token")
- AND -
SE_INCREASE_QUOTA_NAME by modifying the Local Security Policy ("Adjust
memory quotas for a process")

The RunIt method that we are executing is below:

public string RunIt(string userID, string password, string domain, string
appString, string argString)
        {
            string rc = "";

            // Account to run as
            string _logonName = userID; // some user
            string _domain = domain; // domain
            string _password = password;

            StringBuilder sb = new StringBuilder();
            // command to execute
            string commandString = @appString + " " + @argString;
            sb.Append(@commandString);

            PROCESS_INFORMATION processInfo;
            STARTUPINFO startInfo = new STARTUPINFO();
            
            startInfo.cb = Marshal.SizeOf(startInfo);
            startInfo.lpTitle = null;
            startInfo.dwFlags = STARTF_USECOUNTCHARS;
            startInfo.dwYCountChars = 50;
            
            // create process similar as "runas" using the logon users profile
            bool ret = CreateProcessWithLogonW(_logonName, _domain, _password,
                LOGON_WITH_PROFILE, null, sb,
                NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT,
                IntPtr.Zero, "c:\\",
                ref startInfo, out processInfo);
            if(!ret)
            {
                // If failure ...
                rc = "Error: {0}" + Marshal.GetLastWin32Error() + commandString;
            }
            else
            {
                CloseHandle(processInfo.hThread);
                WaitForSingleObject(processInfo.hProcess,
                System.Threading.Timeout.Infinite);
                CloseHandle(processInfo.hProcess);
            }

            return rc;
        }

ANY help would be appreciated!!!
Thanks in advance.
Charlie
Reply to this message...
 
    
charlie@nunya.com
Once again, I am answering my own query hoping that this might help someone
else at some point (does anyone from microsoft ever answere questions that
border on the hard to solve?).

Anyway - we solved this issue using WMI. It works great but there were a
couple of quirks which I hope I have documented below. The code is taken
out of context so I make no gaurantee that it will compile as presented.
There were some good references for WMI and C# on the web. I didn't
remember to save the url's but a quick search will turn them up.
Ultimately, I believe this solution to be a little "cleaner" than the low
level API call that we were using as it uses Framework classes to get the
work done.

public void RunWIM(string domain, string userID, string pwd, string
appString, string argString)
{
string rc = "";
ConnectionOptions options = new ConnectionOptions();
string serverName = Dns.GetHostName();
// because we are running against the local machine we can't validate
// we are impersonating at this point so we have the correct security
// level
//options.Username = domain + @"\" + userID;
//options.Password = pwd;
//Create a scope to work in
ManagementScope WmiScope = new ManagementScope(@"\\" + serverName, options);
WmiScope.Connect();
ManagementClass processClass = new ManagementClass("Win32_Process");
processClass.Scope = WmiScope;
//Get an input parameters object for this method
ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
//Fill in input parameter values
inParams["CommandLine"] = appString + " " + argString;
// this will execute our command but it will not wait for the job to
complete...
ManagementBaseObject outParams = processClass.InvokeMethod("Create",
inParams, null);
}

The calling method has this code in it - we are creating some files and
writing them to disk so we just put some simple logic in a while loop to
make sure we waited until the process finished:
int x = 0;
bool goOn = false;
Impersonation i = new Impersonation();
string userID = "username";
string password = "password";
string domain = @ConfigurationSettings.AppSettings.Get("Default_Domain");
string app = @pAppPath + @"Tools\MergeEngine.exe";
string args = @iniFile;
try
{
RunProcessAs rp = new RunProcessAs();
rc = rp.RunWIM(domain,userID,password,app,args);
}
catch (Exception e)
{
rc = e.ToString();
}

I hope this will help someone - we struggled with the API call for a couple
of days but we got this going in less than an hour.
Charlie
Reply to this message...
 
    
Andrew Zimmer
I have the same issue with using CreateProcessWithLogonW on a 2003
machine. The application does not start. It doesn't even generate an
error message. I tried using the below example but it will not work
when trying to login to the same machine.

Does anyone know how to deal with this Server 2003 security issue? I
have granted about every local security setting policy setting to both
the ID doing the impersonation and the ID it is trying to impersonate
to.

I am trying to start an app under a specific ID from a windows
service. It works great on Server 2000 but not 2003.

Click here to reveal e-mail address wrote in message news:<HUFLc.4611$Click here to reveal e-mail address>...
[Original message clipped]

Reply to this message...
 
    
Yu Chen [MS] (VIP)
If your service is started under Local System account, this is a known issue
in Windows Server 2003 and XPSP2 - the CreateProcessWithLogonW API is
changed to better handle the new process' use of desktop by utilizing "Logon
Sid" in the caller's token. However the local system token (under which your
GINA is running) doesn't have a "Logon sid" so the API failed when caller is
local system.

You can use LogonUser and CreateProcessAsUser to achieve the same thing.

This info will be included in next release of MSDN.

--
Yu Chen [MS]
This posting is provided "AS IS" with no warranties, and confers no rights.

"Andrew Zimmer" <Click here to reveal e-mail address> wrote in message
news:Click here to reveal e-mail address...
[Original message clipped]

Reply to this message...
 
    
Yu Chen [MS] (VIP)
Please ignore the "GINA" part below - it's a cut & paste from an earlier
reply to another thread.

> If your service is started under Local System account, this is a known
issue
[Original message clipped]

Reply to this message...
 
    
Matthew Wieder
Hi - You suggest to use LogonUser and CreateProcessAsUser to replace
CreateProcessWithLogonW, but does that really replcae it exactly? It is
my understanding there are major differences between the two such as
LogonUser and CreateProcessAsUser doesn't load the user's registry hive.
thanks,
-Matthew

Yu Chen [MS] wrote:
[Original message clipped]

Reply to this message...
 
    
Andrew Zimmer
Actually, I the service is logged on as an admin on the machine. It
also has every local security policy setting imaginable.

"Yu Chen [MS]" <Click here to reveal e-mail address> wrote in message news:<Click here to reveal e-mail address>...
[Original message clipped]

Reply to this message...
 
    
Pavel Lebedinsky
Click here to reveal e-mail address (Andrew Zimmer) wrote:

[Original message clipped]

What is the return value from CreateProcessWithLogonW? What are
the exact parameters that you pass in?

If you enable "Detailed process tracking" in the auditing settings
of your security policy, do you get any "process created" events
in the security event log?
Reply to this message...
 
    
charlie@nunya.com
You should be able to do this on the same machine by first impersonating the
user you want to authenticate as, then invking the process using WMI.

Hey Yu - where were you with all the good advice when I was beating my head
against the wall last month?
Reply to this message...
 
    
Matthew Wieder
Is this addressing my question about the differences between
CreateProcessWithLogonW and Yu's proscribed solution?

Click here to reveal e-mail address wrote:
[Original message clipped]

Reply to this message...
 
    
charlie@nunya.com
After countless attempts at trying to get the CreateProcessWithLogonW to
work consistantly, I would suggest going straight to the WMI approach. WMI
has worked on every platform, every time.

Just my two cents but I have tried all that Yu has proposed and WMI works
every time whereas the other methods are spotty.
Reply to this message...
 
    
Matthew Wieder
If you don't mind, since your suggestion seems very different to
CreateProcessWithLogonW, could you please step me through the what calls
I need to make in order to "first impersonating the
user you want to authenticate as, then invking the process using WMI."
What is the call to do the impersonation and what is the cell to run my
process with the command line arguments?
thanks for your time!

Click here to reveal e-mail address wrote:
[Original message clipped]

Reply to this message...
 
    
charlie@nunya.com
OK - here is how I impersonate - the impersonation class is at the end of
this message. I'll just do some code snippets to show the calling of the
impersonation and the executing of the process in WMI. Since we are doing
WMI on the same machine that we are executing on, there is no validation at
that point. However, we need to impersonate first to create the correct
security context in which to execute the external process.

.... some code...
Impersonation i = new Impersonation();
// userid and password are stored in an encrypted registry entry
string userID = i.GetUserNameFromRegistry();
string password = i.GetPasswordFromRegistry();
//domain is stored in a config file
string domain = settings.DefaultDomain;
string app = @pAppPath + @"bin\MergeEngine.exe";
string args = @"whatever arguments you need to pass";
WindowsImpersonationContext mWIC =
i.ImpersonateUser(userID,password,domain);
rc = RunWMI(domain,userID,password,app,args);
if (mWIC != null)
{
impersonation.UndoImpersonate(mWIC);
}
.... some more code...
public string RunWMI(string domain, string userID, string pwd, string
appString, string argString)
{
string rc = "";
ConnectionOptions options = new ConnectionOptions();
string serverName = Dns.GetHostName();
// because we are impersonating and running against the local machine
// we do not validate
//options.Username = domain + @"\" + userID;
//options.Password = pwd;
//Create a scope to work in
ManagementScope WmiScope = new ManagementScope(@"\\" + serverName, options);
WmiScope.Connect();
ManagementClass processClass = new ManagementClass("Win32_Process");
processClass.Scope = WmiScope;
//Get an input parameters object for this method
ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
//Fill in input parameter values
inParams["CommandLine"] = appString + " " + argString; //' Or whatever
application you want
//Note: The return code of the method is provided in the "returnValue"
property of the outParams object
ManagementBaseObject outParams = processClass.InvokeMethod("Create",
inParams, null);
return rc;
}

Impesonation Class follows:

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;
using System.Text;
using Microsoft.Win32;

[assembly:SecurityPermissionAttribute(SecurityAction.RequestMinimum,
UnmanagedCode=true)]
namespace Security.WebModules
{
    /// <summary>
    /// This class has all the necessary methods to allow the application to
    impersonate another user
    /// when the standard user does not have the required permissions.
    /// </summary>
    public class Impersonation
    {
        public Impersonation()
        {    }

        /// <summary>
        /// This method returns the user name that has been stored in encrypted
        form in the registry.
        /// The user name should have been previously created and have the
        appropriate permissions.
        /// </summary>
        public string GetUserNameFromRegistry()
        {
            string userName = "";

            Lookup l = new Lookup();
            userName = l.GetUserNameFromRegistry();

            return userName;
        }

        /// <summary>
        /// This method returns the user name that has been stored in encrypted
        form in the registry.
        /// The user name should have been previously created and have the
        appropriate permissions.
        /// </summary>
        public string GetPasswordFromRegistry()
        {
            string password = "";

            Lookup l = new Lookup();
            password = l.GetPasswordFromRegistry();

            return password;
        }

        public WindowsImpersonationContext ImpersonateUser(string user, string
        password, string domain)
        {
            WindowsImpersonationContext mWIC = null;

            ChangeUser cu = new ChangeUser();

            mWIC = cu.Impersonate(user,password,domain);

            return mWIC;
        }

        public bool UndoImpersonate(WindowsImpersonationContext mWIC)
        {
            bool rc = true;

            ChangeUser cu = new ChangeUser();

            if (mWIC != null)
            {
                rc = cu.UndoImpersonate(mWIC);
            }

            return rc;
        }
    }

    public class ChangeUser
    {
        public ChangeUser()
        {    }

        [DllImport("advapi32.dll")]
        private static extern bool LogonUser(String
            lpszUsername, String lpszDomain, String lpszPassword,
            int dwLogonType, int dwLogonProvider, out int
            phToken);

        [DllImport("Kernel32.dll")]
        private static extern int GetLastError();

        public WindowsImpersonationContext Impersonate(string user, string
        password, string domain)
        {
            // The Windows user token.
            int token1;
            int ret = 0;
            WindowsImpersonationContext mWIC = null;

            bool loggedOn = LogonUser(user,domain,password,8,0, out token1);

            // Call GetLastError to try to determine why logon failed if it did not
            succeed.
            if (!loggedOn)
            {
                ret = GetLastError();
            }

            if (ret != 0)
            {
                return mWIC;
            }

            //Starting impersonation here:
            WindowsIdentity mWI1 = WindowsIdentity.GetCurrent();
            IntPtr token2 = new IntPtr(token1);
            WindowsIdentity mWI2 = new WindowsIdentity(token2);
            // Impersonate the user.
            mWIC = mWI2.Impersonate();

            return mWIC;
        }

        public bool UndoImpersonate(WindowsImpersonationContext mWIC)
        {
            bool rc = true;

            // Revert to previous identity.
            mWIC.Undo();

            return rc;
        }
    }

    public class Lookup
    {
        private RegistryKey pRootKey;
        private RegistryKey pIdentityKey;
        private byte[] entropyBytes = null;
        private string description;

        public RegistryKey rootKey
        {
            get
            {
                return pRootKey;
            }
            set
            {
                pRootKey = value;
            }
        }

        public RegistryKey identityKey
        {
            get
            {
                return pIdentityKey;
            }
            set
            {
                pIdentityKey = value;
            }
        }

        public Lookup()
        {
            pRootKey = Registry.LocalMachine.OpenSubKey("SOFTWARE",false);
            pIdentityKey = pRootKey.OpenSubKey(@"RQTitleCo\identity\ASPNET_SETREG");
        }

        // Wrapper for DPAPI CryptUnprotectData function.
        [DllImport(
        "crypt32.dll",SetLastError=true,CharSet=System.Runtime.InteropServices.CharSet.Auto)]
        private static extern
            bool CryptUnprotectData(ref DATA_BLOB pCipherText,
            ref string pszDescription,
            ref DATA_BLOB pEntropy,
            IntPtr pReserved,
            ref CRYPTPROTECT_PROMPTSTRUCT pPrompt,
            int dwFlags,
            ref DATA_BLOB pPlainText);

        // BLOB structure used to pass data to DPAPI functions.
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct DATA_BLOB
        {
            public int cbData;
            public IntPtr pbData;
        }

        // Prompt structure to be used for required parameters.
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct CRYPTPROTECT_PROMPTSTRUCT
        {
            public int cbSize;
            public int dwPromptFlags;
            public IntPtr hwndApp;
            public string szPrompt;
        }

        // Wrapper for the NULL handle or pointer.
        static private IntPtr NullPtr = ((IntPtr)((int)(0)));

        // DPAPI key initialization flags.
        private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
        private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;

        public string GetUserNameFromRegistry()
        {
            string userName = "";

            byte[] userNameBytes;

            userNameBytes = (byte[]) pIdentityKey.GetValue("userName");
            userNameBytes = Decrypt(userNameBytes,entropyBytes,out description);
            userName = (new UnicodeEncoding()).GetString(userNameBytes);

            return userName;
        }

        public string GetPasswordFromRegistry()
        {
            string password = "";

            byte[] passwordBytes;

            passwordBytes = (byte[]) pIdentityKey.GetValue("password");
            passwordBytes = Decrypt(passwordBytes,entropyBytes,out description);
            password = (new UnicodeEncoding()).GetString(passwordBytes);

            return password;
        }

        /// <SUMMARY>
        /// Initializes empty prompt structure.
        /// </SUMMARY>
        /// <PARAM name="ps">
        /// Prompt parameter (which we do not actually need).
        /// </PARAM>
        private static void InitPrompt(ref CRYPTPROTECT_PROMPTSTRUCT ps)
        {
            ps.cbSize = Marshal.SizeOf(
                typeof(CRYPTPROTECT_PROMPTSTRUCT));
            ps.dwPromptFlags= 0;
            ps.hwndApp = NullPtr;
            ps.szPrompt = null;
        }

        /// <SUMMARY>
        /// Initializes a BLOB structure from a byte array.
        /// </SUMMARY>
        /// <PARAM name="data">
        /// Original data in a byte array format.
        /// </PARAM>
        /// <PARAM name="blob">
        /// Returned blob structure.
        /// </PARAM>
        private static void InitBLOB(byte[] data, ref DATA_BLOB blob)
        {
            // Allocate memory for the BLOB data.
            blob.pbData = Marshal.AllocHGlobal(data.Length);

            // Make sure that memory allocation was successful.
            if (blob.pbData == IntPtr.Zero)
                throw new Exception(
                    "Unable to allocate data buffer for BLOB structure.");

            // Specify number of bytes in the BLOB.
            blob.cbData = data.Length;

            // Copy data from original source to the BLOB structure.
            Marshal.Copy(data, 0, blob.pbData, data.Length);
        }

        // Wrapper for Win32 message formatter.
        [DllImport("kernel32.dll",
             CharSet=System.Runtime.InteropServices.CharSet.Auto)]
        private unsafe static extern
            int FormatMessage( int dwFlags,
            ref IntPtr pMessageSource,
            int dwMessageID,
            int dwLanguageID,
            ref string lpBuffer,
            int nSize,
            IntPtr* pArguments);

        public static byte[] Decrypt( byte[] cipherTextBytes,
            byte[] entropyBytes,
            out string description)
        {
            // Create BLOBs to hold data.
            DATA_BLOB plainTextBlob = new DATA_BLOB();
            DATA_BLOB cipherTextBlob = new DATA_BLOB();
            DATA_BLOB entropyBlob = new DATA_BLOB();

            // We only need prompt structure because it is a required
            // parameter.
            CRYPTPROTECT_PROMPTSTRUCT prompt =
                new CRYPTPROTECT_PROMPTSTRUCT();
            InitPrompt(ref prompt);

            // Initialize description string.
            description = String.Empty;

            try
            {
                // Convert ciphertext bytes into a BLOB structure.
                try
                {
                    InitBLOB(cipherTextBytes, ref cipherTextBlob);
                }
                catch (Exception ex)
                {
                    throw new Exception(
                        "Cannot initialize ciphertext BLOB. " +
                        ex.Message);
                }

                // we don't use entropy bytes so this code is commented out...
                //                // Convert entropy bytes into a BLOB structure.
                //                try
                //                {
                //                    InitBLOB(entropyBytes, ref entropyBlob);
                //                }
                //                catch (Exception ex)
                //                {
                //                    throw new Exception(
                //                        "Cannot initialize entropy BLOB. " +
                //                        ex.Message);
                //                }

                // Disable any types of UI. CryptUnprotectData does not
                // mention CRYPTPROTECT_LOCAL_MACHINE flag in the list of
                // supported flags so we will not set it up.
                int flags = CRYPTPROTECT_UI_FORBIDDEN;

                // Call DPAPI to decrypt data.
                bool success = CryptUnprotectData(ref cipherTextBlob,
                    ref description,
                    ref entropyBlob,
                    IntPtr.Zero,
                    ref prompt,
                    flags,
                    ref plainTextBlob);

                // Check the result.
                if (!success)
                {
                    // If operation failed, retrieve the last Win32 error.
                    throw new Exception(
                        "CryptUnprotectData failed. " +
                        GetErrorMessage(Marshal.GetLastWin32Error()));
                }

                // Allocate memory to hold plaintext.
                byte[] plainTextBytes = new byte[plainTextBlob.cbData];

                // Copy ciphertext from the BLOB to a byte array.
                Marshal.Copy(plainTextBlob.pbData,
                    plainTextBytes,
                    0,
                    plainTextBlob.cbData);

                // Return the result.
                return plainTextBytes;
            }
            catch (Exception ex)
            {
                throw new Exception("DPAPI was unable to decrypt data. " +
                    ex.Message);
            }
                // Free all memory allocated for BLOBs.
            finally
            {
                if (plainTextBlob.pbData != IntPtr.Zero)
                    Marshal.FreeHGlobal(plainTextBlob.pbData);

                if (cipherTextBlob.pbData != IntPtr.Zero)
                    Marshal.FreeHGlobal(cipherTextBlob.pbData);

                if (entropyBlob.pbData != IntPtr.Zero)
                    Marshal.FreeHGlobal(entropyBlob.pbData);
            }
        }

        /// <SUMMARY>
        /// Formats error message from a Win32 error code.
        /// </SUMMARY>
        /// <PARAM name="errorCode">
        /// Error code.
        /// </PARAM>
        /// <RETURNS>
        /// Formatted error message.
        /// </RETURNS>
        private unsafe static string GetErrorMessage(int errorCode)
        {
            // Define flags used to build message.
            int FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
            int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
            int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

            // Initialize data variables.
            int messageSize = 0;
            int minBufferSize = 0;
            string messageBuffer = String.Empty;
            string message = String.Empty;

            // We need build a system message as opposed to a message from
            // a resource DLL. The operation will require auto memory
            // allocation. We will not use formatted arguments.
            int dwFlags = FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_IGNORE_INSERTS;

            // Set null pointers.
            IntPtr pMessageSource = new IntPtr();
            IntPtr pArguments = new IntPtr();

            // Use default language.
            int langID = 0;

            // Get message.
            messageSize = FormatMessage( dwFlags,
                ref pMessageSource,
                errorCode,
                langID,
                ref messageBuffer,
                minBufferSize,
                &pArguments);

            // Include error code in the message.
            if (messageSize == 0)
                message = String.Format("Error {0} occurred.",
                    errorCode);
            else
                message = String.Format("Error {0}: {1}",
                    errorCode, messageBuffer);

            // Return formatted message.
            return message;
        }
    }
}
Reply to this message...
 
 
System.Configuration.ConfigurationSettings
System.Exception
System.IntPtr
System.Management.ConnectionOptions
System.Management.ManagementBaseObject
System.Management.ManagementClass
System.Management.ManagementScope
System.Net.Dns
System.Runtime.InteropServices.CharSet
System.Runtime.InteropServices.LayoutKind
System.Runtime.InteropServices.Marshal
System.Security.Permissions.SecurityAction
System.Security.Permissions.SecurityPermissionAttribute
System.Security.Principal.WindowsIdentity
System.Security.Principal.WindowsImpersonationContext
System.String
System.Text.StringBuilder
System.Text.UnicodeEncoding
System.Threading.Timeout




Ad
MBR BootFX
Best-of-breed application framework for .NET projects, developed by Matthew Baxter-Reynolds and MBR IT
 
 Copyright © Matthew Baxter-Reynolds 2001-2008. '.NET 247 Software Development Services' is a trading style of MBR IT Solutions Ltd.
Contact Us - Terms of Use - Privacy Policy - www.dotnet247.com