1

I really want to be able to do file redirection via shared pipes, and filter the list of inherited handles to only those stdout/stderr/stdin handles, and as far as I can find STARTUPINFOEX and extended attributes are the way to do that. I also need to be able to launch as a different user.

  • CreateProcess works for me when taking STARTUPINFOEX, and either passing no attributes, or a single attribute (either to change the parent, or filter the inherited handles).
  • CreateProcessWithLogonW works when taking STARTUPINFOEX, but only if I leave off EXTENDED_STARTUPINFO_PRESENT from the creation flags (basically treating STARTUPINFOEX as STARTUPINFO, even though startupinfo.cb is the full struct).
  • If I add EXTENDED_STARTUPINFO_PRESENT, I get the ever helpful "The parameter is incorrect", even without using any attributes (which works on CreateProcess)

The below works, until you uncomment // | NativeMethods.EXTENDED_STARTUPINFO_PRESENT,

    public static void CreateProcessExtended(
        string userName,
        SecureString password)
    {
        var startupInfoEx = new NativeMethods.STARTUPINFOEX { StartupInfo = new NativeMethods.STARTUPINFO() };
        startupInfoEx.StartupInfo.dwFlags = NativeMethods.STARTF_USESHOWWINDOW;
        startupInfoEx.StartupInfo.wShowWindow = 0; // SW_HIDE
        NativeMethods.PROCESS_INFORMATION processInfo;

        startupInfoEx.StartupInfo.cb = Marshal.SizeOf(startupInfoEx);
        IntPtr passwordPtr = Marshal.SecureStringToCoTaskMemUnicode(password);
        bool retVal = NativeMethods.CreateProcessWithLogonW(
            userName,
            null,
            passwordPtr,
            NativeMethods.LogonFlags.LOGON_WITH_PROFILE,
            null,
            @"C:\windows\system32\notepad.exe",
            (uint)NativeMethods.CREATE_NO_WINDOW | NativeMethods.CREATE_SUSPENDED,// | NativeMethods.EXTENDED_STARTUPINFO_PRESENT,
            IntPtr.Zero,
            null,
            ref startupInfoEx,
            out processInfo);
        if (!retVal)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    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 dwYSize;
        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, CharSet = CharSet.Unicode)]
    public struct STARTUPINFOEX
    {
        public STARTUPINFO StartupInfo;
        public IntPtr lpAttributeList;
    }

    [return: MarshalAs(UnmanagedType.Bool)]
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [DllImport("Advapi32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
    public static extern bool CreateProcessWithLogonW(
        string userName,
        string domain,
        IntPtr password,
        LogonFlags logonFlags,
        string lpApplicationName,
        string lpCommandLine,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        string lpCurrentDirectory,
        [In] ref STARTUPINFOEX lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

Edit.1 Sent in a filtered handle attribute list in case it was a contract difference that needed the extended attributes to be something other than IntPtr.Zero. Still failed. Again, worked in CreateProcess, failed in CreateProcessWithLogonW using these new sigs:

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool UpdateProcThreadAttribute(
        IntPtr lpAttributeList, uint dwFlags, uint Attribute, IntPtr lpValue,
        IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool InitializeProcThreadAttributeList(
        IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
zackrun
  • 11
  • 2
  • Speculation: It's probably expecting an [initialized attribute list](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683481(v=vs.85).aspx) when that flag is present. – 500 - Internal Server Error Jan 24 '15 at 21:06
  • Wasn't required in CreateProcess (attributeList at IntPtr.Zero was fine), but to make sure it wasn't a contract difference I tried it. Still failed when initializing the list and giving a file filter. updated the question. – zackrun Jan 25 '15 at 03:08
  • My advice in these situations is to do the work first in a C++ test bed so that you know there are no marshalling issues. – David Heffernan Jan 25 '15 at 08:54
  • uhhh...hmmm...so CreateProcessWithLogonW doesn't take a bInheritHandles member like CreateProcess. Somehow I missed that over and over, and I imagine that is why filtering handles doesn't work. I'll see if the other attributeList options work (and if so, maybe I'll see if MSDN can update). I'll post an answer once I verify, IF it works. – zackrun Jan 27 '15 at 08:19

0 Answers0