1
votes

I've already been searching long time but couldn't find a working solution yet :-(

I have created a window service that launches a client on every user logged on to a machine using CreateProcessAsUser (http://www.pinvoke.net/default.aspx/advapi32/createprocessasuser.html), WTSEnumerateSessions and so on...

This works fine already. The client starts in the user's session, shows its taskbar icon, and communication with the service is working fine.

The problem I have is that I need to have that client store temporary files in the user's profile. I tried starting with a small log file so that I can keep track of any errors that my user's could eventually experience. Unfortunately I can not save to the user's temp folder because the client somehow seems to be running in LocalSystem's context although WindowsIdentity shows the correct user: System.IO.Path.GetTempPath() always returns 'C:\Windows\Temp' but my user's don't have administrative rights so they are not able to write there... furthermore, I planned to store settings in the current user's registry which is not working, too. I think this is related to the wrong temp path in some way.

I also tried CreateEnvironmentBlock (http://www.pinvoke.net/default.aspx/userenv/CreateEnvironmentBlock.html) but I could not make it work and somewhere I found an article saying that this won't work any more on Vista or higher so I stopped researching on that one.

For testing I have created a small test form just doing this:

        MessageBox.Show("Temp: " + System.IO.Path.GetTempPath() + Environment.NewLine + "User: " + WindowsIdentity.GetCurrent().Name, "Before impersonation");

        WindowsIdentity currentUserId = WindowsIdentity.GetCurrent();
        WindowsImpersonationContext impersonatedUser = currentUserId.Impersonate();

        MessageBox.Show("Temp: " + System.IO.Path.GetTempPath() + Environment.NewLine + "User: " + WindowsIdentity.GetCurrent().Name, "After impersonation");

This one always shows the same results before and after impersonation: "Temp: C:\Windows\Temp User:testdomain\testuser" :-(

If it helps here's my function to start a process (user token is delivered by WTSEnumerateSessions) - of course this only works under LocalSystem's context:

    public static Process StartProcessAsUser(IntPtr UserToken, string App, string AppPath, string AppParameters)
    {
        Process ResultProcess = null;

        IntPtr hDupedToken = IntPtr.Zero;
        NativeProcessAPI.PROCESS_INFORMATION oProcessInformation = new NativeProcessAPI.PROCESS_INFORMATION();

        try
        {
            NativeProcessAPI.SECURITY_ATTRIBUTES oSecurityAttributes = new NativeProcessAPI.SECURITY_ATTRIBUTES();
            oSecurityAttributes.Length = Marshal.SizeOf(oSecurityAttributes);

            bool result = NativeProcessAPI.DuplicateTokenEx(
                  UserToken,
                  NativeProcessAPI.GENERIC_ALL_ACCESS,
                  ref oSecurityAttributes,
                  (int)NativeProcessAPI.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                  (int)NativeProcessAPI.TOKEN_TYPE.TokenPrimary,
                  ref hDupedToken
               );

            if (!result)
            {
                return null;
            }

            NativeProcessAPI.STARTUPINFO oStartupInfo = new NativeProcessAPI.STARTUPINFO();
            oStartupInfo.cb = Marshal.SizeOf(oStartupInfo);
            oStartupInfo.lpDesktop = String.Empty;

            result = NativeProcessAPI.CreateProcessAsUser(
                                 hDupedToken,
                                 null,
                                 App + " " + AppParameters,
                                 ref oSecurityAttributes, ref oSecurityAttributes,
                                 false, 0, IntPtr.Zero,
                                 AppPath, ref oStartupInfo, ref oProcessInformation
                           );

            if (result)
            {
                try
                {
                    int ProcessID = oProcessInformation.dwProcessID;

                    try
                    {
                        ResultProcess = System.Diagnostics.Process.GetProcessById(ProcessID);
                    }
                    catch
                    {
                        ResultProcess = null;
                    }
                }
                catch (Exception ex)
                {
                    ResultProcess = null;
                }
            }
        }
        catch
        {
            ResultProcess = null;
        }
        finally
        {
            if (oProcessInformation.hProcess != IntPtr.Zero)
                NativeProcessAPI.CloseHandle(oProcessInformation.hProcess);
            if (oProcessInformation.hThread != IntPtr.Zero)
                NativeProcessAPI.CloseHandle(oProcessInformation.hThread);
            if (hDupedToken != IntPtr.Zero)
                NativeProcessAPI.CloseHandle(hDupedToken);
        }

        return ResultProcess;
    }

Any ideas how I could start my processes in the user's contexts and not in the context of LocalSystem?

Thanks a lot!

2
Have you checked the %USERPFORILE% environment variable? Environment.GetEnvironmentVariable("USERPROFILE", EnvironmentVariableTarget.User)MrPaulch
Quote from MSDN: "CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key. Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser. Be sure to call UnloadUserProfile after the new process exits."Hans Passant
Thanks for your answers... I have tried Environment.GetEnvironmentVariable("USERPROFILE", EnvironmentVariableTarget.User) but it returns an empty stringVibrationz
Thanks for the hint on loadProfile - I will check that. My first test was not successful because writing in the current user's registry failed... I'm gonna check further WHY this happened... maybe this does the trick...Vibrationz
Ok I have checked LoadUserProfile but unfortunately it didn't solve my problems :-( LoadUserProfiles runs successfully without errors and returns true but it does not change the overall behaviour... I can not read from the current user's registry and I can not write to it, too... well, at least maybe I can solve this by using HKEY_Users after getting the SID from WindowsIdentity.GetCurrent().UserVibrationz

2 Answers

1
votes

Leaving this here for anyone else wondering how to do this: CreateEnvironmentBlock is what you need to use.

DuplicateTokenEx(userToken, MAXIMUM_ALLOWED | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, IntPtr.Zero, SecurityIdentification, TokenPrimary, out dupUserToken);
CreateEnvironmentBlock(out envBlock, dupUserToken, false);
CreateProcessAsUserW(dupUserToken, null, cmdLine, IntPtr.Zero, IntPtr.Zero, false,
            (uint)(CreateProcessFlags.CREATE_NEW_CONSOLE | CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT),
            envBlock, processDir, ref startupInfo, out procInfo);
0
votes

Ok I have found a workaround: I switched to using the USERS hive instead of the CURRENT_USER hive by using the SID provided by WindowsIdentity:

Microsoft.Win32.Registry.Users.OpenSubKey(System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString() + ..., true)

This works perfectly although it feels a bit uncomfortable to get environment variables from the user's "environment" and "volatile environment" registry paths instead of just using .Net's built-in functions...

But thanks a lot for your help ;-)

EDIT: I will not mark this as an answer because it is a) my own solution and b) just a workaround