VMware {code} Community
jeffpatton
Enthusiast
Enthusiast

Clone VM using existing CustomizationSpec and changing ComputerName property

So I've been working on this project for work and have started a thread (http://communities.vmware.com/message/2194869#2194869) that has more or less turned into a build log since nobody has responded to it but me.

If you look at the last post there is an attachment that has the functions that i'm using in my web app. I'm using the vmware.vim.dll to do everything i need, and for the most part i feel that i'm about 80% complete. My current hangup is handling the computername property of the resulting vm. Now we have a basic customization spec that I load in

VirtualMachineCloneSpec mySpec = new VirtualMachineCloneSpec();
mySpec.Location = new VirtualMachineRelocateSpec();
mySpec.Location.Datastore = itmDatastore.MoRef;
//mySpec.Location.Transform = VirtualMachineRelocateTransformation.sparse;
mySpec.Location.Host = selectedHost.MoRef;
mySpec.Customization = itmSpecItem.Spec;
mySpec.Customization.Identity = itmSpecItem.Spec.Identity;

at runtime I can see that mySpec.Customization.Identity has everything in it specifically, UserData.ComputerName.Name and it's set to some name. We have a PowerShell script that we've been using that will Get-OsCustomizationSpec and then Set-OsCustomizationSpec, or something similar. I'm having a difficult time accessing the the userdata stuff in the spec while I'm writing my code.

For the record outside of the computername thing, this works perfectly. I've spent most of the morning tweaking my code that will customizes the vm and clones it. I get a working vm, in the right network, with the proper addressing, my problem is only the name of it. I had missed this and actually broke a production vm, because my clone came up with the same name.

I've seen several examples where everyone is creating new customizationsysprep, but that info is already in mine, i just need to know how to programatically access and modify it, there must be a way to do that, as i'm doing it now for setting networking information.

Hopefully someone can help me out, as everything i've seen thus far has been dated circa 2005-2008. i get that there is powercli, i'm writing a web app so that's not an option for me now.

thanks in advance!

Jeffrey S. Patton Systems Specialist, Enterprise Systems University of Kansas 1001 Sunnyside Ave. Lawrence, KS. 66045 http://patton-tech.com
0 Kudos
6 Replies
BenN
Enthusiast
Enthusiast

Did you post the source you're using to manipulate the CustomizationSysprep? I see a lot of

other code in that thread (which is greatly appreciated, BTW), but not that.

I did this a few years back, but in Java (using VI Java). I don't have access to that source anymore,

but it looked something like this:

if (identity instanceof CustomizationSysprep) {
    CustomizationSysprep winIdentity = (CustomizationSysprep) identity;
    CustomizationName name;
    if (hostName != null) {
      CustomizationFixedName fname = new CustomizationFixedName();
      fname.setName(hostName);
      name = fname;
    } else {
      name = new CustomizationVirtualMachineName();
    }
    winIdentity.getUserData().setComputerName(name);
  }

The trick is that UserData.computerName takes an object, not a name directly.

So if I had a "hostName" string, I used that as the computer name (via CustomizationFixedName), otherwise

I let vSphere name it based on the VM name (via CustomizationVirtualMachineName).

Hope this helps.

0 Kudos
jeffpatton
Enthusiast
Enthusiast

Ben,

The only code I had was this:

mySpec.Customization = itmSpecItem.Spec;
mySpec.Customization.Identity = itmSpecItem.Spec.Identity;

i'm struggling trying to manipulate it, so it looks like in your code you check to see if the thing coming is a spec right? it looks like the last line of your code block is more or less what i need, i wonder if i need to cast those as objects? or can i just get to them in a different way...just curious to keep the number of lines down. I'll poke at what you gave me and see what i come up with.

glad you enjoyed the code i wrote, i had been struggling with any sort of documentation or useful examples in terms of what i was trying to do. since i've posted that i'm beginning to think that most of that could be condensed down to really just a couple of functions, as i believe i can pass types in, but i'm still really novice at c# right now.

Jeffrey S. Patton Systems Specialist, Enterprise Systems University of Kansas 1001 Sunnyside Ave. Lawrence, KS. 66045 http://patton-tech.com
0 Kudos
jeffpatton
Enthusiast
Enthusiast

Ok, looking at your code, it appears you create a customizationsysprep object based on the identity object. if i set the computername property there, how does that make it back into the identity object? or am i just missing something fundamental in how oop works?

Jeffrey S. Patton Systems Specialist, Enterprise Systems University of Kansas 1001 Sunnyside Ave. Lawrence, KS. 66045 http://patton-tech.com
0 Kudos
BenN
Enthusiast
Enthusiast

The "identity" variable from my snippet is a reference of type CustomizationIdentitySettings. Not shown in the snippet,

but I got it from the "identity" property of a CustomizationSpec:

     http://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvim.vm.customizat...

which I turn read via CustomizationSpecManager, just as you do.

If you look up CustomizationIdentitySettings:

     http://pubs.vmware.com/vsphere-51/topic/com.vmware.wssdk.apiref.doc/vim.vm.customization.IdentitySet...

you'll see it is an abstract type for one of three concrete types. So my snippet is testing if the object I have

is actually of concrete type "CustomizationSysprep", and if it is casting the reference to that type. The code doesn't

create that object, it's already there, the code just needs to refer to that object using the correct concrete type.

The code then creates an object of either type CustomizationFixedName or CustomizationVirtualMachineName (both

of which inherit from abstract type CustomizationName) and adds it to the CustomizationUserData object that is

already attached to the CustomizationSysprep.

All these "attachments" are references, so my original CustomizationSpec still refers to my CustomizationSysprep, which

still refers to the same CustomizationUserData, which now refers to this new object I attached to it. That CustomizationSpec

can now be fed back into clone.

I don't have a .NET setup, but I'd imagine it would look something like:

  CustomizationSysrep winIdentity = (CustomizationSysprep) mySpec.Customization.Identity;

  CustomizationFixedName fname = new CustomizationFixedName();

  fname.Name = myHostName;

  winIdentity.UserData.ComputerName = fname;

jeffpatton
Enthusiast
Enthusiast

Ben,

here is what i came up with after i wrote this morning, and it looks very similar to what you just posted.

mySpec.Customization = itmSpecItem.Spec;
CustomizationSysprep winIdent = (CustomizationSysprep)itmSpecItem.Spec.Identity;
CustomizationFixedName hostname = new CustomizationFixedName();
hostname.Name = txtVmName.Text;
winIdent.UserData.ComputerName = hostname;
mySpec.Customization.Identity = winIdent;

from that i was able to have my clone vm's come up with the proper names Smiley Happy once the code is all tidy i'll post it up.

Jeffrey S. Patton Systems Specialist, Enterprise Systems University of Kansas 1001 Sunnyside Ave. Lawrence, KS. 66045 http://patton-tech.com
0 Kudos
jeffpatton
Enthusiast
Enthusiast

Ben,

Here is my code to handle cloning a windows or linux vm from an existing vm. This appears to allow us to define network settings and hostname infromation which are the important bits for us. It will also pre-stage a computer account in our domain for the windows vm's.

I would very much consider this to be beta1 type code...lool...and take NO responsibility for it 😉 My only limitation here is that I'm hard-coding for a signle network card. We're really only hoping to use this to provision commodity type servers that have no crazy extra settings, and if so, this would get a working vm up and running and then it can be modified later.

The functions i'm calling are the ones from the using vmware.vim post.

(http://communities.vmware.com/message/2194869#2194869)

enjoy!

string dateCreated = (System.DateTime.Now).ToString();
Random rand = new Random();
if (txtVmName.Text == null || txtVmName.Text == "")
{
    txtResult.Text = "Please enter a name for the virtual machine.";
    return;
}
IPAddress theIp;
bool ipResult = IPAddress.TryParse(txtIpAddress.Text, out theIp);
if (ipResult != true)
{
    txtResult.Text = "Please enter a valid IP Address.";
    return;
}
IPAddress theGateway;
bool gwResult = IPAddress.TryParse(txtDefaultGateway.Text, out theGateway);
if (gwResult != true)
{
    txtResult.Text = "Please entera valid IP Address for the default gateway.";
    return;
}
//
// Check for name conflict
//
VimClient vimClient = ConnectServer(Globals.sViServer, Globals.sUsername, Globals.sPassword);
List<VirtualMachine> chkVirtualMachines = GetVirtualMachines(vimClient, null, txtVmName.Text);
if (chkVirtualMachines != null)
{
    vimClient.Disconnect();
    txtResult.Text = "virtual machine " + txtVmName.Text + " already exists";
    return;
}
if (cboCustomizations.SelectedItem.Value=="Windows")
{
    //
    // Connect to directory and create computer object
    //
    string ldapPath = Globals.adServer + "/OU=" + cboSysadminOu.SelectedItem.ToString() + "," + Globals.adRootPath;
    DirectoryEntry dirEntry = new DirectoryEntry();
    dirEntry.Username = Globals.sUsername;
    dirEntry.Password = Globals.sPassword;
    //
    // check to see if name exists
    //
    DirectorySearcher dirSearcher = new DirectorySearcher(dirEntry);
    dirSearcher.Filter = "(cn=" + txtVmName.Text + ")";
    SearchResult dirResult = dirSearcher.FindOne();
    if (dirResult == null)
    {
        //
        // Name not found, create new ad object
        //
        DirectoryEntry dirEntry1 = new DirectoryEntry();
        dirEntry1.Path = ldapPath;
        dirEntry1.Username = Globals.sUsername;
        dirEntry1.Password = Globals.sPassword;
        DirectoryEntries dirEntries = dirEntry1.Children;
        DirectoryEntry newVm = dirEntries.Add("CN=" + txtVmName.Text.ToString(), "computer");
        newVm.Properties["sAMAccountName"].Value = txtVmName.Text.ToString() + "$";
        newVm.Properties["Description"].Value = Globals.sUsername + " created this object on " + DateTime.Now;
        newVm.Properties["userAccountControl"].Value = 4128;
        newVm.CommitChanges();
    }
    else
    {
        txtResult.Text = "The server " + txtVmName.Text + " already exists in the Active Directory, please choose a different name.";
        return;
    }
}
//
// Get a list of hosts in the selected cluster
//
List<HostSystem> lstHosts = GetHosts(vimClient, cboClusters.SelectedValue);
//
// Randomly pick host
//
HostSystem selectedHost = lstHosts[rand.Next(0, lstHosts.Count)];
//
// Connect to selected vm to clone
//
List<VirtualMachine> lstVirtualMachines = GetVirtualMachines(vimClient, null, cboClones.SelectedItem.Text);
VirtualMachine itmVirtualMachine = lstVirtualMachines[0];
//
// Connect to the selected datastore
//
List<Datastore> lstDatastores = GetDataStore(vimClient, null, cboDatastores.SelectedItem.Text);
Datastore itmDatastore = lstDatastores[0];
//
// Connect to portgroup
//
List<DistributedVirtualPortgroup> lstDvPortGroups = GetDVPortGroups(vimClient, null, cboPortGroups.SelectedItem.Text);
DistributedVirtualPortgroup itmDvPortGroup = lstDvPortGroups[0];;
//
// Connect to the customizationspec
//
CustomizationSpecItem itmSpecItem = GetCustomizationSpecItem(vimClient, cboCustomizations.SelectedItem.Text);
//
// Create a new VirtualMachineCloneSpec
//
        
VirtualMachineCloneSpec mySpec = new VirtualMachineCloneSpec();
mySpec.Location = new VirtualMachineRelocateSpec();
mySpec.Location.Datastore = itmDatastore.MoRef;
mySpec.Location.Host = selectedHost.MoRef;
           
//
// Add selected CloneSpec customizations to this CloneSpec
//
mySpec.Customization = itmSpecItem.Spec;
//
// Handle hostname for either windows or linux
//
if (cboCustomizations.SelectedValue == "Windows")
{
    //
    // Create a windows sysprep object
    //
    CustomizationSysprep winIdent = (CustomizationSysprep)itmSpecItem.Spec.Identity;
    CustomizationFixedName hostname = new CustomizationFixedName();
    hostname.Name = txtVmName.Text;
    winIdent.UserData.ComputerName = hostname;
    //
    // Store identity in this CloneSpec
    //
    mySpec.Customization.Identity = winIdent;
}
if (cboCustomizations.SelectedValue == "Linux")
{
    //
    // Create a Linux "sysprep" object
    //
    CustomizationLinuxPrep linIdent = (CustomizationLinuxPrep)itmSpecItem.Spec.Identity;
    CustomizationFixedName hostname = new CustomizationFixedName();
    hostname.Name = txtVmName.Text;
    linIdent.HostName = hostname;
    //
    // Store identity in this CloneSpec
    //
    mySpec.Customization.Identity = linIdent;
}
//
// Create a new ConfigSpec
//
mySpec.Config = new VirtualMachineConfigSpec();
//
// Set number of CPU's
//
int numCpus = new int();
numCpus = Convert.ToInt16(cboCpuNum.SelectedValue);
mySpec.Config.NumCPUs = numCpus;
//
// Set amount of RAM
//
long memoryMb = new long();
memoryMb = (long)(Convert.ToInt16(cboRam.SelectedValue) * 1024);
mySpec.Config.MemoryMB = memoryMb;          
//
// Only handle the first network card
//
mySpec.Customization.NicSettingMap = new CustomizationAdapterMapping[1];
mySpec.Customization.NicSettingMap[0] = new CustomizationAdapterMapping();
//
// Read in the DNS from web.config and assign
//
string[] ipDns = new string[1];
ipDns[0] = WebConfigurationManager.AppSettings["dnsServer"].ToString();
mySpec.Customization.GlobalIPSettings = new CustomizationGlobalIPSettings();
mySpec.Customization.GlobalIPSettings.DnsServerList = ipDns;
//
// Create a new networkDevice
//
VirtualDevice networkDevice = new VirtualDevice();
foreach (VirtualDevice vDevice in itmVirtualMachine.Config.Hardware.Device)
{
    //
    // get nic on vm
    //
    if (vDevice.DeviceInfo.Label.Contains("Network"))
    {
        networkDevice = vDevice;
    }
}
//
// Create a DeviceSpec
//
VirtualDeviceConfigSpec[] devSpec = new VirtualDeviceConfigSpec[0];
mySpec.Config.DeviceChange = new VirtualDeviceConfigSpec[1];
mySpec.Config.DeviceChange[0] = new VirtualDeviceConfigSpec();
mySpec.Config.DeviceChange[0].Operation = VirtualDeviceConfigSpecOperation.edit;
mySpec.Config.DeviceChange[0].Device = networkDevice;
//
// Define network settings for the new vm
//
CustomizationFixedIp ipAddress = new CustomizationFixedIp();
ipAddress.IpAddress = txtIpAddress.Text;
mySpec.Customization.NicSettingMap[0].Adapter = new CustomizationIPSettings();
//
// Assign IP address
//
mySpec.Customization.NicSettingMap[0].Adapter.Ip = ipAddress;
mySpec.Customization.NicSettingMap[0].Adapter.SubnetMask = txtSubnetMask.Text;
//
// Assign default gateway
//
string[] ipGateway = new string[1];
ipGateway[0] = txtDefaultGateway.Text;
mySpec.Customization.NicSettingMap[0].Adapter.Gateway = ipGateway;
//
// Create network backing information
//
VirtualEthernetCardDistributedVirtualPortBackingInfo nicBack = new VirtualEthernetCardDistributedVirtualPortBackingInfo();
nicBack.Port = new DistributedVirtualSwitchPortConnection();
//
// Connect to the virtual switch
//
VmwareDistributedVirtualSwitch dvSwitch = GetDvSwitch(vimClient, itmDvPortGroup.Config.DistributedVirtualSwitch);
//
// Assign the proper switch port
//
nicBack.Port.SwitchUuid = dvSwitch.Uuid;
//
// Connect the network card to proper port group
//
nicBack.Port.PortgroupKey = itmDvPortGroup.MoRef.Value;
mySpec.Config.DeviceChange[0].Device.Backing = nicBack;
//
// Enable the network card at bootup
//
mySpec.Config.DeviceChange[0].Device.Connectable = new VirtualDeviceConnectInfo();
mySpec.Config.DeviceChange[0].Device.Connectable.StartConnected = true;
mySpec.Config.DeviceChange[0].Device.Connectable.AllowGuestControl = true;
mySpec.Config.DeviceChange[0].Device.Connectable.Connected = true;
//
// Get the vmfolder from the datacenter
//
List<ClusterComputeResource> lstClusters = GetClusters(vimClient, cboClusters.SelectedItem.Text);
List<Datacenter> lstDatacenters = GetDcFromCluster(vimClient, lstClusters[0].Parent.Value);
Datacenter itmDatacenter = lstDatacenters[0];
//
// Perform the clone
//
itmVirtualMachine.CloneVM_Task(itmDatacenter.VmFolder, txtVmName.Text, mySpec);
           
vimClient.Disconnect();
Jeffrey S. Patton Systems Specialist, Enterprise Systems University of Kansas 1001 Sunnyside Ave. Lawrence, KS. 66045 http://patton-tech.com
0 Kudos