You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动