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);
}
}
}