Sunday, October 4, 2015

VMware Workstation: scripting unity mode

When trying to keep a whole bunch of legacy programs running at a case appointed to me, it was necessary to use Windows 7 running in a virtual machine under VMware Workstation (running on Windows 10). To prevent the VM's of staying on after the program execution finished, I wrote a small batch file on the virtualization host to start and suspend the VM:

@echo off
cd C:\Program Files (x86)\VMware\VMware Workstation

vmrun start C:\VMs\Win7\Win7.vmx

vmrun -T ws -gu -gp runProgramInGuest C:\VMs\Win7\Win7.vmx -activeWindow -interactive "C:\Program Files\MyLegacyApp\Legacy.exe"

vmrun suspend C:\VMs\Win7\Win7.vmx

This works fine, since the vmrun "runProgramInGuest" runs synchronously, only terminating after the application inside the guest VM has finished -- which then nicely calls a "suspend" to freeze the VM for the next time the batch file is called on the host.

However, to improve even further the user experience, I wanted to use VMware Workstation's unity feature. That turned out to be more difficult in my setting than I had expected: whether Unity was used the last time in the VM or not, the script above always opens the VMware Workstation GUI and just shows the VM starting. I also read about adding the following lines to the VMX file:

gui.fullScreenAtPowerOn = "FALSE"
gui.lastPoweredViewMode = "unity"
gui.viewModeAtPowerOn = "unity"

Yet that didn't work either... 

So instead, I decide to use a workaround that does both the scripted & synchronous execution of the program I need in the guest, and enables Unity at the same time:
  • I run a dummy program in "Unity" which puts the (running) VM in Unity view mode -- notice that vmware-unity-helper.exe works asynchronously, hence immediately returns.
  • Afterwards, I start the Legacy.exe using vmrun.
To complicate matters further, also multiple users have to be able to run the program in the same Workstation VM, so some "preconfiguration" for Unity is required for every user.

In a nutshell:
  • vmware-unity-helper.exe on Windows seems to only run predefined commands on predefined VMs. The configuration is under %LOCALAPPDATA%\VMware\unity-helper.xml.
  • This file looks (in my case) as follows:

    <unity_helper version="1">
      <ghilaunch>
        <apps nextid="3">
          <app id="1" uri="file:///d:/dummy.lnk">
        </apps>
        <vms nextid="2">
          <vm id="1" path="C:\VMs\Win7\Win7.vmx">
        </vms>
      </ghilaunch>
    </unity_helper>
  • You can see in the XML file:
    • First, all applications that Unity can start are defined using an App ID and the URI on where the file is located.
    • Then, a list of all known VMs is provided, with the path to the VMX file.
  • An application is then started with vmware-unity-helper.exe as follows:

    vmware-unity-helper.exe -r -G:1 -V:1

    where the "G" parameter specifies the app ID and the V parameter the VM ID.
So in order to have any user utilize Unity, I modified the script above as follows:

@echo off
C:
cd C:\Program Files (x86)\VMware\VMware Workstation

REM Prepare unity
copy /Y C:\VMs\unity-helper.xml %LOCALAPPDATA%\VMware\unity-helper.xml >NUL

REM Start VM
vmrun start C:\VMs\Win7\Win7.vmx nogui

REM Now run application 
vmware-unity-helper.exe -r -G:1 -V:1
vmrun -T ws -gu Adobe -gp adobe runProgramInGuest C:\VMs\Win7\Win7.vmx -activeWindow -interactive "D:\Program Files\MyLegacyApp\Legacy.exe"

REM Now suspend VM
vmrun suspend C:\VMs\Win7\Win7.vmx

This script:
  1. First copies the predefined XML file to the %LOCALAPPDATA% folder (overwriting anything there -- my users don't use Unity for other purposes, so if you need to support that too you'll need to do some more magic).
  2. Then we start the VM as before
  3. Next step is to run "a dummy application" using Unity, which sets the already running VM in Unity mode. More on this dummy application in a second.
  4. Then we starts the application again using vmrun, wait for it to end, and nicely close the VM as before.

The dummy application inside the VM is just a batch file "dummy.bat" with contents "@echo off" and "exit". I created a shortcut "dummy.lnk" to this "dummy.bat" so I can keep the command prompt window minimized (properties of shortcut) -- this prevents a unity window popping up & disappearing into view.

This works great and runs the application in Unity, nicely starting & stopping the VM as needed. Obviously the script should be extended to check if the VM is already running by another user, but that'll be for another time :). The only disadvantage is that you briefly still see the VMware Workstation window -- unfortunately using the "nogui" option with vmrun start does not seem to fix this...

As a sidenote, to top it of I:
  • Enabled "Shared Files" to make available the data directories of the users to the users under the guest OS, under a mapped network drive.
  • I disabled all unnecessary services in the VM for a very fast startup.
  • I've hidden all disk drives (C:, D:, E:) in the guest VM using group policy (https://support.microsoft.com/kb/231289) - to ensure the users can only see the "Shared Folders" and never mistakenly save data inside the guest.
  • The VM was located on a SSD disk so the Windows 7 in the guest starts incredibly fast