Microsoft MVP성태의 닷넷 이야기
.NET Framework: 480. C# - 배치 파일 실행하고 출력 결과를 얻는 방법 [링크 복사], [링크+제목 복사],
조회: 57438
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

(시리즈 글이 6개 있습니다.)
.NET Framework: 480. C# - 배치 파일 실행하고 출력 결과를 얻는 방법
; https://www.sysnet.pe.kr/2/0/1810

.NET Framework: 602. Process.Start의 cmd.exe에서 stdin만 redirect 하는 방법
; https://www.sysnet.pe.kr/2/0/11029

.NET Framework: 817. Process.Start로 실행한 콘솔 프로그램의 출력 결과를 얻는 방법
; https://www.sysnet.pe.kr/2/0/11870

Linux: 24. Linux/C# - 실행 파일이 아닌 스크립트 형식의 명령어를 Process.Start로 실행하는 방법
; https://www.sysnet.pe.kr/2/0/12073

닷넷: 2292. C# - 자식 프로세스의 출력이 4,096보다 많은 경우 Process.WaitForExit 호출 시 hang 현상
; https://www.sysnet.pe.kr/2/0/13707

닷넷: 2314. C# - ProcessStartInfo 타입의 Arguments와 ArgumentList
; https://www.sysnet.pe.kr/2/0/13876




C# - 배치 파일 실행하고 출력 결과를 얻는 방법

우선 배치 파일을 C#으로 실행하는 방법은 다음과 같이 간단합니다.

Process.Start("test.bat");

또는 ProcessStartInfo 타입을 이용해 복잡하게(?) 실행할 수 있습니다.

ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "test.bat";

Process proc = Process.Start(psi);

더 복잡하게 실행하는 다음의 방법도 있습니다.

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";
psi.Arguments = "/C test.bat";

Process.Start(psi);

위의 3가지 방법은 다른 듯 하지만, 실은 모두 똑같습니다. 결국 실행하면 마지막 방식으로 실행한 것처럼 cmd.exe로 "/C" 옵션을 경유해 배치 파일이 실행됩니다. 개인적으로는, 복잡하지만 제가 제어했다는 이유만으로 3번째 방법을 선호합니다.




그럼, 배치 파일을 실행한 이후 그 출력 결과를 가져오려면 어떻게 해야 할까요?

이를 위해 다음과 같이 Process.StandardOutput 속성을 이용해 ReadToEnd를 호출하면?

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";
psi.Arguments = "/C test.bat";

Process proc = Process.Start(psi);
proc.WaitForExit();

string txt = proc.StandardOutput.ReadToEnd();

아쉽게도 이런 오류가 발생합니다.

System.InvalidOperationException was unhandled
  _HResult=-2146233079
  _message=StandardOut has not been redirected or the process hasn't started yet.
  HResult=-2146233079
  IsTransient=false
  Message=StandardOut has not been redirected or the process hasn't started yet.
  Source=System
  StackTrace:
       at System.Diagnostics.Process.get_StandardOutput()
       at ConsoleApplication1.Program.CallType1() in e:\...\ConsoleApplication1\Program.cs:line 52
       at ConsoleApplication1.Program.Main(String[] args) in e:\...\ConsoleApplication1\Program.cs:line 15
  InnerException: 

즉, StandardOutput 속성을 사용하려면 미리 ProcessStartInfo.RedirectStandardOutput 속성을 true로 명시해줘야 합니다.

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";
psi.Arguments = "/C test.bat";

psi.RedirectStandardOutput = true;

Process proc = Process.Start(psi);
proc.WaitForExit();

string txt = proc.StandardOutput.ReadToEnd();

하지만, 이렇게 했어도 예외가 발생합니다.

System.InvalidOperationException was unhandled
  _HResult=-2146233079
  _message=The Process object must have the UseShellExecute property set to false in order to redirect IO streams.
  HResult=-2146233079
  IsTransient=false
  Message=The Process object must have the UseShellExecute property set to false in order to redirect IO streams.
  Source=System
  StackTrace:
       at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
       at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
       at ConsoleApplication1.Program.CallType1() in e:\...\ConsoleApplication1\Program.cs:line 49
       at ConsoleApplication1.Program.Main(String[] args) in e:\...\ConsoleApplication1\Program.cs:line 15
  InnerException: 

친절하게도 이번엔 속성 이름까지 지정해주고 있습니다. 결국 다음과 같이 됩니다.

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";
psi.Arguments = "/C test.bat";

psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;

Process proc = Process.Start(psi);
proc.WaitForExit();

string txt = proc.StandardOutput.ReadToEnd();

위의 경우, ReadToEnd를 호출했기 때문에 Process.WaitForExit를 호출할 필요는 없습니다. 그래서 다음과 같이 작성해도 됩니다.

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";
psi.Arguments = "/C test.bat";

psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;

Process proc = Process.Start(psi);
string txt = proc.StandardOutput.ReadToEnd();

만약, 출력 결과를 라인 단위로 가져오고 싶다면 이렇게 작성할 수 있습니다.

ProcessStartInfo psi = new ProcessStartInfo();

psi.FileName = "cmd.exe";
psi.Arguments = "/C test.bat";

psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;

Process proc = Process.Start(psi);

while (true)
{
    string txt = proc.StandardOutput.ReadLine(); // blocking 함수

    if (txt == null) // 프로세스가 종료한 경우 null 반환
    {
        break;
    }

    Console.WriteLine(txt);
}




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 11/15/2014]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2018-10-14 10시12분
[wpfbat] wpf로 간단하게 버튼을 만들어서 버튼을 누를시에 배치파일이 실행되고 실행된 결과값을 textblock 으로 출력하려는데 어렵네요ㅠ
[guest]
2018-12-27 11시29분
[초보개발] 안녕하세요. 다름이 아니라 질문이 있어서.. 여쭤봅니다..
다름이 아니라 c#으로 구현된 프로젝트가 하나 있는데..
이것을 기능 분리하기위해 프로젝트를 하나 더 생성해서 만들었습니다.

같은 솔루션안에 1이라는 프로젝트와 2라는 프로젝트로 분리가 되는데.. 2는 기능 1프로젝트에서 2가지만 뺀기능이고요...
1프로젝트에서 2 프로젝트 기능을 실행하기위해서 버튼클릭시

Process[] processes = Process.GetProcessesByName("1번프로젝트");
            foreach (var process in processes)
            {

                ProcessModule mainModule = process.MainModule;
                FileInfo fileInfo = new FileInfo(mainModule.FileName);
                DirectoryInfo directoryInfo = fileInfo.Directory;
                FileInfo[] files = directoryInfo.GetFiles("2번 프로젝트.exe", SearchOption.AllDirectories);

                ProcessStartInfo startInfo = new ProcessStartInfo();
                startInfo.FileName = $"{files[0].ToString()}";
                startInfo.RedirectStandardOutput = true;
                startInfo.UseShellExecute = false;

                Process p = Process.Start(startInfo);
                p.WaitForInputIdle();
                p.WaitForExit();
            }
이렇게 실행하게 되었습니다..
RedirectStandardOutput
UseShellExecute 사실 이거에 대한 속성 기능을 일단 잘몰라서 질문드린거도 있고요 ..

저걸 실행하게 되면 1번 프로젝트에 load부분에서 exe를 실행 시키고 검증를해야하는데..
이부분이 들어오지않아 또 문제가 있습니다.

솔루션
1 프로젝트
- a 클래스
2 프로젝트
- a클래스의 코드 재사용 필요..

다이나믹 함수를 사용하면 2를 만든 이유가 없더라고요... 방법좀 알려주세요 ㅠㅠ
[guest]
2018-12-27 09시44분
기능 분리를 왜 EXE로 하죠? 만약 Linux CLI 도구들처럼 입/출력 간의 redirection을 원한다면 이 글에서 설명하고 있는 것처럼 StandardOutput 속성 등을 이용해 EXE 프로그램 간의 데이터 전송을 다루면 됩니다. 혹은 IsInputRedirected 속성을 사용하실 수 있습니다.

C# - .NET 4.0 이하에서 Console.IsInputRedirected 구현
; https://www.sysnet.pe.kr/2/0/12590

C# - 간단하게 만들어 보는 리눅스의 nc(netcat) 프로그램
; https://www.sysnet.pe.kr/2/0/12311

UseShellExecute는 SHELL의 도움을 받아 실행하는 것으로 가령 .txt 파일인 경우 실행은 안 되지만 UseShellExecute를 true로 하고 실행하면 윈도우 Shell이 중간에 레지스트리에 등록된 프로그램을 살펴 보고 있으면 그 프로그램으로 대신 실행시키도록 하는 역할을 합니다. 질문자가 원하는 상황에서는 UseShellExecute를 사용할 필요가 없습니다.
정성태
2018-12-28 02시01분
[초보개발] 일단 어느정도 어제 해결을 좀 했습니다.

ExitCode 통하여 xml 정보를 리턴 했고 그 xml 정보를 다시 복호화 하는 과정으로 마무리 하고 있긴 합니다..

일단 제가 질문했던거에 대해 답해보자면요..
기능 분리를 왜 EXE로 하죠?
 -> 원래있던 프로젝트의 UI 부분을 분리하여 따로 배포시 설정창 UI를 따로 만드려 했습니다. 그렇기에 필요했습니다..

만약 Linux CLI 도구들처럼 입/출력 간의 redirection을 원한다면 이 글에서 설명하고 있는 것처럼 StandardOutput 속성 등을 이용해 EXE 프로그램 간의 데이터 전송을 다루면 됩니다. UseShellExecute는 SHELL의 도움을 받아 실행하는 것으로 가령 .txt 파일인 경우 실행은 안 되지만 UseShellExecute를 true로 하고 실행하면 윈도우 Shell이 중간에 레지스트리에 등록된 프로그램을 살펴 보고 있으면 그 프로그램으로 대신 실행시키도록 하는 역할을 합니다. 질문자가 원하는 상황에서는 UseShellExecute를 사용할 필요가 없습니다.
-> 어제 찾아보니 ..
RedirectStandardOutput 은 출력 파일의 필요성을 제거합니다. 이를 통해 디스크에 기록되는 출력 파일에 의존하지 않고도 C # 프로그램 내에서 직접 콘솔 프로그램을 사용할 수 있습니다.

프로세스의 출력을 리디렉션하려면 UseShellExecute를 false로 설정하고 RedirectStandardOutput을 true로 설정을 해야한다고 나와있었습니다..
https://www.dotnetperls.com/redirectstandardoutput -> 여기를 참고 했고용..

ui(exe)기능에는 각 서버 접속에 관한 정보를 xml파일로 담고 있기에 일단 RedirectStandardOutput, UseShellExecute 속성이 일단 필요해보였습니다.. 사실 아직까지 UseShellExecute 기능을 정확하게 잘모르겠습니다. 일단 config 정보를 리턴하려고 ExitCode를 통해 실행시에 그정보를 리턴하도록했는데 .. 맞는지도 정확하게는 잘모르겠고용...
[guest]

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13917정성태4/30/202544VS.NET IDE: 199. Directory.Build.props에 정의한 속성에 대해 Condition 제약으로 값을 변경하는 방법
13916정성태4/23/2025446디버깅 기술: 221. WinDbg 분석 사례 - ASP.NET HttpCookieCollection을 다중 스레드에서 사용할 경우 무한 루프 현상 - 두 번째 이야기
13915정성태4/13/20251652닷넷: 2331. C# - 실행 시에 메서드 가로채기 (.NET 9)파일 다운로드1
13914정성태4/11/20251965디버깅 기술: 220. windbg 분석 사례 - x86 ASP.NET 웹 응용 프로그램의 CPU 100% 현상 (4)
13913정성태4/10/20251204오류 유형: 950. Process Explorer - 64비트 윈도우에서 32비트 프로세스의 덤프를 뜰 때 "Error writing dump file: Access is denied." 오류
13912정성태4/9/2025863닷넷: 2330. C# - 실행 시에 메서드 가로채기 (.NET 5 ~ .NET 8)파일 다운로드1
13911정성태4/8/20251091오류 유형: 949. WinDbg - .NET Core/5+ 응용 프로그램 디버깅 시 sos 확장을 자동으로 로드하지 못하는 문제
13910정성태4/8/20251257디버깅 기술: 219. WinDbg - 명령어 내에서 환경 변수 사용법
13909정성태4/7/20251725닷넷: 2329. C# - 실행 시에 메서드 가로채기 (.NET Framework 4.8)파일 다운로드1
13908정성태4/2/20252138닷넷: 2328. C# - MailKit: SMTP, POP3, IMAP 지원 라이브러리
13907정성태3/29/20251933VS.NET IDE: 198. (OneDrive, Dropbox 등의 공유 디렉터리에 있는) C# 프로젝트의 출력 경로 변경하기
13906정성태3/27/20252188닷넷: 2327. C# - 초기화되지 않은 메모리에 접근하는 버그?파일 다운로드1
13905정성태3/26/20252217Windows: 281. C++ - Windows / Critical Section의 안정화를 위해 도입된 "Keyed Event"파일 다운로드1
13904정성태3/25/20251901디버깅 기술: 218. Windbg로 살펴보는 Win32 Critical Section파일 다운로드1
13903정성태3/24/20251522VS.NET IDE: 197. (OneDrive, Dropbox 등의 공유 디렉터리에 있는) C++ 프로젝트의 출력 경로 변경하기
13902정성태3/24/20251737개발 환경 구성: 742. Oracle - 테스트용 hr 계정 및 데이터 생성파일 다운로드1
13901정성태3/9/20252112Windows: 280. Hyper-V의 3가지 Thread Scheduler (Classic, Core, Root)
13900정성태3/8/20252346스크립트: 72. 파이썬 - SQLAlchemy + oracledb 연동
13899정성태3/7/20251806스크립트: 71. 파이썬 - asyncio의 ContextVar 전달
13898정성태3/5/20252124오류 유형: 948. Visual Studio - Proxy Authentication Required: dotnetfeed.blob.core.windows.net
13897정성태3/5/20252367닷넷: 2326. C# - PowerShell과 연동하는 방법 (두 번째 이야기)파일 다운로드1
13896정성태3/5/20252178Windows: 279. Hyper-V Manager - VM 목록의 CPU Usage 항목이 항상 0%로 나오는 문제
13895정성태3/4/20252220Linux: 117. eBPF / bpf2go - Map에 추가된 요소의 개수를 확인하는 방법
13894정성태2/28/20252250Linux: 116. eBPF / bpf2go - BTF Style Maps 정의 구문과 데이터 정렬 문제
13893정성태2/27/20252197Linux: 115. eBPF (bpf2go) - ARRAY / HASH map 기본 사용법
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...