using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using Microsoft.Win32; using Vestris.VMWareLib; using Cosmos.Build.Common; using Cosmos.Debug.Common; namespace Cosmos.Debug.VSDebugEngine.Host { public class VMware : Base { protected VMwareEdition mEdition; protected string mDir; protected string mVmxPath; protected Process mProcess; protected string mWorkstationPath; protected string mPlayerPath; public VMware(NameValueCollection aParams, bool aUseGDB) : base(aParams, aUseGDB) { mDir = Path.Combine(CosmosPaths.Build, @"VMWare\Workstation\"); mVmxPath = Path.Combine(mDir, @"Debug.vmx"); mWorkstationPath = GetPathname("VMware Workstation", "vmware.exe"); mPlayerPath = GetPathname("VMware Player", "vmplayer.exe"); if (mWorkstationPath == null && mPlayerPath == null) { throw new Exception("VMware not found."); } CheckIfHyperVServiceIsRunning(); string xFlavor = aParams[BuildProperties.VMwareEditionString].ToUpper(); mEdition = VMwareEdition.Player; if (xFlavor == "WORKSTATION") { mEdition = VMwareEdition.Workstation; } // Try alternate if selected one is not installed if (mEdition == VMwareEdition.Player && mPlayerPath == null && mWorkstationPath != null) { mEdition = VMwareEdition.Workstation; } else if (mEdition == VMwareEdition.Workstation && mWorkstationPath == null) { mEdition = VMwareEdition.Player; } } private static void CheckIfHyperVServiceIsRunning() { using (System.ServiceProcess.ServiceController sc = new System.ServiceProcess.ServiceController("vmms")) { try { if (sc.Status == System.ServiceProcess.ServiceControllerStatus.Running) { if (System.Windows.Forms.DialogResult.Yes == global::System.Windows.Forms.MessageBox.Show("Do you want to stop the Hyper-V Virtual Machine Management Service? This is needed to allow to run VMware. If you press \"No\" the debug will stop.", "Question", System.Windows.Forms.MessageBoxButtons.YesNo, System.Windows.Forms.MessageBoxIcon.Question)) sc.Stop(); else throw new NotSupportedException("VMware start useless, because of running Hyper-V Virtual Machine Management!"); } } catch (InvalidOperationException) { // service not present } } } protected void ConnectToVMWare(VMWareVirtualHost aHost) { if (mEdition != VMwareEdition.Player) { aHost.ConnectToVMWareWorkstation(); } else { aHost.ConnectToVMWarePlayer(); } } protected string GetPathname(string aKey, string aEXE) { using (var xRegKey = Registry.LocalMachine.OpenSubKey(@"Software\VMware, Inc.\" + aKey, false)) { if (xRegKey != null) { string xResult = Path.Combine(((string)xRegKey.GetValue("InstallPath")), aEXE); if (File.Exists(xResult)) { return xResult; } } return null; } } public override void Start() { Cleanup(); CreateDebugVmx(); // Target exe or file mProcess = new Process(); var xPSI = mProcess.StartInfo; if (mEdition == VMwareEdition.Player) { xPSI.FileName = mPlayerPath; } else { xPSI.FileName = mWorkstationPath; } var xArgSB = new StringBuilder(); string xVmxPath = "\"" + mVmxPath + "\""; if (mEdition == VMwareEdition.Player) { xPSI.Arguments = xVmxPath; } else { // -x: Auto power on VM. Must be small x, big X means something else. // -q: Close VMWare when VM is powered off. // Options must come beore the vmx, and cannot use shellexecute xPSI.Arguments = "-x -q " + xVmxPath; } xPSI.UseShellExecute = true; //must be true to allow elevate the process, sometimes needed if vmware only runs with admin rights mProcess.EnableRaisingEvents = true; mProcess.Exited += delegate(Object aSender, EventArgs e) { if (OnShutDown != null) { OnShutDown(aSender, e); } }; mProcess.Start(); } public override void Stop() { using (var xHost = new VMWareVirtualHost()) { ConnectToVMWare(xHost); using (var xMachine = xHost.Open(mVmxPath)) { xMachine.PowerOff(); } xHost.Close(); } Cleanup(); } protected void DeleteFiles(string aPath, string aPattern) { var xFiles = Directory.GetFiles(aPath, aPattern); foreach (var xFile in xFiles) { File.Delete(xFile); } } protected void Cleanup() { try { string xPath = Path.GetDirectoryName(mVmxPath); // Delete old Debug.vmx and other files that might be left over from previous run. // Especially important with newer versions of VMWare player which defaults to suspend // when the close button is used. File.Delete(mVmxPath); File.Delete(Path.ChangeExtension(mVmxPath, ".nvram")); // Delete the auto snapshots that latest vmware players create as default. // It creates them with suffixes though, so we need to wild card find them. DeleteFiles(xPath, "*.vmxf"); DeleteFiles(xPath, "*.vmss"); DeleteFiles(xPath, "*.vmsd"); DeleteFiles(xPath, "*.vmem"); // Delete log files so that logged data is only from last boot File.Delete(Path.Combine(xPath, "vmware.log")); File.Delete(Path.Combine(xPath, "vmware-0.log")); File.Delete(Path.Combine(xPath, "vmware-1.log")); File.Delete(Path.Combine(xPath, "vmware-2.log")); } catch (Exception) { // Ignore errors, users can stop VS while VMware is still running and files will be locked. } } protected void CreateDebugVmx() { // VMWare doesn't like to boot a read only VMX. // We also need to make changes based on project / debug settings. // Finally we do not want to create VCS checkins based on local user changes. // Because of this we use Cosmos.vmx as a template and output a Debug.vmx on every run. using (var xSrc = new StreamReader(Path.Combine(mDir, "Cosmos.vmx"))) { try { // Write out Debug.vmx using (var xDest = new StreamWriter(mVmxPath)) { string xLine; while ((xLine = xSrc.ReadLine()) != null) { var xParts = xLine.Split('='); if (xParts.Length == 2) { string xName = xParts[0].Trim(); string xValue = xParts[1].Trim(); if ((xName == "uuid.location") || (xName == "uuid.bios")) { // We delete uuid entries so VMWare doesnt ask the user "Did you move or copy" the file xValue = null; } else if (xName == "ide1:0.fileName") { // Set the ISO file for booting xValue = "\"" + mParams["ISOFile"] + "\""; } else if (xName == "nvram") { // Point it to an initially non-existent nvram. // This has the effect of disabling PXE so the boot is faster. xValue = "\"Debug.nvram\""; } if (xValue != null) { xDest.WriteLine(xName + " = " + xValue); } } } if (mUseGDB) { xDest.WriteLine(); xDest.WriteLine("debugStub.listen.guest32 = \"TRUE\""); xDest.WriteLine("debugStub.hideBreakpoints = \"TRUE\""); xDest.WriteLine("monitor.debugOnStartGuest32 = \"TRUE\""); xDest.WriteLine("debugStub.listen.guest32.remote = \"TRUE\""); } } } catch (IOException ex) { if (ex.Message.Contains(Path.GetFileName(mDir))) { throw new Exception("The VMware image " + mDir + " is still in use. Please exit current Vmware session with Cosmos and try again.", ex); } throw; } } } } }