Ubuntu主机通过VirtualBox调用Windows虚拟机Office应用失效的问题咨询
Ubuntu主机通过VirtualBox调用Windows虚拟机Office应用失效的问题咨询
我的核心需求
我用Ubuntu 23.04作为主机系统,通过VirtualBox运行Windows 10虚拟机来使用Microsoft Office套件。之前我写了一套脚本,可以直接从Linux文件管理器双击.docx、.xlsx这类Office文件,自动在虚拟机里打开对应的Office应用,用起来特别顺手。但最近我升级了Ubuntu,还重装了一些软件包之后,这套脚本就失效了。
具体问题
通过VBoxManage guestcontrol命令从主机调用虚拟机里的程序,有些能正常工作,比如下面这条命令可以在Win10虚拟机里打开计算器:
VBoxManage guestcontrol $MY_VM_NAME run --exe "C:\\Windows\\System32\\calc.exe" --username $MY_USERNAME --password $MY_PASSWORD
但如果换成调用Office应用,比如下面这条启动Word的命令,就完全没反应了——虚拟机里看不到任何动静,主机终端里的命令还会一直卡住,永远不结束:
VBoxManage guestcontrol $MY_VM_NAME run --exe "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE" --username $MY_USERNAME --password $MY_PASSWORD
我现有的完整脚本
Ubuntu主机端的runWord脚本
我在Ubuntu里创建了一个“Word桌面应用”,关联了.docx文件,双击时会执行/my_path/runWord %F,下面是这个脚本的内容:
#!/bin/bash # -- CODE DEVELOPERS/CONTRIBUTORS -- andpy73, sbnwl, 3Pilif, TVG clear MY_VM_NAME="" # 你的虚拟机名称 MY_USERNAME="" # Windows系统用户名 MY_PASSWORD="" # Windows系统密码 # 如果虚拟机未运行 if !( vboxmanage showvminfo "$MY_VM_NAME" | grep -c "running (since" ); then # 启动虚拟机 vboxmanage startvm "$MY_VM_NAME" --type separate > /dev/null # 等待虚拟机完全启动 sleep 30 # 可根据你的虚拟机启动速度调整等待时间 fi # 创建临时可执行文件,写入带参数的VBoxManage命令 if [ ! -d ~/.tmp ]; then mkdir ~/.tmp fi cd ~/.tmp touch tmpfile chmod +x tmpfile # 如果是双击打开某个Word文件 if [ -f "$1" ]; then # 写入启动Word的VBoxManage命令 printf 'VBoxManage guestcontrol "'"$MY_VM_NAME"'" run --exe "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE" --username '$MY_USERNAME' --password '$MY_PASSWORD' ' >> tmpfile # 转换共享文件夹路径为Windows格式 FILE=$(echo $1 | awk -F '/MOUNTED_PATH/' '{print $2}' | sed 's/\//\\/g') # 替换/MOUNTED_PATH/为你虚拟机里共享文件夹的路径 echo ' -- WINWORD/arg0 "'Z:\\$FILE'"' >> tmpfile # 替换Z:\\为你虚拟机里映射的共享文件夹盘符 else # 如果没有指定文件,打开空白Word或切换到已打开的Word窗口 printf 'VBoxManage guestcontrol "'"$MY_VM_NAME"'" run --exe "C:\\APP_PATH\\runWord.exe" --username '$MY_USERNAME' --password '$MY_PASSWORD' ' >> tmpfile # 替换APP_PATH为虚拟机里runWord.exe的路径 fi # 调试用:打印临时文件内容 echo '-------------------------------------------------------------------------' echo '这是临时文件tmpfile的内容(用于调试):' echo ' ' cat tmpfile echo ' ' echo '-------------------------------------------------------------------------' # 执行临时文件里的命令 ./tmpfile & rm tmpfile # 切换到虚拟机窗口 window_id=$(wmctrl -l | grep "$MY_VM_NAME" | awk '{print $1;}' | head -1) # 获取虚拟机窗口ID wmctrl -ia $window_id # 还原并聚焦虚拟机窗口
Windows虚拟机端的runWord.ps1脚本
我写了一个PowerShell脚本,用工具转换成了exe文件,用来处理Word窗口的聚焦逻辑:
$code = @' using System; using System.Runtime.InteropServices; namespace API { public class FocusWindow { [DllImport("User32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool AttachThreadInput(IntPtr idAttach, IntPtr idAttachTo, bool fAttach); [DllImport("User32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("User32.dll")] private static extern IntPtr GetWindowThreadProcessId(IntPtr hwnd, IntPtr lpdwProcessId); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("user32")] private static extern int BringWindowToTop(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni); private const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000; private const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001; private const int SPIF_SENDCHANGE = 0x2; private const int SW_HIDE = 0; private const int SW_SHOWNORMAL = 1; private const int SW_NORMAL = 1; private const int SW_SHOWMINIMIZED = 2; private const int SW_SHOWMAXIMIZED = 3; private const int SW_MAXIMIZE = 3; private const int SW_SHOWNOACTIVATE = 4; private const int SW_SHOW = 5; private const int SW_MINIMIZE = 6; private const int SW_SHOWMINNOACTIVE = 7; private const int SW_SHOWNA = 8; private const int SW_RESTORE = 9; private const int SW_SHOWDEFAULT = 10; private const int SW_MAX = 10; public static void Focus(IntPtr windowHandle) { IntPtr blockingThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero); IntPtr ownThread = GetWindowThreadProcessId(windowHandle, IntPtr.Zero); if (blockingThread == ownThread || blockingThread == IntPtr.Zero) { SetForegroundWindow(windowHandle); ShowWindow(windowHandle, SW_NORMAL); } else { if (AttachThreadInput(ownThread, blockingThread, true)) { BringWindowToTop(windowHandle); SetForegroundWindow(windowHandle); ShowWindow(windowHandle, SW_NORMAL); AttachThreadInput(ownThread, blockingThread, false); } } if (GetForegroundWindow() != windowHandle) { IntPtr Timeout = IntPtr.Zero; SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, Timeout, 0); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, SPIF_SENDCHANGE); BringWindowToTop(windowHandle); SetForegroundWindow(windowHandle); ShowWindow(windowHandle, SW_NORMAL); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Timeout, SPIF_SENDCHANGE); } } } } '@ # Remove -PassThru in production. It is used only to expose the added types Add-Type -PassThru -TypeDefinition $code # 获取第一个WINWORD进程的主窗口句柄 $process = Get-Process -Name WINWORD -ErrorAction SilentlyContinue | Select-Object -First 1 $mainWindowHandle = $process.MainWindowHandle # 如果Word已运行,聚焦窗口;否则启动Word if ($process) { [API.FocusWindow]::Focus($mainWindowHandle) }else{ Start-Process -FilePath "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE" }
求助
有没有大佬能帮我找出调用Office应用失效的原因?或者有没有更可靠的方法实现“从Ubuntu主机直接打开虚拟机里的Office文件”这个需求?
备注:内容来源于stack exchange,提问作者TVG




