to write directly to it, bypassing any stdout redirection.
It seems the usual way to get the output in such a case appears to be "screenscraping" the console buffer (I think), see:
(as far as I can tell) feature: if a named pipe server is created with the name of
, then the server will send its output there.
I had a go at writing a script that gets that output when available and writes it into a file on the desktop.
This script automatically exits if the server process is closed; if you exit the script first, it will automatically kill the server (
doesn't seem to have any effect on KFServer, or a nicer exit would have been possible).
Code: Select all
#NoEnv
#Persistent
if (!A_IsUnicode)
MsgBox The KF server sends strings in UTF-16. Continuing with ANSI AHK anyway (which may or may not work)...
; Start the server:
_PROCESS_INFORMATION(pi)
VarSetCapacity(si, (siCb := A_PtrSize == 8 ? 104 : 68), 0), NumPut(siCb, si,, "UInt")
if (!DllCall("CreateProcess", "Ptr", 0
,"Str", A_Desktop . "\KF2-Server-Installer-Manager\Installer\steamcmd\kf2server\Binaries\Win64\KFServer.exe" ; command line
,"Ptr", 0
,"Ptr", 0
,"Int", False
,"UInt", CREATE_SUSPENDED := 0x00000004 ; needs to be created suspended so that we can get KFServer's PID and to make sure the named pipe is created before KFServer tries looking for it and fails in doing so
,"Ptr", 0
,"Str", A_Desktop . "\KF2-Server-Installer-Manager\Installer\steamcmd\kf2server\Binaries\Win64" ; starting/working directory (,"Ptr", 0 instead to inherit this process's)
,"Ptr", &si
,"Ptr", &pi))
{
DieWithLastError("CreateProcess")
}
hProcess := NumGet(pi,, "Ptr")
hThread := NumGet(pi, A_PtrSize, "Ptr")
dwProcessId := NumGet(pi, A_PtrSize * 2, "UInt")
OnExit("AtExit")
hPipe := DllCall("CreateNamedPipe"
,"Str", Format("\\.\pipe\{:u}cout", dwProcessId)
,"UInt", PIPE_ACCESS_INBOUND := 0x00000001 | FILE_FLAG_OVERLAPPED := 0x40000000
,"UInt", PIPE_TYPE_BYTE := 0x00000000
,"UInt", 1
,"UInt", 0
,"UInt", 0
,"UInt", 0
,"Ptr", 0, "Ptr")
if (hPipe == -1) {
DieWithLastError("CreateNamedPipe")
}
; resume the process
DllCall("ResumeThread", "Ptr", hThread)
,WaitOnEvents()
WaitOnEvents() {
global hPipe, hProcess
static GetOverlappedResult := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr"), "AStr", "GetOverlappedResult", "Ptr")
,MsgWaitForMultipleObjectsEx := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr"), "AStr", "MsgWaitForMultipleObjectsEx", "Ptr")
,hEvent := DllCall("CreateEvent", "Ptr", 0, "Int", False, "Int", False, "Ptr", 0, "Ptr"), overlapped, handles, buffer
,ConnectNamedPipe := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandleW", "WStr", "kernel32.dll", "Ptr"), "AStr", "ConnectNamedPipe", "Ptr")
if (!VarSetCapacity(overlapped)) {
VarSetCapacity(overlapped, 32, 0)
,NumPut(hEvent, overlapped, 2*A_PtrSize+8, "Ptr")
VarSetCapacity(handles, A_PtrSize * 2) ; handles to wait on, specified in a C array for MsgWaitForMultipleObjectsEx
,NumPut(hProcess, handles,, "Ptr")
,NumPut(hEvent, handles, A_PtrSize, "Ptr")
VarSetCapacity(buffer, 4096*2)
}
FileDelete, %A_Desktop%\dsffds.txt
notConnected := !DllCall(ConnectNamedPipe, "Ptr", hPipe, "Ptr", &overlapped) && A_LastError == 997
if (!notConnected)
DllCall("ReadFile", "Ptr", hPipe, "Ptr", &buffer, "UInt", 4096*2, "Ptr", 0, "Ptr", &overlapped) ; if already connected, try and initially get some output
Loop { ; stolen from Lexikos: wait on the process to terminate, while allowing messages to be pumped etc.
r := DllCall(MsgWaitForMultipleObjectsEx, "UInt", 2, "Ptr", &handles, "UInt", 0xFFFFFFFF, "UInt", 0x4FF, "UInt", 0x6, "UInt")
if (r == 0 || r == 0xFFFFFFFF) ; first object (process) signalled (terminated) / failure
ExitApp
else if (r == 1) { ; the same event is used for two purposes: the first being it's signalled when KFServer connects to it and the other is when ReadFile has something from the pipe that we should read
if (!notConnected) {
if (DllCall(GetOverlappedResult, "Ptr", hPipe, "Ptr", &overlapped, "UInt*", len, "Int", True)) {
NumPut(0, buffer, len, "UShort")
FileAppend, % StrGet(&buffer,, "UTF-16"), %A_Desktop%\dsffds.txt, UTF-16
}
DllCall("ReadFile", "Ptr", hPipe, "Ptr", &buffer, "UInt", 4096*2, "Ptr", 0, "Ptr", &overlapped) ; another ReadFile call is needed to get more output as it occurs
} else {
notConnected := False
;DllCall("ResetEvent", "Ptr", hEvent)
DllCall("ReadFile", "Ptr", hPipe, "Ptr", &buffer, "UInt", 4096*2, "Ptr", 0, "Ptr", &overlapped) ; start the initial reading when available
}
}
Sleep -1
}
}
AtExit()
{
global hProcess, hThread
OnExit(A_ThisFunc, 0)
if (DllCall("WaitForSingleObject", "Ptr", hProcess, "UInt", 0) == 258) ; process still running
DllCall("TerminateProcess", "Ptr", hProcess, "UInt", 1)
DllCall("CloseHandle", "Ptr", hThread)
DllCall("CloseHandle", "Ptr", hProcess)
return 0
}
DieWithLastError(failedFuncName)
{
dw := A_LastError
ccherrFmt := DllCall("FormatMessage", "UInt", 0x00000100 | 0x00001000 | 0x00000200, "Ptr", 0, "UInt", dw, "UInt", 1024, "Ptr*", errFmt, "UInt", 0, "Ptr", 0, "UInt")
MsgBox % Format("{:s} failed ({:u}){:s}", failedFuncName, dw, ccherrFmt ? ": " . StrGet(errFmt, ccherrFmt) : "")
if (ccherrFmt)
DllCall("LocalFree", "Ptr", errFmt, "Ptr")
ExitApp 1
}
_PROCESS_INFORMATION(ByRef pi) {
static piCb := A_PtrSize == 8 ? 24 : 16
if (IsByRef(pi))
VarSetCapacity(pi, piCb, 0)
}