I've been looking for a way to launch Windows applications (the host) from an Ubuntu virtual machine (under VMWare Player). I got a bit carried away and wrote the client and server scripts listed below. The guest OS is not Windows, so these will need some changes to work for a Windows guest. I used this setup to have Git (running on the Ubuntu guest) call KDiff3 on the host when merging.
The following Python script (host_run_server.py) acts as a server accepting commands from the guest. It expects the guest to provide a Samba share with name GUEST_ROOT_SHARE
(set this at the top of the script) that exposes the root of the filesystem. This share is mapped to a drive GUEST_DRIVE
. This is needed so that the host and guest can access the same files. In my case, I already had also mounted 'My Documents' to a folder on the guest to be able to use git on my host's files.
import asyncore, asynchat
import os
import socket
import shlex, subprocess
import threading
# make the root / of the guest accessible as a samba share and map
# this share in the host to GUEST_DRIVE
HOST_IP = '192.168.126.1'
GUEST_IP = '192.168.126.129'
GUEST_ROOT_SHARE = 'root'
GUEST_DRIVE = 'K:'
TCP_PORT = 5005
BUFFER_SIZE = 1024
ENCODING = 'utf-8'
# map network drive
try:
import win32wnet
import pywintypes
from win32netcon import RESOURCETYPE_DISK
network_path = r'\\{}\{}'.format(GUEST_IP, GUEST_ROOT_SHARE)
try:
win32wnet.WNetAddConnection2(RESOURCETYPE_DISK, GUEST_DRIVE, network_path)
except pywintypes.error as e:
if (e.args[0] != 85 or
win32wnet.WNetGetUniversalName(GUEST_DRIVE) != network_path):
raise
except ImportError:
pass
# allow GUI applications to pop to front on Windows
try:
import win32gui
from win32con import SPI_SETFOREGROUNDLOCKTIMEOUT
result = win32gui.SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0)
if result is not None:
print("Failed:", result)
except ImportError:
pass
class Handler(asynchat.async_chat):
def __init__(self, sock, map=None):
asynchat.async_chat.__init__(self, sock, map=map)
self.remote_ip, self.remote_port = self.socket.getpeername()
self.log('connected')
self.set_terminator(b'\x00')
self.data = b''
self.state = 'cwd'
def handle_close(self):
remote_ip, remote_port = self.socket.getpeername()
self.log('disconnected')
self.close()
def collect_incoming_data(self, data):
self.data += data
def found_terminator(self):
if self.state == 'cwd':
self.cwd = self.data.decode(ENCODING)
self.state = 'cmd'
self.data = b''
elif self.state == 'cmd':
self.cmd = self.data.decode(ENCODING)
self.reply()
self.state = 'end'
def prepare(self):
cwd = GUEST_DRIVE + self.cwd.replace('/', '\\')
self.log('in {}'.format(cwd))
os.chdir(cwd)
cmd_args = []
for arg in shlex.split(self.cmd):
if arg.startswith('[FILE]'):
arg = arg[6:].replace('/', '\\')
if arg.startswith('\\'):
arg = GUEST_DRIVE + arg
cmd_args.append(arg)
return cwd, cmd_args
def run(self, cwd, cmd_args):
self.log('executing: {}'.format(' '.join(cmd_args)))
try:
p = subprocess.Popen(cmd_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
out, err = p.communicate()
rcode = p.returncode
except WindowsError as e:
out = b''
err = '{}: {}\n'.format(e.__class__.__name__, e.args[1]).encode(ENCODING)
rcode = -1
return rcode, out, err
def reply(self):
cwd, cmd_args = self.prepare()
rc, out, err = self.run(cwd, cmd_args)
self.push(str(len(out)).encode(ENCODING) + b'\x00')
if len(out):
self.push(out)
self.push(str(len(err)).encode(ENCODING) + b'\x00')
if len(err):
self.push(err)
self.push(str(rc).encode(ENCODING) + b'\x00')
def log(self, msg):
print("[{}:{}]\t{}".format(self.remote_ip, self.remote_port, msg))
class HandlerThread(threading.Thread):
def __init__(self, sock):
super().__init__()
self.sock = sock
def run(self):
handler = Handler(self.sock)
asyncore.loop(map=handler._map)
class Server(asyncore.dispatcher):
def __init__(self, host, port, guest_ip):
asyncore.dispatcher.__init__(self, map={})
self.guest_ip = guest_ip
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind((host, port))
self.listen(5)
print("Service started. Listening on {} port {}."
.format(host, port))
def handle_accepted(self, sock, addr):
(guest_ip, guest_port) = addr
if guest_ip == self.guest_ip:
ht = HandlerThread(sock)
ht.start()
else:
print("Ignoring request from {}".format(guest_ip))
server = Server(HOST_IP, TCP_PORT, GUEST_IP)
asyncore.loop(map=server._map)
Below is the script to invoke on the guest side (host_run.py).
#!/usr/bin/env python3
import asyncore, asynchat
import os
import socket
import sys
from optparse import OptionParser
HOST_IP = "192.168.126.1"
GUEST_IP = "192.168.126.129"
HOST_IS_WINDOWS = True
TCP_PORT = 5005
BUFFER_SIZE = 1024
ENCODING = 'utf-8'
STD_ENCODING = 'cp1252' if HOST_IS_WINDOWS else ENCODING
class HostRun(asynchat.async_chat):
def __init__(self, host, port):
asynchat.async_chat.__init__(self)
self.set_terminator(b'\x00')
self.data = b''
self.state = 'stdout1'
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect((host, port))
def handle_connect(self):
self.push(os.getcwd().encode(ENCODING) + b'\x00')
self.push(command.encode(ENCODING) + b'\x00')
def collect_incoming_data(self, data):
self.data += data
def found_terminator(self):
if self.state == 'stdout1':
stdout_len = int(self.data.decode(ENCODING))
if stdout_len:
self.set_terminator(stdout_len)
self.state = 'stdout2'
else:
self.state = 'stderr1'
elif self.state == 'stdout2':
stdout = self.data.decode(STD_ENCODING)
sys.stdout.write(stdout)
self.set_terminator(b'\x00')
self.state = 'stderr1'
elif self.state == 'stderr1':
stderr_len = int(self.data.decode(ENCODING))
if stderr_len:
self.set_terminator(stderr_len)
self.state = 'stderr2'
else:
self.state = 'rc'
elif self.state == 'stderr2':
stderr = self.data.decode(STD_ENCODING)
sys.stderr.write(stderr)
self.set_terminator(b'\x00')
self.state = 'rc'
elif self.state == 'rc':
rc = int(self.data.decode(ENCODING))
sys.exit(rc)
self.close_when_done()
self.data = b''
def handle_close(self):
remote_ip, remote_port = self.socket.getpeername()
print("%s:%s disconnected" %(remote_ip, remote_port))
self.close()
parser = OptionParser()
(options, args) = parser.parse_args()
command = ' '.join(args)
HostRun(HOST_IP, TCP_PORT)
asyncore.loop()
The scripts take care of translating file paths. For this to work, you need to prepend paths passed as arguments to the client script with [FILE]
First start the server script on the host. Now you can pass commands to the client script:
brecht@krubuntu ~ $ ./host_run.py dir [FILE]/home
This will translate /home
to K:\home
and thus execute dir K:\home
on the host. The server sends the stdout/stderr output and return code back to the client, which spits it back out to the shell prompt:
Volume in drive K is root
Volume Serial Number is 64C2-522A
Directory of K:\home
07/22/2012 22:13 <DIR> .
12/04/2012 06:53 <DIR> ..
02/28/2013 21:56 <DIR> brecht
0 File(s) 0 bytes
3 Dir(s) 12,723,302,400 bytes free
This is possible in VMware Fusion's global preferences, which is the Mac version of VMware. Check your VMware settings.
– Daniel Beck – 2011-05-29T13:28:15.700