主要目标
我正在使用 VirtualBox 在 Windows 10 虚拟机中使用 Microsoft Office 应用程序。我的主机操作系统是 Ubuntu 23.04。我编写了一些脚本,允许从主机启动 Microsoft Office 应用程序。具体来说,它允许双击直接从 Linux 文件管理器打开 Microsoft Office(.docx、.xlsx 等)。它运行得很好,直到我最近升级 Ubuntu 并重新安装了一些软件包。
问题
可以使用命令从主机运行客户机中的应用程序VBoxManage guestcontrol
。例如,在 Ubuntu 主机的终端中执行以下命令会在 Windows 10 客户机中打开计算器。
VBoxManage guestcontrol $MY_VM_NAME run --exe "C:\\Windows\\System32\\calc.exe" --username $MY_USERNAME --password $MY_PASSWORD
这可以正常工作。但是,如果我尝试打开 Microsoft Office 应用程序(如以下命令),它不起作用。客户机上什么也没有发生,并且主机终端中的命令执行永远不会结束。
VBoxManage guestcontrol $MY_VM_NAME run --exe "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE" --username $MY_USERNAME --password $MY_PASSWORD
完整脚本
我在 Ubuntu 中创建了一个“Word 桌面应用程序”,用于执行命令/my_path/runWord %F
。下面是运行词脚本:
#!/bin/bash
# -- CODE DEVELOPERS/CONTRIBUTORS -- andpy73, sbnwl, 3Pilif, TVG
# https://forums.virtualbox.org/viewtopic.php?f=7&t=91799&start=15
clear
MY_VM_NAME="" # your VM name
MY_USERNAME="" # your Windows username
MY_PASSWORD="" # your Windows password
# If VM is not running
if !( vboxmanage showvminfo "$MY_VM_NAME" | grep -c "running (since" ); then
# Start the VM
vboxmanage startvm "$MY_VM_NAME" --type separate > /dev/null
# Sleep long enough so the VM is running before opening the app
sleep 30 # (change the number of seconds depending how fast is the loading)
fi
# Create a temporary executable file in which we will write a VBoxManage command with Windows path as argument
if [ ! -d ~/.tmp ]; then
mkdir ~/.tmp
fi
cd ~/.tmp
touch tmpfile
chmod +x tmpfile
# If a file is specified (double click on a Word file)
if [ -f "$1" ]; then
# Write in the temporary executable file the VBoxManage command (change MY_USERNAME and MY_PASSWORD by your Windows username and password)...
printf 'VBoxManage guestcontrol "'"$MY_VM_NAME"'" run --exe "C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE" --username '$MY_USERNAME' --password '$MY_PASSWORD' ' >> tmpfile
# ...with the file as argument
FILE=$(echo $1 | awk -F '/MOUNTED_PATH/' '{print $2}' | sed 's/\//\\/g') # file with Windows path (slash replace) from "/MOUNTED_PATH/" (change MOUNTED_PATH by the path of your shared folder in the VM)
echo ' -- WINWORD/arg0 "'Z:\\$FILE'"' >> tmpfile # (change "Z:\\" with the your shared folder mounted drive in the VM)
else # else open blank file if no one opened or focus on the opened one (this is done by runWord.exe application in the VM)
printf 'VBoxManage guestcontrol "'"$MY_VM_NAME"'" run --exe "C:\\APP_PATH\\runWord.exe" --username '$MY_USERNAME' --password '$MY_PASSWORD' ' >> tmpfile # (change APP_PATH by the path of the runWord.exe application in the VM)
fi
# Show the contents of tmpfile (only for debugging purpose)
echo '-------------------------------------------------------------------------'
echo 'This is the content of tmpfile for debugging purpose:'
echo ' '
cat tmpfile
echo ' '
echo '-------------------------------------------------------------------------'
# # Run the command in tmpfile
./tmpfile &
rm tmpfile
# Focus on the VM window
window_id=$(wmctrl -l | grep "$MY_VM_NAME" | awk '{print $1;}' | head -1) # Get window id
wmctrl -ia $window_id # Un-mininized the window (i.e. focus on it)
此脚本调用 Windows VM 中的可执行文件 (runWord.exe)。我编写了一个 PowerShell (runWord.ps1) 脚本,然后使用软件“PS1 To Exe”将其转换为 Windows 可执行文件 (runWord.exe)。我主要从这里复制了代码:https://powershell.one/powershell-internals/extending-powershell/vbscript-and-csharp#c-to-the-rescue。这是文件 runWord.ps1:
$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
# Get the main window handle for the process you want to switch to the foreground
# in this example, the first instance of WINWORD is used
$process = Get-Process -Name WINWORD -ErrorAction SilentlyContinue |
Select-Object -First 1
$mainWindowHandle = $process.MainWindowHandle
# If process exists
if ($process)
{
# Focus application window
[API.FocusWindow]::Focus($mainWindowHandle)
}else{
# Open application
Start-Process -FilePath "C:\Program Files\Microsoft Office\root\Office16\WINWORD.EXE"
}
如果您知道如何解决我指出的具体问题,或者您想到了更好的方法来实现主要目标,请告诉我。
答案1
解决方案
问题来自于VirtualBox 客户机附加功能 v7.降级至v6版本解决问题。
细节
- 主机操作系统:Ubuntu 23.10
- 客户操作系统:Windows 10
- VirtualBox:v7.0.10
- 在 Windows 10 VM 中,卸载Oracle VM VirtualBox 客户机添加项(控制面板 > 卸载程序)。
- 关闭虚拟机。
- 从旧版本的 VirtualBox Guest Additions 下载 ISO 文件这里. 适用
VBoxGuestAdditions_6.1.48.iso
。 - (可选)将 ISO 文件保存在 下
~/.config/VirtualBox/
。 - 在 VirtualBox 管理器中,选择 ISO 文件SATA 端口 1:[光驱] VBoxGuestAdditions_6.1.48.iso。
- 启动虚拟机。
- 在“此电脑”下,双击CD 驱动器 (D:) VirtualBox 客户机添加项,然后
VBoxWindowsAdditions.exe
按照说明安装 VirtualBox Guest Additions v6.1.48。
笔记
VirtualBox Guest Additions v7 似乎都不起作用。那么,由于只有 VirtualBox Guest Additions v6 可以工作,因此最好也使用 VirtualBox v6 而不是 VirtualBox v7。