Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

How to add existing VHD/VHDX to VM? #7

Closed
danijeljw-RPC opened this issue Oct 10, 2021 · 5 comments
Closed

How to add existing VHD/VHDX to VM? #7

danijeljw-RPC opened this issue Oct 10, 2021 · 5 comments
Assignees
Labels
enhancement New feature or request

Comments

@danijeljw-RPC
Copy link

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?

@danijeljw-RPC
Copy link
Author

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

@jscarle
Copy link
Owner

jscarle commented Nov 18, 2021

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.

@jscarle jscarle added the enhancement New feature or request label Nov 18, 2021
@jscarle jscarle self-assigned this Nov 18, 2021
@psteffensen
Copy link

psteffensen commented Jun 9, 2022

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();
                }
            }
        }

@tuke-code
Copy link

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

@jscarle
Copy link
Owner

jscarle commented Sep 2, 2023

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

@jscarle jscarle closed this as completed Sep 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants