OSDN Git Service

na-get-lib,パッケージコレクションのパッケージが既にインストール済みの場合には処理しない処理が正常動作していなかった不具合を修正
[applistation/AppliStation.git] / na-get-lib / NaGet.InteropServices / CreateProcessCaller.cs
index cfb1d5c..af62a3a 100644 (file)
@@ -1,9 +1,18 @@
-using System;\r
+using System;\r
 using System.Diagnostics;\r
 using System.Runtime.InteropServices;\r
 \r
 namespace NaGet.InteropServices\r
 {\r
+       /// <summary>\r
+       /// Win32 API の<code>CreateProcess</code>を直に叩くためのクラス。\r
+       /// </summary>\r
+       /// <remarks>\r
+       /// .NETのProcess.Startは、<code>CreateProcess(NULL, cmdLine, ...)</code>\r
+       /// のように第一引数が<code>NULL</code>に相当する呼び出しが、\r
+       /// *厳密な意味*でできない。厳密な意味で、\r
+       /// これと同じ呼び出しを実現する必要があるときに使われる。\r
+       /// </remarks>\r
        public class CreateProcessCaller : IDisposable\r
        {\r
                #region Win32API\r
@@ -73,12 +82,75 @@ namespace NaGet.InteropServices
                [return: MarshalAs(UnmanagedType.Bool)]\r
                static extern bool GetExitCodeProcess(IntPtr hProcess, out int lpExitCode);\r
                \r
+               #region 優先度関連\r
+               \r
+               [DllImport("kernel32.dll")]\r
+               static extern uint GetPriorityClass(IntPtr hProcess);\r
+               \r
+               [DllImport("kernel32.dll")]\r
+               static extern bool SetPriorityClass(IntPtr hProcess, uint dwPriorityClass);\r
+               \r
+               #endregion\r
+               \r
+               #region 権限降格関連\r
+               \r
+               [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError=true)]\r
+               [return: MarshalAs(UnmanagedType.Bool)]\r
+               static extern bool CreateProcessWithTokenW(\r
+            IntPtr hToken,\r
+            uint dwLogonFlags,\r
+            string lpApplicationName, string lpCommandLine,\r
+            uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,\r
+            [In] ref STARTUPINFO lpStartupInfo,\r
+            out PROCESS_INFORMATION lpProcessInfo);\r
+\r
+               // For Windows Mobile, replace user32.dll with coredll.dll\r
+               [DllImport("user32.dll", SetLastError = true)]\r
+               static extern IntPtr FindWindow(string lpClassName, string lpWindowName);\r
+               \r
+               [DllImport("user32.dll", SetLastError=true)]\r
+               static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);\r
+                               \r
+               [DllImport("advapi32.dll", SetLastError=true)]\r
+               [return: MarshalAs(UnmanagedType.Bool)]\r
+               static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);\r
+               \r
+               [DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]\r
+               extern static bool DuplicateTokenEx(\r
+                       IntPtr hExistingToken,\r
+                       uint dwDesiredAccess,\r
+                       /* ref SECURITY_ATTRIBUTES lpTokenAttributes, */\r
+                       IntPtr lpTokenAttributes,\r
+                       /* SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, */\r
+                       uint ImpersonationLevel,\r
+                       /* TOKEN_TYPE TokenType, */\r
+                       uint TokenType,\r
+                       out IntPtr phNewToken );\r
+               \r
+               [DllImport("kernel32.dll")]\r
+               static extern IntPtr GetEnvironmentStrings();\r
+\r
+               [DllImport("kernel32.dll")]\r
+               static extern bool FreeEnvironmentStrings(IntPtr lpszEnvironmentBlock);\r
+               \r
+               #endregion\r
+               \r
                #endregion\r
                \r
                STARTUPINFO si;\r
                PROCESS_INFORMATION pi;\r
                \r
+               /// <summary>\r
+               /// プロセスを生成する\r
+               /// </summary>\r
+               /// <param name="procInfo">プロセス起動情報。\r
+               /// なお、<code>procInfo.UseShellExecute</code>は必ずfalseでなければならない</param>\r
                public CreateProcessCaller(ProcessStartInfo procInfo)\r
+                       : this(procInfo, false)\r
+               {\r
+               }\r
+               \r
+               public CreateProcessCaller(ProcessStartInfo procInfo, bool runAsNormalUser)\r
                {\r
                        if (procInfo.UseShellExecute) {\r
                                throw new ArgumentException("UseShellExecute must be false");\r
@@ -91,27 +163,113 @@ namespace NaGet.InteropServices
                        if (procInfo.CreateNoWindow) dwCreationFlags |= 0x08000000; // CREATE_NO_WINDOW\r
                        string lpCurrentDirectory = (System.IO.Directory.Exists(procInfo.WorkingDirectory))? procInfo.WorkingDirectory : null;\r
                        \r
-                       bool retValue = CreateProcess(lpFileName, procInfo.Arguments,\r
+                       bool retValue;\r
+                       if (runAsNormalUser && NaGet.Utils.IsAdministrators()) {\r
+                               retValue = _CreateProcessAsNormalUser(lpFileName, procInfo.Arguments,\r
                                                      IntPtr.Zero, IntPtr.Zero,\r
                                                      false, dwCreationFlags,\r
                                                      IntPtr.Zero, lpCurrentDirectory, ref si, out pi);\r
+                       } else {\r
+                               retValue = CreateProcess(lpFileName, procInfo.Arguments,\r
+                                                     IntPtr.Zero, IntPtr.Zero,\r
+                                                     false, dwCreationFlags,\r
+                                                     IntPtr.Zero, lpCurrentDirectory, ref si, out pi);\r
+                       }\r
+                       \r
                        if (! retValue) {\r
                                throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());\r
                        }\r
-                       CloseHandle(pi.hThread);\r
+                       if (pi.hThread != IntPtr.Zero) {\r
+                               CloseHandle(pi.hThread);\r
+                       }\r
                }\r
                \r
+               private static bool _CreateProcessAsNormalUser(string lpApplicationName,\r
+                       string lpCommandLine,\r
+                       /* ref SECURITY_ATTRIBUTES lpProcessAttributes, */\r
+                       IntPtr lpProcessAttributes,\r
+                       /* ref SECURITY_ATTRIBUTES lpThreadAttributes, */\r
+                       IntPtr lpThreadAttributes,\r
+                       bool bInheritHandles,\r
+                       uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,\r
+                       [In] ref STARTUPINFO lpStartupInfo,\r
+                       out PROCESS_INFORMATION lpProcessInformation)\r
+               {\r
+                       /*\r
+                        * cf. nsWindowsRestart.cpp#LaunchAsNormalUser\r
+                        * 動作チェックせず\r
+                        */\r
+                       \r
+                       \r
+                       lpProcessInformation = new PROCESS_INFORMATION(); /* fake */\r
+                       try {\r
+                               uint result;\r
+                               \r
+                               IntPtr hwndShell = FindWindow("Progman", null);\r
+                               uint dwProcessId;\r
+                               result = GetWindowThreadProcessId(hwndShell, out dwProcessId);\r
+                               if (result == 0) {\r
+                                       throw new System.ComponentModel.Win32Exception();\r
+                               }\r
+                               \r
+                               Process procShell = Process.GetProcessById((int) dwProcessId);\r
+                               if (procShell == null) {\r
+                                       return false;\r
+                               }\r
+                               \r
+                               IntPtr hTokenHandle, hNewToken;\r
+                               // bool ok = OpenProcessToken(hProcessShell, MAXIMUM_ALLOWED, out hTokenHandle);\r
+                               bool ok = OpenProcessToken(procShell.Handle, 0x02000000, out hTokenHandle);\r
+                               if (!ok) return false;\r
+                               // ok = DuplicateTokenEx(hTokenShell, MAXIMUM_ALLOWED, null, SecurityDelegation, TokenPrimary, out hNewToken);\r
+                               ok = DuplicateTokenEx(hTokenHandle, 0x02000000, IntPtr.Zero, 3, 1, out hNewToken);\r
+                               CloseHandle(hTokenHandle);\r
+                               if (!ok) return false;\r
+\r
+                               IntPtr myenv = GetEnvironmentStrings();\r
+                               ok = CreateProcessWithTokenW(hNewToken,\r
+                                                            0, // profile is already loaded\r
+                                                            lpApplicationName,\r
+                                                            lpCommandLine,\r
+                                                            dwCreationFlags,\r
+                                                            myenv,\r
+                                                            lpCurrentDirectory,\r
+                                                            ref lpStartupInfo,\r
+                                                            out lpProcessInformation);\r
+                               if (myenv != IntPtr.Zero) {\r
+                                       FreeEnvironmentStrings(myenv);\r
+                               }\r
+                               CloseHandle(hNewToken);\r
+                               \r
+                               return ok;\r
+                       } catch {\r
+                               return false;\r
+                       }\r
+               }\r
+               \r
+               /// <summary>\r
+               /// 関連付けられたプロセスが終了するまで、最大指定したミリ秒間待機。 \r
+               /// </summary>\r
+               /// <param name="timeout">最大待機時間(ミリ秒単位)</param>\r
+               /// <returns>終了コード</returns>\r
                public uint WaitForExit(uint timeout)\r
                {\r
                        return WaitForSingleObject(pi.hProcess, timeout);\r
                }\r
                \r
+               /// <summary>\r
+               /// 関連付けられたプロセスが終了するまで無期限に待機。\r
+               /// </summary>\r
+               /// <returns>終了コード</returns>\r
                public uint WaitForExit()\r
                {\r
                        // return WaitForExit(INFINITE)\r
                        return WaitForExit(0xFFFFFFFF);\r
                }\r
                \r
+               /// <summary>\r
+               /// 終了コード\r
+               /// </summary>\r
                public int ExitCode {\r
                        get {\r
                                int lpExitCode;\r
@@ -122,10 +280,30 @@ namespace NaGet.InteropServices
                        }\r
                }\r
                \r
+               /// <summary>\r
+               /// プロセスの優先度を取得または設定します。\r
+               /// </summary>\r
+               public ProcessPriorityClass PriorityClass {\r
+                       get {\r
+                               return (ProcessPriorityClass) GetPriorityClass(pi.hProcess);\r
+                       }\r
+                       set {\r
+                               SetPriorityClass(pi.hProcess, (uint) value);\r
+                       }\r
+               }\r
+               \r
+               /// <summary>\r
+               /// プロセスのハンドルを開放する\r
+               /// </summary>\r
                public void Dispose()\r
                {\r
-                       CloseHandle(pi.hProcess);\r
+                       if (pi.hProcess != IntPtr.Zero) {\r
+                               CloseHandle(pi.hProcess);\r
+                       }\r
+                       GC.SuppressFinalize(this);\r
                }\r
                \r
+               \r
+               \r
        }\r
 }\r