- PR -

別アカウントで起動したプロセスの標準出力結果を得るには

1
投稿者投稿内容
MI
常連さん
会議室デビュー日: 2007/01/30
投稿数: 30
投稿日時: 2008-12-18 17:22
VB.Net 1.1
Win2000

お世話になります。
以下の希望があり、Process.StartではなくCreateProcessWithLogonW でコードの中からコマンドを実行したく思っております。

1) .Net1.1を利用
2) 実行アカウントを指定
3) 標準出力結果を取得し、ログに記入


参考先の記事「別アカウントでプロセスを起動」を見て、アカウントを指定するコードを作成しましたが、
希望 3) の標準出力結果を取得する方法がどうしてもわかりません。

WaitForSingleObjectやGetExitCodeProcessを調べてみましたが、
実行を待ってくれたりエラーが分かっても、
Process.Startの StandardOutput.ReadToEnd のような文字列は取得できませんでした。

お心当たりのある方、どうぞお知恵を貸して頂けないでしょうか。
よろしくお願い致します。

>==============================================
コード:

Private Const LOGON_WITH_PROFILE = &H1&
Private Const CREATE_DEFAULT_ERROR_MODE = &H4000000
Private Const CREATE_NEW_CONSOLE = &H10&
Private Const CREATE_NEW_PROCESS_GROUP = &H200&
Private Const CREATE_SEPARATE_WOW_VDM = &H800&
Private Const CREATE_SUSPENDED = &H4&
Private Const CREATE_UNICODE_ENVIRONMENT = &H400&
Public Structure STARTUPINFO
Dim cb As Integer
<MarshalAs(UnmanagedType.LPTStr)> _
Dim lpReserved As String
<MarshalAs(UnmanagedType.LPTStr)> _
Dim lpDesktop As String
<MarshalAs(UnmanagedType.LPTStr)> _
Dim lpTitle As String
Dim dwX As Integer
Dim dwY As Integer
Dim dwXSize As Integer
Dim dwYSize As Integer
Dim dwXCountChars As Integer
Dim dwYCountChars As Integer
Dim dwFillAttribute As Integer
Dim dwFlags As Integer
Dim wShowWindow As Short
Dim cbReserved2 As Short
Dim lpReserved2 As Integer
Dim hStdInput As Integer
Dim hStdOutput As Integer
Dim hStdError As Integer
End Structure

Public Structure PROCESS_INFORMATION
Dim hProcess As Integer
Dim hThread As Integer
Dim dwProcessId As Integer
Dim dwThreadId As Integer
End Structure

<DllImport("Advapi32.dll")> _
Public Shared Function CreateProcessWithLogonW( _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpUsername As String, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpDomain As String, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpPassword As String, _
ByVal dwLogonFlags As Integer, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpApplicationName As String, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpCommandLine As String, _
ByVal lpCreationFlags As Integer, _
ByVal lpEnvironment As IntPtr, _
<MarshalAs(UnmanagedType.LPWStr)> ByVal lpCurrentDirectory As String, _
ByRef lpStartupInfo As STARTUPINFO, _
ByRef lpProcessInfo As PROCESS_INFORMATION) As Boolean
End Function

<DllImport("kernel32.dll")> _
Public Shared Function CloseHandle( _
ByVal hObject As Long) As Long
End Function

'指定されたプロセスの終了ステータスを取得します
<DllImport("kernel32.dll", SetLastError:=True)> _
Public Function GetExitCodeProcess(ByVal process As Integer, ByRef exitCode As UInt32) As Boolean
End Function

'指定されたカーネルオブジェクトがシグナル状態になるか、指定された時間が経過するまでスレッドをスリープさせます
<DllImport("Kernel32.dll", SetLastError:=True)> _
Public Function WaitForSingleObject(ByVal handle As Integer, ByVal milliseconds As UInt32) As UInt32
End Function

'The FormatMessage function formats a message string that is passed as input.
<DllImport("kernel32.dll")> _
Public Function FormatMessage(ByVal dwFlags As Integer, ByRef lpSource As IntPtr, _
ByVal dwMessageId As Integer, ByVal dwLanguageId As Integer, ByRef lpBuffer As [String], _
ByVal nSize As Integer, ByRef Arguments As IntPtr) As Integer
End Function

'実行
Public Sub RunAs(ByVal t As Task, ByVal logPath As String)
Dim processInfo As PROCESS_INFORMATION
Dim startupInfo As STARTUPINFO = New STARTUPINFO
Dim retval As Boolean

Try
startupInfo.cb = System.Runtime.InteropServices.Marshal.SizeOf(startupInfo)
startupInfo.lpTitle = Nothing
startupInfo.dwYCountChars = 50

retval = CreateProcessWithLogonW( _
t.UserName, t.Domain, t.PassWord, _
LOGON_WITH_PROFILE, Nothing, t.Command, _
CREATE_DEFAULT_ERROR_MODE Or CREATE_NEW_CONSOLE Or CREATE_NEW_PROCESS_GROUP, _
IntPtr.Zero, Nothing, _
startupInfo, processInfo)

Dim wait As System.UInt32 = Convert.ToUInt32(60000)
Dim exitCode As System.UInt32

'終了まで60秒内で待つ
WaitForSingleObject(processInfo.hProcess, wait)
'終了コード取得
GetExitCodeProcess(processInfo.hProcess, exitCode)
'終了コードが異常だった場合
If exitCode.ToString = "0" Then
'エラーコード取得
Dim ret As Integer = Marshal.GetLastWin32Error()
'エラーメッセージに変換
Dim errmsg As String = GetErrorMessage(ret)
WriteLog("Error : " & errmsg, logPath)
End If

Catch ex As Exception
WriteLog("実行時エラー" & ex.ToString, logPath)
Finally
If retval = True Then
CloseHandle(processInfo.hProcess)
CloseHandle(processInfo.hThread)
End If
End Try
End Sub

Private Function GetErrorMessage(ByVal errorCode As Integer) As String
Dim FORMAT_MESSAGE_ALLOCATE_BUFFER As Integer = &H100 '関数に、バッファの割り当てを要求します
Dim FORMAT_MESSAGE_IGNORE_INSERTS As Integer = &H200 'パラメータを無視するよう要求します
Dim FORMAT_MESSAGE_FROM_SYSTEM As Integer = &H1000 'システムメッセージテーブルリソースを使用するよう要求します

Dim msgSize As Integer = 255
Dim lpMsgBuf As String
Dim dwFlags As Integer = FORMAT_MESSAGE_ALLOCATE_BUFFER _
Or FORMAT_MESSAGE_FROM_SYSTEM _
Or FORMAT_MESSAGE_IGNORE_INSERTS
Dim lpSource As IntPtr = IntPtr.Zero
Dim lpArguments As IntPtr = IntPtr.Zero

Dim returnVal As Integer = _
FormatMessage(dwFlags, lpSource, errorCode, 0, lpMsgBuf,msgSize, lpArguments)
If returnVal = 0 Then
Throw New Exception("Failed to format message for error code " + errorCode.ToString() + ". ")
End If

Return lpMsgBuf
End Function



<編集>
セラフ様、[CODE]のご指摘ありがとうございます。
すみません、早速修正いたしました。

[ メッセージ編集済み 編集者: MI 編集日時 2008-12-18 18:37 ]
セラフ
ベテラン
会議室デビュー日: 2005/12/01
投稿数: 95
お住まい・勤務地: 東北の顔の形といえば
投稿日時: 2008-12-18 18:24
ソース部分はBBコードのcodeタグを使っていただけると見やすいです

で本題。

kernel32.dll の CreatePipeをDllImportして、STARTUPINFO の hStdOutputのハンドルにパイプを作成し、Streamを使って読み出してください。

※ちなみにhStdOutputがそのものずばり標準入出力のハンドルです。
http://yokohama.cool.ne.jp/chokuto/urawaza/struct/STARTUPINFO.html

ただしこのやり方だと、Streamから普通に読み出そうとすると自分のプロセスが待ちに入ってしまうので、マルチスレッドにする必要が出てきます。

自分は昔この実装を行いましたが、もっとスマートなやり方があるのかもしれません・・・。判る人居たら教えて欲しい・・・

#WindowsAPIの部分をkernel32.dllに置換えました。


[ メッセージ編集済み 編集者: セラフ 編集日時 2008-12-18 18:32 ]
MI
常連さん
会議室デビュー日: 2007/01/30
投稿数: 30
投稿日時: 2008-12-21 14:50
セラフ様

ご回答ありがとうございます。
お返事が遅くなりまして、大変申し訳ございません。

今までAPIを恐る恐る使っていたのですが、ご回答を勉強してとてもためになりました。

教えて頂きましたCreatePipeを調べて、
CreatePipe→STARTUP_INFO→CreateProcessWithLogonW と渡す所まで何とかでき、
マルチスレッドも、モジュールの呼び出し側でデリゲートを使ってできた(と思います)

ただ、hStdOutputからの呼び出しが調べてみても理解することができませんでした。

APIのReadFileで試してみたのですが、
(すみません、Streamから読み出す、というのが分かりませんでした)
ReadFileの行で進まなくなってしまいます。

恐らくとても基本的な事で恐縮ですが、宜しければ読み出し方を教えて頂けないでしょうか。
どうぞよろしくお願い致します。

コード:


Structure SECURITY_ATTRIBUTES
Dim nLength As Int32
Dim lpSecurityDescriptor As Int32
Dim bInheritHandle As Int32
End Structure

<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Function CreatePipe( _
ByRef phReadPipe As IntPtr, _
ByRef phWritePipe As IntPtr, _
ByRef lpPipeAttributes As SECURITY_ATTRIBUTES, _
ByVal nSize As Long) As Long
End Function

<DllImport("kernel32.dll")> _
Public Function ReadFile( _
ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), _
ByVal nNumberOfBytesToRead As Int32, _
ByRef lpNumberOfBytesRead As Int32, _
ByVal lpOverlapped As IntPtr) As Boolean
End Function

'実行
Public Sub RunAs(ByVal t As Task, ByVal logPath As String)
Dim pi As PROCESS_INFORMATION
Dim st As STARTUPINFO = New STARTUPINFO
Dim sa As New SECURITY_ATTRIBUTES
Dim retval As Boolean
Dim rt As Long
Dim phReadPipe As IntPtr
Dim phWritePipe As IntPtr
Dim wait As System.UInt32 = Convert.ToUInt32(60000)
Dim exitCode As System.UInt32 = Convert.ToUInt32(123456)

Try
With sa 'SECURITY_ATTRIBUTES
.nLength = Len(sa)
.bInheritHandle = 1&
.lpSecurityDescriptor = 0&
End With

'パイプ作成
rt = CreatePipe(phReadPipe, phWritePipe, sa, 0&)

If rt = 0 Then
Console.WriteLine("CreatePipe失敗")
Exit Sub
End If

With st 'STARTUPINFO
.cb = System.Runtime.InteropServices.Marshal.SizeOf(st)
.lpTitle = Nothing
.lpReserved = Nothing
.dwFlags = st.dwFlags And Startf_UseStdHandles
.hStdOutput = phWritePipe 'パイプを渡す
End With


'実行
retval = CreateProcessWithLogonW( _
t.UserName, t.Domain, t.PassWord, _
LOGON_WITH_PROFILE, Nothing, t.Command, _
CREATE_DEFAULT_ERROR_MODE Or CREATE_NEW_CONSOLE Or CREATE_NEW_PROCESS_GROUP, _
IntPtr.Zero, Nothing, _
st, pi)

'終了まで60秒内で待つ
WaitForSingleObject(pi.hProcess, wait)
'終了コード取得
GetExitCodeProcess(pi.hProcess, exitCode)

If exitCode.ToString = "0" Then
'エラーコード取得
Dim ret As Integer = Marshal.GetLastWin32Error()
'エラーメッセージに変換
Dim errmsg As String = GetErrorMessage(ret)
WriteLog("Error : " & errmsg, logPath)
Else

'成功
Dim buf(1023) As Byte
Dim BytesWritten As Long = 1023
Dim BytesRead As Long
Dim ret As Boolean

ret = ReadFile(phReadPipe, buf, BytesWritten, BytesRead, IntPtr.Zero)
If ret <> 0 Then
Console.WriteLine("出力結果 : " & System.Text.Encoding.Default.GetString(buf))
End If
End If
Catch ex As Exception

WriteLog("実行時エラー " & ex.ToString, logPath)
Finally
If retval = True Then
CloseHandle(phWritePipe)
CloseHandle(phReadPipe)
CloseHandle(pi.hProcess)
CloseHandle(pi.hThread)
End If
End Try
End Sub



<編集> コードを修正致しました

[ メッセージ編集済み 編集者: MI 編集日時 2008-12-21 15:10 ]
セラフ
ベテラン
会議室デビュー日: 2005/12/01
投稿数: 95
お住まい・勤務地: 東北の顔の形といえば
投稿日時: 2008-12-26 12:52
アプリケーション終了後にReadFileしようとしていませんか?

アプリケーション終了後では、標準出力へのハンドルが閉じられてしまっていて、読み出せないのではないでしょうか・・・
1

スキルアップ/キャリアアップ(JOB@IT)