I just did some tests with the function CreateProcessWithTokenW new in Windows Vista (well, MSDN says that). However I could not make it work and always got an error 5 (Access denied). I simply tried to run the notepad application. Fortunately, I could remember a similar case that happened to a friend.

He told me about a problem, he had with CreateProcessAsUser and that it always failed (was it #5 ?). However it was late (or early) in the morning and we finally found out that we misspelled the desktop’s name “default” (something like “defaut”). So how does it apply to my problem?

I just coded this first version in Delphi elevated by RunAsSys. Thus the code was executed in the SYSTEM account (remember this!).

uses
  JwaWindows,
  JwsclPrivileges,
  JwsclToken,
  SysUtils;

var Token : TJwSecurityToken;
  App : WideString;
  StartupInfo: TSTARTUPINFOW;
  ProcessInformation: TPROCESSINFORMATION;
begin
  ZeroMemory(@lpStartupInfo, SizeOf(lpStartupInfo));
  lpStartupInfo.cb := sizeof(lpStartupInfo);

  Token := TJwSecurityToken.CreateWTSQueryUserToken(1); //session 1 for vista
  App := ‘C:\Windows\system32\notepad.exe’;

 Win32Check(
  CreateProcessWithTokenW(
    Token.TokenHandle,//hToken: HANDLE;
    0,//dwLogonFlags: DWORD;
    PWideChar(App), //lpApplicationName: LPCWSTR;
    nil, //lpCommandLine: LPWSTR;
    0, //dwCreationFlags: DWORD;
    nil,//lpEnvironment: LPVOID;
    nil,//lpCurrentDirectory: LPCWSTR;
    @StartupInfo,//: LPSTARTUPINFOW;
  @ProcessInformation)//: LPPROCESS_INFORMATION): BOOL; stdcall;
  );
end.

Yes, I’m using JWSCL here for the token stuff. It’s just convenient, isn’t it?

Some people may see the problem. However the same code using CreateProcessAsUserW instead of CreateProcessWithTokenW runs just fine in the SYSTEM context.

Win32Check(
  CreateProcessAsUserW(
      Token.TokenHandle,//HANDLE hToken,
      PWideChar(App),//__in_opt     LPCTSTR lpApplicationName,
      nil, //__inout_opt  LPTSTR lpCommandLine,
      nil,//__in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
      nil,//__in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
      false,//__in         BOOL bInheritHandle
      0,//__in         DWORD dwCreationFlags,
      nil,//__in_opt     LPVOID lpEnvironment,
      nil,////__in_opt     LPCTSTR lpCurrentDirectory,
   StartupInfo,//: LPSTARTUPINFOW;
  ProcessInformation)//: LPPROCESS_INFORMATION): BOOL; stdcall;
     );

And here it comes. In the beginning, I told you sth about a misspelled desktop name. However what is the desktop here? Yes, it is nil. As MSDN reads for CreateProcessAsUser, the new process uses the parent’s desktop if the given desktop is nil.
And CreateProcessWithTokenW ? The same goes for this function. However, It did not work for me.

The solution was simple. I just added the name of the default desktop to the lpDesktop member of the StartupInfo record.

lpStartupInfo.lpDesktop := ‘default’;

So the complete code looks like this:

uses
  JwaWindows,
  JwsclPrivileges,
  JwsclToken,
  SysUtils;

var Token : TJwSecurityToken;
  App : WideString;
  StartupInfo: TSTARTUPINFOW;
  ProcessInformation: TPROCESSINFORMATION;
begin
  ZeroMemory(@lpStartupInfo, SizeOf(StartupInfo));
  lpStartupInfo.cb := sizeof(StartupInfo);
  lpStartupInfo.lpDesktop := ‘default’; //winsta0\default

  Token := TJwSecurityToken.CreateWTSQueryUserToken(1); //session 1 for vista
  App := ‘C:\Windows\system32\notepad.exe’;

 Win32Check(
  CreateProcessWithTokenW(
    Token.TokenHandle,//hToken: HANDLE;
    0,//dwLogonFlags: DWORD;
    PWideChar(App), //lpApplicationName: LPCWSTR;
    nil, //lpCommandLine: LPWSTR;
    0, //dwCreationFlags: DWORD;
    nil,//lpEnvironment: LPVOID;
    nil,//lpCurrentDirectory: LPCWSTR;
    @StartupInfo,//: LPSTARTUPINFOW;
  @ProcessInformation)//: LPPROCESS_INFORMATION): BOOL; stdcall;
  );
  //…cleanup
end.

So this code works now for SYSTEM processes. However, as MSDN reads, the default desktop is used if the lpDesktop member is nil. This is true and surprisingly works under one condition. If you run the above code with administrative privileges (run UAC in Vista) you will see a notepad. So it works after all? Yes. Eventually, with the given solution it works both ways. You just have to make sure to add the default desktop or current desktop if you are thorough.

So, why does it happen at all? I cannot prove my theory but it seems that the Secondary Logon Service cannot get access to something. Yes, the Secondary Logon Process hosts this function. You can prove it by suspending the seclogon thread in the svchost.exe. The thread awakes immediately if the CreateProcessWithTokenW is called. To get a sufficient answer I had to debug the seclogon.dll. However I did not debug it since imho the whole problem seems to be a bug and it is not my task to provide ready-to-use solutions for MS. Feel free to find the bug yourself. I’m looking forward to read it.

Eventually there can be one more reason why you get an access denied error:

EFS – Encrypted File System and CreateProcessXXX

A secondary reason why all CreateProcessXXX may fail is the Encrypted File System (EFS). If a user encrypts her file system, only this particular user can access it (hopefully). However in this case, CreateProcessAsUser (and similar) may fail with an access denied error since they cannot access the file (or even find it). As a solution you just have to impersonate the user before calling CreateProcessAsUser (or similar) with the same token. The impersonation does not affect the execution at all because CreateProcessAsUser itself always checks the privileges of your process (the caller) and not the privileges held by the thread. So it’s “safe”.