使用 VirtualBox 在主机的客户虚拟机中运行 Microsoft Office 应用程序

使用 VirtualBox 在主机的客户虚拟机中运行 Microsoft Office 应用程序

主要目标

我正在使用 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
  1. 在 Windows 10 VM 中,卸载Oracle VM VirtualBox 客户机添加项(控制面板 > 卸载程序)。
  2. 关闭虚拟机。
  3. 从旧版本的 VirtualBox Guest Additions 下载 ISO 文件这里. 适用VBoxGuestAdditions_6.1.48.iso
  4. (可选)将 ISO 文件保存在 下~/.config/VirtualBox/
  5. 在 VirtualBox 管理器中,选择 ISO 文件SATA 端口 1:[光驱] VBoxGuestAdditions_6.1.48.iso
  6. 启动虚拟机。
  7. 在“此电脑”下,双击CD 驱动器 (D:) VirtualBox 客户机添加项,然后VBoxWindowsAdditions.exe按照说明安装 VirtualBox Guest Additions v6.1.48。

笔记

VirtualBox Guest Additions v7 似乎都不起作用。那么,由于只有 VirtualBox Guest Additions v6 可以工作,因此最好也使用 VirtualBox v6 而不是 VirtualBox v7。

相关内容