This code, run from IIS under the app pool account, can run an executable like notepad.exe or some custom simple .NET app. I can even write to a file from the guest account/app. But accessing the registry (such as Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main", "Local Page", null);
) from the app results in a generic .NET APP CRASH (0xC0000142
).
I'm pretty sure that the LoadUserProfile()
is what is failing to have an effect.
I've tried several variations, including SetUserObjectSecurity
for the "current" window station and desktop (to an EXPLICT_ACCESS with WINSTA_ALL_ACCESS | READ_CONTROL and GENERIC_ALL respectively but this has no noticeable effect). I saw this access rights to the window station and desktop suggested elsewhere.
I've tried variations with LogonUserEx()
or CreateProcessWithLogon()
(instead of LogonUser()
and LoadProfile()
) and CreateProcessWithToken(...LOGON_WITH_PROFILE...)
, CreateProcessAsUser()
.
One thing I don't understand is why LoadUserProfile()
doesn't seem to have any association with a logon/session/window station/desktop/process. What are we loading the profile into? Maybe I'm loading the profile, but not into where the target can access it?
I have logon as batch rights for the target account and all kinds of rights for the service account including administrator, act as part of the OS, adjust memory quotas, replace tokens, backup, and restore. I suspect that UAC or a similar mechanism is stripping some of these rights from the app pool account when it is created.
Anyway, the big question is how do we successfully load profile from a service?
The targeted apps run fine when invoked by IIS for execution by the same user account using the built-in System.Diagnostics.Process, but fail when a different user account is specified. Apparently "Process.Start internally calls CreateProcessWithLogonW(CPLW) when credentials are specified, which cannot be called from a Windows Service Environment."
uint exitCode;
IntPtr userToken = IntPtr.Zero;
IntPtr userProfile = IntPtr.Zero;
try
{
if (!Native.LogonUser(
username,
domain,
password,
Native.LOGON32_LOGON_BATCH,
Native.LOGON32_PROVIDER_DEFAULT,
ref userToken))
{
var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
throw new Exception("LogonUser failed: " + win32Ex.Message, win32Ex);
}
Native.PROFILEINFO profileInfo = new Native.PROFILEINFO();
profileInfo.dwSize = Marshal.SizeOf(profileInfo);
profileInfo.lpUserName = username;
if (!Native.LoadUserProfile(userToken, ref profileInfo))
{
var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
throw new Exception("LoadUserProfile failed: " + win32Ex.Message, win32Ex);
}
Native.STARTUPINFO startUpInfo = default(Native.STARTUPINFO);
startUpInfo.cb = Marshal.SizeOf(startUpInfo);
startUpInfo.lpDesktop = string.Empty;
Native.PROCESS_INFORMATION processInfo = default(Native.PROCESS_INFORMATION);
try
{
if (!Native.CreateProcessAsUserW(
userToken,
command,
// CreateProcessAsUser() doesn't include the executable name in the args as other mechanisms do,
// and so when you read them in on the other side (which skips args[0] by convention) you'll be missing
// your expected first argument!
string.Format("\"{0}\" {1}", command, arguments),
IntPtr.Zero,
IntPtr.Zero,
true,
0,
IntPtr.Zero,
null,
ref startUpInfo,
out processInfo))
{
var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
throw new Exception("CreateProcessAsUserW failed: " + win32Ex.Message, win32Ex);
}
Native.WaitForSingleObject(processInfo.hProcess, Native.INFINITE);
if (!Native.GetExitCodeProcess(processInfo.hProcess, out exitCode))
{
var win32Ex = new Win32Exception(Marshal.GetLastWin32Error());
throw new Exception("GetExitCodeProcess failed: " + win32Ex.Message, win32Ex);
}
}
finally
{
Native.CloseHandle(processInfo.hThread);
Native.CloseHandle(processInfo.hProcess);
}
}
finally
{
if (userProfile != IntPtr.Zero) Native.UnloadUserProfile(userToken, userProfile);
if (userToken != IntPtr.Zero) Native.CloseHandle(userToken);
}
ViewBag.Message = string.Format("VersionB ran to the end with exit code ({0})", exitCode);
return View("Index");
The LogonUserEx crashes IIS hard with "Access Violation Exception" with every variation I tried suggesting that the pinvoke signature is very wrong. (maybe I had it close once, or maybe that it worked was just (un)lucky).
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] // to invoke the 'W' variant, I tried some variations there AND specifed MarshallAs on the strings.
internal static extern bool LogonUserEx(
string lpszUsername,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken,
ref IntPtr ppLogonSid, // I tried these as out and no decoration
ref IntPtr ppProfileBuffer, // I tried null, IntPtr.zero
ref IntPtr pdwProfileLength, //
ref IntPtr pQuotaLimits
);