static void Main(string[] args) { // 종료시 메시지를 표시하기 위한 스트링빌더입니다. StringBuilder sbLog = new StringBuilder(); // 본 프로그램은 조용히 실행시키는게 목적이므로 오류가 없는 한 조용히 넘기려고 합니다. // 오류가 잡히면 메시지창을 표시하게 하고자 합니다. bool hasError = false; //디버그 컴파일 상태인 경우 안내 문구를 추가합니다. if (IS_DEBUG) { sbLog.AppendLine("Program compiled in DEBUG mode. MessageBox will appear everytime."); hasError = true; // 또한 메시지박스가 무조건 표시되도록 합니다. } //if (IS_DEBUG) //{ // uint exitCode = 0xF8A432EB;// (-123456789)를 32비트 안에 표현 // // int intExitCode = 1<<(32-1) | (int)(exitCode & 0x7FFFFFFF); // // sbLog.AppendLine(intExitCode); //} if (args.Length <= 0) { //오류가 있습니다. hasError = true; //아무 명령도 오지 않았으므로 도움말을 표시합니다. sbLog.AppendLine(getHelp("There is nothing to do.")); } else { int nCmdStart = 0; bool haveToWaitForFinish = STR_CMD_WAIT.Equals(STR_CMD_DEFAULT); if (args[nCmdStart].StartsWith("/")) { // 첫 매개변수가 /로 시작합니다. LAUNCH인지 WAIT인지 살핍니다. String cmdToLaunchOrWait = args[nCmdStart]; // StringComparison.OrdinalIgnoreCase는 문화적 차이나 지역화에 신경쓰지 않는 비교방식을 채용합니다. if (STR_CMD_LAUNCH.Equals(cmdToLaunchOrWait, StringComparison.OrdinalIgnoreCase)) { haveToWaitForFinish = false; } else if (STR_CMD_WAIT.Equals(cmdToLaunchOrWait, StringComparison.OrdinalIgnoreCase)) { haveToWaitForFinish = true; } else { //명령어가 하나도 맞지 않는 경우 기본 명령으로 회귀. sbLog.AppendLine("WARNING: No matching commands found for " + cmdToLaunchOrWait + ". defaulting to " + STR_CMD_DEFAULT); } // 다음 매개변수로 진행합니다. nCmdStart++; } else { // 첫 매개변수가 /로 시작하지 않습니다. 그대로 사용합니다. // 기본값은 STR_CMD_DEFAULT에 정의되어 있습니다. } // 현재 매개변수 위치 이후의 매개변수, 실제로 실행할 명령만 셉니다. sbLog.AppendLine("# of args to launch: " + (args.Length - nCmdStart)); if (args.Length >= 1 + nCmdStart) { //매개변수로 전달받은 프로그램의 이름입니다. String strTargetProgramName = args[0 + nCmdStart]; if (!(strTargetProgramName.Contains(@":\") || strTargetProgramName.Contains(":/"))) { //만약 절대 경로가 아니라면 절대 경로로 변환해줍니다. //http://stackoverflow.com/a/4796339 //현재 프로세스의 경로를 활용합니다. string strProcessPath = System.IO.Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName); string strAbsoluteProgramPath = System.IO.Path.Combine(strProcessPath, strTargetProgramName); strTargetProgramName = System.IO.Path.GetFullPath((new Uri(strAbsoluteProgramPath)).LocalPath); } //이 프로그램의 매개변수입니다. String strArgsToHandover = ""; //프로그램을 실행시킬 준비를 합니다. if (IF_DEPRECATED_HACK) { // TODO: 히든모드야 누구든 잘하고 잘되지만, PREVENT FOCUS UTIL의 경우 WaitForExit이 가능할지 못할지는 모르겠다.. ㅠㅠ System.Diagnostics.ProcessStartInfo targetProgram = new System.Diagnostics.ProcessStartInfo(strTargetProgramName); targetProgram.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; sbLog.AppendLine("Preparing to launch in minized mode: " + strTargetProgramName); if (args.Length >= 2 + nCmdStart) { // 매개변수의 갯수가 2개 이상인경우 다음 매개변수를 모두 합칩니다. StringBuilder sbArgs = new StringBuilder(); for (int cur = 1 + nCmdStart; cur < args.Length; cur++) { //첫번째가 아닌 경우 사이에 빈 칸을 넣어줍니다. if (cur != 1 + nCmdStart) { sbArgs.Append(' '); } sbArgs.Append(args[cur]); } // 문자열을 완성시켜 전달해줍니다. strArgsToHandover = sbArgs.ToString(); targetProgram.Arguments = strArgsToHandover; sbLog.AppendLine(STR_INDENT + "with parameters: " + strArgsToHandover); } try { // 프로세스를 가동 준비합니다. System.Diagnostics.Process procTargetProgram = new System.Diagnostics.Process { StartInfo = targetProgram }; // Exit 이벤트를 걸어서 현재 스레드를 기다리지 않게 합니다. 그러나 여기에선 기다려야합니다. 주석처리 //https://msdn.microsoft.com/en-us/library/system.diagnostics.process.exited.aspx //procTargetProgram.EnableRaisingEvents = true; //procTargetProgram.Exited += new EventHandler(myProcess_Exited); // 프로세스를 시작합니다. procTargetProgram.Start(); sbLog.AppendLine("Launched " + strTargetProgramName + ' ' + strArgsToHandover); //기다려야한다면 기다립니다. if (haveToWaitForFinish) { procTargetProgram.WaitForExit(); sbLog.AppendLine("Finished. Exit code was " + procTargetProgram.ExitCode); } } catch (Exception e) { hasError = true; sbLog.AppendLine("ERROR launching " + strTargetProgramName + ' ' + strArgsToHandover); sbLog.AppendLine(STR_INDENT + e.Message); } } else { sbLog.AppendLine("Preparing to launch in minized mode: " + strTargetProgramName); if (args.Length >= 2 + nCmdStart) { // 매개변수의 갯수가 2개 이상인경우 다음 매개변수를 모두 합칩니다. StringBuilder sbArgs = new StringBuilder(); for (int cur = 1 + nCmdStart; cur < args.Length; cur++) { //첫번째가 아닌 경우 사이에 빈 칸을 넣어줍니다. if (cur != 1 + nCmdStart) { sbArgs.Append(' '); } sbArgs.Append(args[cur]); } // 문자열을 완성시켜 전달해줍니다. strArgsToHandover = sbArgs.ToString(); sbLog.AppendLine(STR_INDENT + "with parameters: " + strArgsToHandover); } // 프로그램이 존재하는지 확인합니다. if (!System.IO.File.Exists(strTargetProgramName)) { hasError = true; sbLog.AppendLine("ERROR, Program not found."); sbLog.AppendLine(STR_INDENT + "Check program's path and try again."); sbLog.AppendLine(STR_INDENT + "Surrounding program's path with double-quote marks may fix this error."); } else { // 프로그램이 존재하므로 시작시킵니다. // unManaged 코드에서는 예외를 처리할 수 없기 때문에 경우의 수를 직접 찾아야합니다. ProcessLaunchInfo pliLaunch = PreventFocusUtil.startProcessNoActivate(strTargetProgramName, strArgsToHandover, haveToWaitForFinish); if (pliLaunch.bLaunched) { if (haveToWaitForFinish) { sbLog.AppendLine("Finished. Exit code was " + pliLaunch.exitCode); } else { sbLog.AppendLine("Launched."); } } else { hasError = true; sbLog.AppendLine("ERROR launching " + strTargetProgramName + ' ' + strArgsToHandover); sbLog.AppendLine(STR_INDENT + "Exit code was " + pliLaunch.exitCode); sbLog.AppendLine(STR_INDENT + "Check required privileges and other things"); sbLog.AppendLine(STR_INDENT + "that may prevent program from launching."); } } } } } //모든 명령이 끝났습니다. if (hasError) { //오류가 있었습니다. 메시지 상자를 표시합니다. System.Windows.Forms.MessageBox.Show(sbLog.ToString()); } else { //오류가 없었다면 그냥 종료합니다. } }
public static ProcessLaunchInfo startProcessNoActivate(string program, string arguments, bool haveToWaitFinishes) { // 프로그램의 작업 디렉토리를 확인합니다. string workDir = System.IO.Path.GetDirectoryName(program); if (workDir.Length <= 0) { // 작업 디렉토리 문자열의 길이가 0인 경우 // 호출 디렉토리와 같은 곳에 있다는 뜻이므로 // 실행 불능 오류 발생하는 빈 칸 대신 null을 넣습니다. workDir = null; } // 실행 정보를 보관했다가 실행 이후 활용하려합니다. ProcessLaunchInfo pliProcess = new ProcessLaunchInfo(); STARTUPINFO si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.dwFlags = STARTF_USESHOWWINDOW; // 이 부분이 중요합니다. 최소화시킨 상태에서 포커스도 갖지 않도록 호출하게 합니다. si.wShowWindow = SW_SHOWMINNOACTIVE; PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); // 새 프로세스와 그의 주 스레드를 생성합니다. // 호출하는 프로세스의 보안 컨텍스트를 상속받습니다. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx pliProcess.bLaunched = CreateProcess( program, // 실행시킬 프로그램 이름 arguments, // 실행시킬 명령행 (최대 MAX_PATH:260 자) IntPtr.Zero, // 보안 속성(SECURITY_ATTRIBUTES) 구조를 가리키는 포인터. 프로세스용 IntPtr.Zero, // 보안 속성(SECURITY_ATTRIBUTES) 구조를 가리키는 포인터. 스레드용 true, // 호출하는 핸들의 상속 여부 0, // 프로세스 생성 플래그. 여기에서 프로세스 우선순위도 지정 가능 IntPtr.Zero, // 실행 환경을 나타내는 포인터. 유니코드/ANSI 등의 인코딩의 영향을 줌. (NULL인 경우 호출 프로세스의 실행환경 상속) workDir, // 작업 디렉토리. (NULL인 경우 호출 프로세스의 위치를 작업 디렉토리로 사용) ref si, // 실행 방법에 대한 정보 out pi // 프로세스에 대한 정보 ); // 종료를 기다려야하는 경우 기다려줍니다. if (haveToWaitFinishes) { ProcessWaitHandle waitable = new ProcessWaitHandle(pi.hProcess); waitable.WaitOne(); } // pInvoke를 통해 프로세스의 exitCode를 가져옵니다. //http://www.pinvoke.net/default.aspx/kernel32.getexitcodeprocess uint exitCode = 0; GetExitCodeProcess(pi.hProcess, out exitCode); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); // ExitCode 속성은 부호 있는 32비트 정수입니다. // 이 속성이 음수 값을 반환하지 않게 하려면 // 0x80000000 보다 크거나 같은 수를 쓰지 마십시오. //https://msdn.microsoft.com/en-us/library/system.environment.exitcode(v=vs.110).aspx if (exitCode >= 0x80000000) { // 2의 보수로 이루어진 음수 표현 체계에 맞게 보정해줍니다. // 최상위 비트를 1로 만든 다음, // unsigned int인 exitCode에서 0x7FFFFFFF로 걸러낸 값을 int에 맞게 우겨넣습니다. pliProcess.exitCode = 1 << (32 - 1) | (int)(exitCode & 0x7FFFFFFF); } else { pliProcess.exitCode = (int)exitCode; } return(pliProcess); }