using System; using System.Diagnostics; using System.Runtime.InteropServices; namespace NaGet.InteropServices { /// /// Win32 API のCreateProcessを直に叩くためのクラス。 /// /// /// .NETのProcess.Startは、CreateProcess(NULL, cmdLine, ...) /// のように第一引数がNULLに相当する呼び出しが、 /// *厳密な意味*でできない。厳密な意味で、 /// これと同じ呼び出しを実現する必要があるときに使われる。 /// public class CreateProcessCaller : IDisposable { #region Win32API /* [StructLayout(LayoutKind.Sequential)] internal struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } */ [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] struct STARTUPINFO { public Int32 cb; string lpReserved; string lpDesktop; string lpTitle; Int32 dwX; Int32 dwY; Int32 dwXSize; Int32 dwYSize; Int32 dwXCountChars; Int32 dwYCountChars; Int32 dwFillAttribute; Int32 dwFlags; Int16 wShowWindow; Int16 cbReserved2; IntPtr lpReserved2; IntPtr hStdInput; IntPtr hStdOutput; IntPtr hStdError; } [DllImport("kernel32.dll", CharSet= CharSet.Auto, SetLastError=true)] static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, /* ref SECURITY_ATTRIBUTES lpProcessAttributes, */ IntPtr lpProcessAttributes, /* ref SECURITY_ATTRIBUTES lpThreadAttributes, */ IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32", SetLastError=true, ExactSpelling=true)] static extern UInt32 WaitForSingleObject(IntPtr handle, UInt32 milliseconds); [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetExitCodeProcess(IntPtr hProcess, out int lpExitCode); #region 優先度関連 [DllImport("kernel32.dll")] static extern uint GetPriorityClass(IntPtr hProcess); [DllImport("kernel32.dll")] static extern bool SetPriorityClass(IntPtr hProcess, uint dwPriorityClass); #endregion #region 権限降格関連 [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool CreateProcessWithTokenW( IntPtr hToken, uint dwLogonFlags, string lpApplicationName, string lpCommandLine, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInfo); // For Windows Mobile, replace user32.dll with coredll.dll [DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", SetLastError=true)] static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("advapi32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] extern static bool DuplicateTokenEx( IntPtr hExistingToken, uint dwDesiredAccess, /* ref SECURITY_ATTRIBUTES lpTokenAttributes, */ IntPtr lpTokenAttributes, /* SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, */ uint ImpersonationLevel, /* TOKEN_TYPE TokenType, */ uint TokenType, out IntPtr phNewToken ); [DllImport("kernel32.dll")] static extern IntPtr GetEnvironmentStrings(); [DllImport("kernel32.dll")] static extern bool FreeEnvironmentStrings(IntPtr lpszEnvironmentBlock); #endregion #endregion STARTUPINFO si; PROCESS_INFORMATION pi; /// /// プロセスを生成する /// /// プロセス起動情報。 /// なお、procInfo.UseShellExecuteは必ずfalseでなければならない public CreateProcessCaller(ProcessStartInfo procInfo) : this(procInfo, false) { } public CreateProcessCaller(ProcessStartInfo procInfo, bool runAsNormalUser) { if (procInfo.UseShellExecute) { throw new ArgumentException("UseShellExecute must be false"); } si.cb = Marshal.SizeOf(si); string lpFileName = (string.IsNullOrEmpty(procInfo.FileName))? null : procInfo.FileName; uint dwCreationFlags = 0x0020; // NORMAL_PRIORITY_CLASS if (procInfo.CreateNoWindow) dwCreationFlags |= 0x08000000; // CREATE_NO_WINDOW string lpCurrentDirectory = (System.IO.Directory.Exists(procInfo.WorkingDirectory))? procInfo.WorkingDirectory : null; bool retValue; if (runAsNormalUser && NaGet.Utils.IsAdministrators()) { retValue = _CreateProcessAsNormalUser(lpFileName, procInfo.Arguments, IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, IntPtr.Zero, lpCurrentDirectory, ref si, out pi); } else { retValue = CreateProcess(lpFileName, procInfo.Arguments, IntPtr.Zero, IntPtr.Zero, false, dwCreationFlags, IntPtr.Zero, lpCurrentDirectory, ref si, out pi); } if (! retValue) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } if (pi.hThread != IntPtr.Zero) { CloseHandle(pi.hThread); } } private static bool _CreateProcessAsNormalUser(string lpApplicationName, string lpCommandLine, /* ref SECURITY_ATTRIBUTES lpProcessAttributes, */ IntPtr lpProcessAttributes, /* ref SECURITY_ATTRIBUTES lpThreadAttributes, */ IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation) { /* * cf. nsWindowsRestart.cpp#LaunchAsNormalUser * 動作チェックせず */ lpProcessInformation = new PROCESS_INFORMATION(); /* fake */ try { uint result; IntPtr hwndShell = FindWindow("Progman", null); uint dwProcessId; result = GetWindowThreadProcessId(hwndShell, out dwProcessId); if (result == 0) { throw new System.ComponentModel.Win32Exception(); } Process procShell = Process.GetProcessById((int) dwProcessId); if (procShell == null) { return false; } IntPtr hTokenHandle, hNewToken; // bool ok = OpenProcessToken(hProcessShell, MAXIMUM_ALLOWED, out hTokenHandle); bool ok = OpenProcessToken(procShell.Handle, 0x02000000, out hTokenHandle); if (!ok) return false; // ok = DuplicateTokenEx(hTokenShell, MAXIMUM_ALLOWED, null, SecurityDelegation, TokenPrimary, out hNewToken); ok = DuplicateTokenEx(hTokenHandle, 0x02000000, IntPtr.Zero, 3, 1, out hNewToken); CloseHandle(hTokenHandle); if (!ok) return false; IntPtr myenv = GetEnvironmentStrings(); ok = CreateProcessWithTokenW(hNewToken, 0, // profile is already loaded lpApplicationName, lpCommandLine, dwCreationFlags, myenv, lpCurrentDirectory, ref lpStartupInfo, out lpProcessInformation); if (myenv != IntPtr.Zero) { FreeEnvironmentStrings(myenv); } CloseHandle(hNewToken); return ok; } catch { return false; } } /// /// 関連付けられたプロセスが終了するまで、最大指定したミリ秒間待機。 /// /// 最大待機時間(ミリ秒単位) /// 終了コード public uint WaitForExit(uint timeout) { return WaitForSingleObject(pi.hProcess, timeout); } /// /// 関連付けられたプロセスが終了するまで無期限に待機。 /// /// 終了コード public uint WaitForExit() { // return WaitForExit(INFINITE) return WaitForExit(0xFFFFFFFF); } /// /// 終了コード /// public int ExitCode { get { int lpExitCode; if (! GetExitCodeProcess(pi.hProcess, out lpExitCode) ) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } return lpExitCode; } } /// /// プロセスの優先度を取得または設定します。 /// public ProcessPriorityClass PriorityClass { get { return (ProcessPriorityClass) GetPriorityClass(pi.hProcess); } set { SetPriorityClass(pi.hProcess, (uint) value); } } /// /// プロセスのハンドルを開放する /// public void Dispose() { if (pi.hProcess != IntPtr.Zero) { CloseHandle(pi.hProcess); } GC.SuppressFinalize(this); } } }