CreateProcessAsUser won't open my winform in noninteractive session

0

I am trying to launch a Winforms application process from within a windows-service. So far i have tried using TopShelf and spawning a Winforms message box worked. I had to stop using for some reasons TopShelf and now i keep getting this error in the Winforms Application.

Showing a modal dialog box or form when the application is not running in UserInteractive mode is not a valid operation. Specify the ServiceNotification or DefaultDesktopOnly style to display a notification from a service application.

P.S I have made a similar question here and one reply was to use PInvoke.I did not since TopShelf just worked but i do not know how. (If it is duplicate i will remove this post and accept the previous one - but i do not know for sure if this is the case now).

I have tried using the Win32 API method CreateProcessAsUser ,and while i can open the process when it tries to show the dialog of the form i am still getting the same error.

Win32 Method

public static void Do() {
            IntPtr impToken = WindowsIdentity.GetCurrent().Token;
            IntPtr clonedToken = IntPtr.Zero;
            Win32.PROCESS_INFORMATION pi = new Win32.PROCESS_INFORMATION();
            try {
                Win32.SECURITY_ATTRIBUTES sa = new Win32.SECURITY_ATTRIBUTES();
                sa.Length = Marshal.SizeOf(sa);
                bool result = Win32.DuplicateTokenEx(impToken,
                    Win32.GENERIC_ALL_ACCESS,
                    ref sa,
                    (int)Win32.SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)Win32.TOKEN_TYPE.TokenPrimary,
                    ref clonedToken);
                if (!result) {
                    return;
                }
                Win32.STARTUPINFO info = new Win32.STARTUPINFO();
                info.cb = Marshal.SizeOf(info);
                info.lpDesktop = string.Empty;
                result = Win32.CreateProcessAsUser(clonedToken, Constants.USER.PROCESS_FILEPATH, string.Empty,
                    ref sa, ref sa, false, 0, IntPtr.Zero, @"C:\", ref info, ref pi);
                int error = Marshal.GetLastWin32Error();
                string message = string.Format($"Create Process As User Error:{0}", error);

            } catch (Exception) {

                if (pi.hProcess != IntPtr.Zero) {
                    Win32.CloseHandle(pi.hProcess);
                }
                if (pi.hThread != IntPtr.Zero) {
                    Win32.CloseHandle(pi.hThread);
                }
                if (clonedToken != IntPtr.Zero) {
                    Win32.CloseHandle(clonedToken);
                }
            }
        }

Win32 Bindings

class Win32 {

        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO {
            public Int32 cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public Int32 dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION {
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessID;
            public Int32 dwThreadID;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        public enum SECURITY_IMPERSONATION_LEVEL {
            SecurityAnonymous,
            SecurityIdentification,
            SecurityImpersonation,
            SecurityDelegation
        }

        public enum TOKEN_TYPE {
            TokenPrimary = 1,
            TokenImpersonation
        }

        public const int GENERIC_ALL_ACCESS = 0x10000000;

        [
           DllImport("kernel32.dll",
              EntryPoint = "CloseHandle", SetLastError = true,
              CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool CloseHandle(IntPtr handle);

        [
           DllImport("advapi32.dll",
              EntryPoint = "CreateProcessAsUser", SetLastError = true,
              CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)
        ]
        public static extern bool
           CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine,
                               ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes,
                               bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment,
                               string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo,
                               ref PROCESS_INFORMATION lpProcessInformation);

        [
           DllImport("advapi32.dll",
              EntryPoint = "DuplicateTokenEx")
        ]
        public static extern bool
           DuplicateTokenEx(IntPtr hExistingToken, Int32 dwDesiredAccess,
                            ref SECURITY_ATTRIBUTES lpThreadAttributes,
                            Int32 ImpersonationLevel, Int32 dwTokenType,
                            ref IntPtr phNewToken);


    }

Winform

[STAThread]
 static void Main(){
  try {
       var form = new SomeForm();
       form.ShowDialog();

      } catch (Exception ex) {

      }
 }

So i can create the process from non interactive mode , but i still can't open a dialog.I thought that with CreateProcessAsUser i can open a winform.

Update Reading the documentation of CreateProcessAsUser i found this interesting piece of information:

By default, CreateProcessAsUser creates the new process on a noninteractive window station with a desktop that is not visible and cannot receive user input. To enable user interaction with the new process, you must specify the name of the default interactive window station and desktop, "winsta0\default", in the lpDesktop member of the STARTUPINFO structure. In addition, before calling CreateProcessAsUser, you must change the discretionary access control list (DACL) of both the default interactive window station and the default desktop. The DACLs for the window station and desktop must grant access to the user or the logon session represented by the hToken parameter.

P.S Can anyone shed some light on how i can make this work ? I have already read all those articles about using token duplication and using the win32 api.

Update I have managed to bypass the error.It seems the problem was with the STARTUPINFO.lpDesktop field.It was set as String.Empty.After i've set it to the default: @winsta0\default there is no more error.

Now having no error , form.ShowDialog does not throw any error , but it is still not visible.What else can i do ?

winforms
winapi
windows-services
interactive
asked on Stack Overflow Jul 4, 2019 by Bercovici Adrian • edited Jul 5, 2019 by Bercovici Adrian

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0