eg/WinApi/PipedProcess.py
# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright © 2005-2020 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.
import ctypes
import sys
from comtypes import GUID
from cPickle import dumps, loads
from os.path import abspath, dirname, join
# Local imports
import eg
from Dynamic import (
byref,
cast,
CloseHandle,
ConnectNamedPipe,
create_string_buffer,
CreateEvent,
CreateNamedPipe,
DisconnectNamedPipe,
DWORD,
ERROR_NOT_CONNECTED,
FILE_FLAG_OVERLAPPED,
FlushFileBuffers,
FormatError,
GetLastError,
HANDLE,
INVALID_HANDLE_VALUE,
OVERLAPPED,
PIPE_ACCESS_DUPLEX,
PIPE_READMODE_MESSAGE,
PIPE_TYPE_MESSAGE,
PIPE_UNLIMITED_INSTANCES,
PIPE_WAIT,
POINTER,
ReadFile,
SEE_MASK_FLAG_DDEWAIT,
SEE_MASK_FLAG_NO_UI,
SEE_MASK_NOCLOSEPROCESS,
SHELLEXECUTEINFO,
sizeof,
SW_SHOWNORMAL,
WAIT_OBJECT_0,
WAIT_TIMEOUT,
WaitForMultipleObjects,
WriteFile,
)
from eg.WinApi.Dynamic.Winnetwk import (
UNIVERSAL_NAME_INFO,
UNIVERSAL_NAME_INFO_LEVEL,
WNetGetUniversalName,
)
MESSAGE_ARGS = 0
MESSAGE_STDOUT = 1
MESSAGE_STDERR = 2
MESSAGE_RESULT = 3
MESSAGE_EXCEPTION = 4
BUFSIZE = 4096
def ExecAs(scriptPath, asAdministrator, funcName, *args, **kwargs):
pipeName = "\\\\.\\pipe\\" + str(GUID.create_new())
Msg("creating named pipe")
hPipe = CreateNamedPipe(
pipeName,
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
BUFSIZE,
BUFSIZE,
0,
None
)
if hPipe == INVALID_HANDLE_VALUE:
raise Exception("Error in creating Named Pipe")
overlapped = OVERLAPPED()
overlapped.hEvent = CreateEvent(None, 1, 0, None)
try:
Msg("calling ConnectNamedPipe")
ConnectNamedPipe(hPipe, byref(overlapped))
localPath = dirname(__file__.decode('mbcs'))
Msg("starting subprocess")
hProcess = RunAs(
abspath(join(localPath, "..", "..", "EventGhost.exe")),
asAdministrator,
"-execfile",
GetUncPathOf(join(localPath, "PipedProcessClient.py")),
pipeName,
str(eg.debugLevel)
)
Msg("waiting for subprocess to connect")
pHandles = (HANDLE * 2)(overlapped.hEvent, hProcess)
ret = WaitForMultipleObjects(2, pHandles, 0, 25000)
if ret == WAIT_OBJECT_0:
# connect event
Msg("got connect event")
elif ret == WAIT_OBJECT_0 + 1:
raise Exception("Unexpected end of subprocess.")
elif ret == WAIT_TIMEOUT:
raise Exception("Timeout in waiting for subprocess.")
else:
raise Exception("Unknown return value")
Msg("sending startup message")
WritePipeMessage(
hPipe,
MESSAGE_ARGS,
(GetUncPathOf(scriptPath), funcName, args, kwargs)
)
chBuf = create_string_buffer(BUFSIZE)
cbRead = DWORD(0)
while True:
fSuccess = ReadFile(hPipe, chBuf, BUFSIZE, byref(cbRead), None)
if ((fSuccess == 1) or (cbRead.value != 0)):
code, data = loads(chBuf.value)
if code == MESSAGE_STDERR:
sys.stderr.write(data)
elif code == MESSAGE_STDOUT:
sys.stdout.write(data)
elif code == MESSAGE_RESULT:
result = data
break
elif code == MESSAGE_EXCEPTION:
break
else:
raise Exception("Unknown message type %r" % code)
FlushFileBuffers(hPipe)
DisconnectNamedPipe(hPipe)
finally:
CloseHandle(hPipe)
CloseHandle(overlapped.hEvent)
if code == MESSAGE_EXCEPTION:
raise Exception("Child process raised an exception\n" + data)
return result
def ExecAsAdministrator(scriptPath, funcName, *args, **kwargs):
"""
Execute some Python code in a process with elevated privileges.
This call will only return, after the subprocess has terminated. The
sys.stdout and sys.stderr streams of the subprocess will be directed to
the calling process through a named pipe. All parameters for the function
to call and its return value must be picklable.
:param scriptPath: Path to the Python file to load.
:param funcName: Name of the function to call inside the Python file
:param args: Positional parameters for the function
:param kwargs: Keyword parameters for the function
:returns: The return value of the function
"""
return ExecAs(scriptPath, True, funcName, *args, **kwargs)
def GetUncPathOf(filePath):
buf = create_string_buffer(1024)
dwBufSize = DWORD(1024)
err = WNetGetUniversalName(
filePath,
UNIVERSAL_NAME_INFO_LEVEL,
buf,
byref(dwBufSize)
)
if err == 0:
return cast(buf, POINTER(UNIVERSAL_NAME_INFO)).contents.lpUniversalName
elif err == ERROR_NOT_CONNECTED:
pass
else:
print "GetUncPathOf Error:", err, FormatError(err)
return filePath
def Msg(msg):
try:
eg.PrintDebugNotice(str(msg))
except:
pass
def RunAs(filePath, asAdministrator, *args):
sei = SHELLEXECUTEINFO()
sei.cbSize = sizeof(SHELLEXECUTEINFO)
sei.fMask = (
SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS
)
if asAdministrator:
sei.lpVerb = u"runas"
else:
sei.lpVerb = u""
sei.lpFile = GetUncPathOf(filePath)
sei.lpParameters = " ".join(
['"%s"' % arg.replace('"', '""') for arg in args]
)
sei.nShow = SW_SHOWNORMAL
if not ctypes.windll.shell32.ShellExecuteExW(byref(sei)):
err = GetLastError()
raise WindowsError(err, "ShellExecuteEx: %s" % FormatError(err))
return sei.hProcess
def RunAsAdministrator(filePath, *args):
return RunAs(filePath, True, *args)
def WritePipeMessage(hPipe, code, data):
message = dumps((code, data))
cbWritten = DWORD(0)
fSuccess = WriteFile(
hPipe,
message,
len(message),
byref(cbWritten),
None
)
if (not fSuccess) or (len(message) != cbWritten.value):
raise Exception("WritePipeMessage failed")