jscarle/HyperV.NET

How to add existing VHD/VHDX to VM?

danijeljw-RPC opened this issue · 5 comments

How is it possible to add an existing VHD/VHDX to a virtual machine?

I can create a Virtual Hard Drive and subsequently a Virtual Hard Disk, but I cannot point it to an existing path?

Can you provide an example of how do do this?

I have an existing VHD I need to add, this is the code I am using but it's not working:

// assign an existing VHD file
VirtualHardDrive t = new VirtualHardDrive();
t.VirtualHardDisk.Path = @"C:\tmp\forty.vhd";

Error output is:

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.
    at CreateVM.Program.Main(String[] args) in C:\Users\UserName\source\repos\CreateVM\Program.cs:line 68

I'm not sure where you're getting the NullReferenceException from, I'd need to see what's on line 68. Nonetheless, at the moment there's a few assumptions that are built into the library that makes it so that it's not currently possible to attach an existing VHD/VHDX.

Hi,

I managed to add a VHDX to the SCSI controller, building on the Windows classic samples (https://github.com/microsoft/Windows-classic-samples) and this git repository. My following code example might not work for you directly, as I have changed a lot, but I hope it can lead you to a solution:

        /// <summary>
        /// Attach a vhdx disk to Scsi in VM
        /// </summary>
        /// <param name="serverName">The name of the server on which to perform the action.</param>
        /// <param name="vmName">The name of the VM to query.</param>
        //internal static void
        static public void AttachScsiVhd(
            string serverName,
            string vmName,
            string hostResource)
        {
            ManagementObject virtualMachine = GetVm.GetVmClass.GetVm(serverName, vmName);
            ManagementObject vmSettings = StorageClass.GetVirtualMachineSettings(virtualMachine);
            ManagementObject virtualHardDriveResource = CreateResource(Resources.VirtualHardDrive);
            AddResourceSettings(vmSettings, new ManagementObject[] { virtualHardDriveResource }, out ManagementObject[] virtualHardDrives);
            ManagementObject virtualHardDrive = virtualHardDrives[0];

            ManagementObject virtualHardDiskResource = CreateResource(Resources.VirtualHardDisk);
            virtualHardDiskResource["Parent"] = virtualHardDrive.Path.Path;
            virtualHardDiskResource["HostResource"] = new string[] { hostResource };
            
            AddResourceSettings(vmSettings, new ManagementObject[] { virtualHardDiskResource }, out _);
        }

        /// <summary>
        /// Detach a vhdx disk from Scsi in VM
        /// </summary>
        /// <param name="serverName">The name of the server on which to perform the action.</param>
        /// <param name="vmName">The name of the VM to query.</param>
        //internal static void
        static public void DetachScsiVhdString(
            string serverName,
            string vmName,
            string hostResource)
        {
            ManagementObject virtualMachine = GetVm.GetVmClass.GetVm(serverName, vmName);
            ManagementObject vmSettings = StorageClass.GetVirtualMachineSettings(virtualMachine);
            var vhdSettings = WmiUtilities.GetVhdSettingsFromVirtualMachineSettings(vmSettings);
            foreach (ManagementObject vhd in vhdSettings)
            {
                if (string.Join("", (string[])vhd["HostResource"]).Contains(VhdsClass(Vhds.msVm))) // Never remove ms-vm, you must re-import the vm
                {
                    continue;
                }
                else if (string.Join("", (string[])vhd["HostResource"]).Contains(hostResource))
                {
                    ManagementObject scsiController = GetRelatedSettings(vhd, Settings.ScsiController); // I don't think this is actually the scsi controller, but but virtualHardDrive....
                    //ushort address = 1;
                    //vhd["AddressOnParent"] = address; // Port
                    RemoveResourceSettings(new ManagementObject[] { vhd });
                    RemoveResourceSettings(new ManagementObject[] { scsiController });
                }

            }
        }


        internal enum Resources
        {
            Processor,
            Memory,
            SCSIController,
            VirtualHardDrive,
            VirtualHardDisk,
            VirtualDvdDrive,
            VirtualDvdDisk,
            NetworkAdapter,
            SwitchPort
        }

        public enum Vhds
        {
            msVm,
            pythonGeneral,
            msRedis,
            msWorkerJl,
            msWorkerOps,
            mainPackage,
            testFiles,
            dockerStorage
        }

        internal static string VhdsClass(Vhds vhds)
        {
            switch (vhds)
            {
                case Vhds.msVm: return "ms-vm_";
                case Vhds.pythonGeneral: return "python-general_";
                case Vhds.msRedis: return "ms-redis_";
                case Vhds.msWorkerJl: return "ms-worker-jl_";
                case Vhds.msWorkerOps: return "ms-worker-ops_";
                case Vhds.mainPackage: return "main-package_";
                case Vhds.testFiles: return "test-files_";
                case Vhds.dockerStorage: return "docker-storage_";
            }
            return null;
        }

This is the rest of the code, just in case you need it:

internal enum Settings
        {
            System,
            Security,
            Resource,
            Memory,
            Processor,
            Storage,
            VirtualHardDisk,
            NetworkAdapter,
            SwitchPort,
            SwitchPortOffload,
            Shutdown,
            TimeSynchronization,
            DataExchange,
            Heartbeat,
            VolumeShadowCopy,
            GuestServices,
            ScsiController
        }


        internal static string SettingsClass(Settings settings)
        {
            switch (settings)
            {
                case Settings.System: return "Msvm_VirtualSystemSettingData";
                case Settings.Security: return "Msvm_SecuritySettingData";
                case Settings.Resource: return "Msvm_ResourceAllocationSettingData";
                case Settings.Memory: return "Msvm_MemorySettingData";
                case Settings.Processor: return "Msvm_ProcessorSettingData";
                case Settings.Storage: return "Msvm_StorageAllocationSettingData";
                case Settings.VirtualHardDisk: return "Msvm_VirtualHardDiskSettingData";
                case Settings.NetworkAdapter: return "Msvm_SyntheticEthernetPortSettingData";
                case Settings.SwitchPort: return "Msvm_EthernetPortAllocationSettingData";
                case Settings.SwitchPortOffload: return "Msvm_EthernetSwitchPortOffloadSettingData";
                case Settings.Shutdown: return "Msvm_ShutdownComponentSettingData";
                case Settings.TimeSynchronization: return "Msvm_TimeSyncComponentSettingData";
                case Settings.DataExchange: return "Msvm_KvpExchangeComponentSettingData";
                case Settings.Heartbeat: return "Msvm_HeartbeatComponentSettingData";
                case Settings.VolumeShadowCopy: return "Msvm_VssComponentSettingData";
                case Settings.GuestServices: return "Msvm_GuestServiceInterfaceComponentSettingData";
                case Settings.ScsiController: return "Msvm_ResourceAllocationSettingData";
            }

            return null;
        }


        static internal ManagementObject CreateSettings(Settings settings)
        {
            string hostname = ".";
            System.Management.ManagementScope virtualizationScope = new System.Management.ManagementScope(@"\\" + hostname + @"\root\virtualization\v2", null);

            using (ManagementClass managementClass = new ManagementClass(SettingsClass(settings)))
            {
                managementClass.Scope = virtualizationScope;
                return managementClass.CreateInstance();
            }
        }

        static internal ManagementObject CreateResource(Resources resource)
        {
            string hostname = ".";
            System.Management.ManagementScope virtualizationScope = new System.Management.ManagementScope(@"\\" + hostname + @"\root\virtualization\v2", null);

            string resourcePoolClass = "Msvm_ResourcePool";
            if (resource == Resources.Processor)
                resourcePoolClass = "Msvm_ProcessorPool";
            //else if (resource == Resources.SCSIController)
            //    resourcePoolClass = "Msvm_ResourceAllocationSettingDate";

            string resourceSubType = "";
            switch (resource)
            {
                case Resources.Processor: resourceSubType = "Microsoft:Hyper-V:Processor"; break;
                case Resources.Memory: resourceSubType = "Microsoft:Hyper-V:Memory"; break;
                case Resources.SCSIController: resourceSubType = "Microsoft:Hyper-V:Synthetic SCSI Controller"; break;
                case Resources.VirtualHardDrive: resourceSubType = "Microsoft:Hyper-V:Synthetic Disk Drive"; break;
                case Resources.VirtualHardDisk: resourceSubType = "Microsoft:Hyper-V:Virtual Hard Disk"; break;
                case Resources.VirtualDvdDrive: resourceSubType = "Microsoft:Hyper-V:Synthetic DVD Drive"; break;
                case Resources.VirtualDvdDisk: resourceSubType = "Microsoft:Hyper-V:Virtual CD/DVD Disk"; break;
                case Resources.NetworkAdapter: resourceSubType = "Microsoft:Hyper-V:Synthetic Ethernet Port"; break;
                case Resources.SwitchPort: resourceSubType = "Microsoft:Hyper-V:Ethernet Connection"; break;
            }

            string defaultSettingPath = null;
            ObjectQuery query = new ObjectQuery($"SELECT * FROM {resourcePoolClass} WHERE ResourceSubType = \"{resourceSubType}\" AND Primordial = True");
            using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(virtualizationScope, query))
            using (ManagementObject resourcePool = WmiUtilities.GetFirstObjectFromCollection(searcher.Get()))
            using (ManagementObjectCollection capabilitiesCollection = resourcePool.GetRelated("Msvm_AllocationCapabilities", "Msvm_ElementCapabilities", null, null, null, null, false, null))
            using (ManagementObject capabilities = WmiUtilities.GetFirstObjectFromCollection(capabilitiesCollection))
                foreach (ManagementObject settingAssociation in capabilities.GetRelationships("Msvm_SettingsDefineCapabilities"))
                    if ((ushort)settingAssociation["ValueRole"] == 0)
                    {
                        defaultSettingPath = (string)settingAssociation["PartComponent"];
                        break;
                    }

            if (defaultSettingPath == null)
                throw new ManagementException("Unable to find the Default Resource Settings.");

            using (ManagementObject defaultSetting = new ManagementObject(defaultSettingPath))
            {
                defaultSetting.Scope = virtualizationScope;
                defaultSetting.Get();
                return defaultSetting;
            }
            
            
            return null;
        }

        static internal void DefineSystem(ManagementObject systemSettings, ManagementObject[] resourceSettings, out ManagementObject resultingSystem)
        {
            string hostname = ".";
            System.Management.ManagementScope scope = new System.Management.ManagementScope(@"\\" + hostname + @"\root\virtualization\v2", null);

            using (ManagementObject managementService = WmiUtilities.GetVirtualMachineManagementService(scope))
            using (ManagementBaseObject inputParameters = managementService.GetMethodParameters("DefineSystem"))
            {
                inputParameters["SystemSettings"] = systemSettings.GetText(TextFormat.WmiDtd20);
                inputParameters["ResourceSettings"] = resourceSettings.ToStringArray();
                using (ManagementBaseObject outputParameters = managementService.InvokeMethod("DefineSystem", inputParameters, null))
                {
                    WmiUtilities.ValidateOutput(outputParameters, scope);
                    resultingSystem = new ManagementObject((string)outputParameters["ResultingSystem"]);
                }
            }
        }

        static internal void ModifyResourceSettings(ManagementObject[] resourceSettings, out ManagementObject[] resultingResourceSettings)
        {
            string hostname = ".";
            System.Management.ManagementScope scope = new System.Management.ManagementScope(@"\\" + hostname + @"\root\virtualization\v2", null);

            using (ManagementObject managementService = WmiUtilities.GetVirtualMachineManagementService(scope))
            using (ManagementBaseObject inputParameters = managementService.GetMethodParameters("ModifyResourceSettings"))
            {
                inputParameters["ResourceSettings"] = resourceSettings.ToStringArray();
                using (ManagementBaseObject outputParameters = managementService.InvokeMethod("ModifyResourceSettings", inputParameters, null))
                {
                    WmiUtilities.ValidateOutput(outputParameters, scope);
                    resultingResourceSettings = ((string[])outputParameters["ResultingResourceSettings"]).ToObjectArray();
                }
            }
        }


        static internal void RemoveResourceSettings(ManagementObject[] resourceSettings)
        {
            string hostname = ".";
            System.Management.ManagementScope scope = new System.Management.ManagementScope(@"\\" + hostname + @"\root\virtualization\v2", null);

            using (ManagementObject managementService = WmiUtilities.GetVirtualMachineManagementService(scope))
            using (ManagementBaseObject inputParameters = managementService.GetMethodParameters("RemoveResourceSettings"))
            {
                inputParameters["ResourceSettings"] = resourceSettings;
                using (ManagementBaseObject outputParameters = managementService.InvokeMethod("RemoveResourceSettings", inputParameters, null))
                {
                    WmiUtilities.ValidateOutput(outputParameters, scope);
                    //resultingResourceSettings = ((string[])outputParameters["ResultingResourceSettings"]).ToObjectArray();
                }
            }
        }


        internal static ManagementObject GetRelatedSettings(ManagementObject instance, Settings settings)
        {
            return WmiUtilities.GetFirstObjectFromCollection(instance.GetRelated(SettingsClass(settings)));
        }

        internal static void AddResourceSettings(ManagementObject systemSettings, ManagementObject[] resourceSettings, out ManagementObject[] resultingResourceSettings)
        {
            string hostname = ".";
            System.Management.ManagementScope scope = new System.Management.ManagementScope(@"\\" + hostname + @"\root\virtualization\v2", null);

            using (ManagementObject managementService = WmiUtilities.GetVirtualMachineManagementService(scope))
            using (ManagementBaseObject inputParameters = managementService.GetMethodParameters("AddResourceSettings"))
            {
                inputParameters["AffectedConfiguration"] = systemSettings.Path.Path;
                inputParameters["ResourceSettings"] = resourceSettings.ToStringArray();
                using (ManagementBaseObject outputParameters = managementService.InvokeMethod("AddResourceSettings", inputParameters, null))
                {
                    WmiUtilities.ValidateOutput(outputParameters, scope);
                    resultingResourceSettings = ((string[])outputParameters["ResultingResourceSettings"]).ToObjectArray();
                }
            }
        }

Hello, could you please write down the comment? I can't understand it a little. Also, I want to realize a custom system, according to the image from the vhdx template, obtain the specified system installation according to the image name, and customize the password

I no longer have access to an environment in which I can test this.