Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

I really like VMWare VIX API’s CopyFileFromGuestToHost and CopyFileFromHostToGuest. These automatically copy a single file or an entire directory tree. Easy. Unfortunately I am copying large (150-300 Mb) files to/from a VMWare guest OS and it’s taking an hour to copy a hundred megs. I bet VMWare API was developed by Russian developers, so it’s probably sending UTF-8 encoded bytes in Russian via SOAP. You might remember that I am writing a tool that lets one test a cross-product of installers and virtual machine snapshots: three MSIs multipled by twenty snapshots equals days of file copying! It’s not going to work.

I posted a question about this in VMWare Communities and got a satisfactory response: I don’t think there is a workaround. We are aware of the issue (we run in to it internally) and hope to fix it in a future release.

In the meantime, I need a short term solution.

I decided to try and map the remote drive and use a simple File.Copy. The first challenge is to find out the remote server’s IP address. VMWare exposes guest OS variables, including the ip, so I’ve extended VMWareTasks to support those.

/// <summary>
/// Environment, guest and runtime variables
/// </summary>
/// <param name="name">name of the variable</param>
[IndexerName("Variables")]
public string this[string name]
{
  get
  {
    VMWareJob job = new VMWareJob(_vm.ReadVariable(_variableType, name, 0, null));
    object[] properties = { Constants.VIX_PROPERTY_JOB_RESULT_VM_VARIABLE_STRING };
    return job.Wait<string>(properties, 0, VMWareInterop.Timeouts.ReadVariableTimeout);
  }

  set
  {
    VMWareJob job = new VMWareJob(_vm.WriteVariable(_variableType, name, value, 0, null));
    job.Wait(VMWareInterop.Timeouts.WriteVariableTimeout);
  }
}

Here’s the IP address of a powered-on VM.

virtualMachine.GuestVariables["ip"];

We can make up a network path out of a local one.

public string PathToNetworkPath(string ip, string value)
{
  return string.Format(@"\\{0}\{1}", ip, value.Replace(":", "$"));
}

Mapping a network drive is implemented in mpr.dll with WNetAddConnection2. A wrapper class will do the job.

public class NetworkDrive
{
  [DllImport("mpr.dll")]
  public static extern int WNetAddConnection2(ref NETRESOURCE lpNetResource, string lpPassword, string UserName, int dwFlags);

  [StructLayout(LayoutKind.Sequential)]
  public struct NETRESOURCE
  {
    public int dwScope;
    public int dwType;
    public int dwDisplayType;
    public int dwUsage;
    public string lpLocalName;
    public string lpRemoteName;
    public string lpComment;
    public string lpProvider;
  }

  private const int RESOURCETYPE_DISK = 0x1;

  private string _localName;
  private string _remoteName;

  public NetworkDrive(string remoteName)
  {
    _remoteName = remoteName;
  }

  public NetworkDrive(string remoteName, string localName)
  {
    _remoteName = remoteName;
    _localName = localName;
  }

  public string LocalName
  {
    get { return _localName; }
    set { _localName = value; }
  }

  public string RemoteName
  {
    get { return _remoteName; }
    set { _remoteName = value; }
  }

  public void MapNetworkDrive(string username, string password)
  {
    NETRESOURCE netResource = new NETRESOURCE();
    netResource.dwScope = 2;
    netResource.dwType = RESOURCETYPE_DISK;
    netResource.dwDisplayType = 3;
    netResource.dwUsage = 1;
    netResource.lpRemoteName = _remoteName;
    netResource.lpLocalName = _localName;
    int rc = WNetAddConnection2(ref netResource, password, username, 0);
    if (rc != 0)
    {
      throw new Win32Exception(rc);
    }
  }
}

Let’s put it all together.

public void CopyFileFromGuestToHost(string guestPath, string hostPath)
{
  string guestNetworkPath = PathToNetworkPath(_ip, guestPath);
  NetworkDrive guestNetworkDrive = new NetworkDrive(Path.GetPathRoot(guestNetworkPath));
  guestNetworkDrive.MapNetworkDrive(_username, _password);
  File.Copy(guestNetworkPath, hostPath, true);
}

The last thing that remains to be done to make the new copy compatible is to also copy directories and subdirectories. That’s left as an exercise to the reader.