VMware LifeCycle Manager – Migration error “SSH is not enabled or invalid” – LCMMIGRATION15102

During my migration from vRSLCM 2.1 patch 2 to the latest version 8 release, I encountered the following error;

Error Code: LCMMIGRATION15102

vRSLCM Migration Failed with SSH is not enabled or Root credential invalid. Please make sure SSH is enabled or porvide the correct root credential by adding the credential to the home page locker app

Pretty obvious error, however the provided root credentials were correct, and I could use putty to connect to my existing LCM instance.

The fix

I spoke with an internal VMware employee about this, the suggestion was to just create the same authentication details again in the locker and chose them on the retry. However I decided to just go ahead and reset the SSH user on my old environment as a precaution as well.

1. Old LCM Instance > Go to Settings > System Administration

Scroll down find the section to reset the “root” user as below, enter the new password and confirm, then select save.

I would recommend you test connecting to the old LCM instance using ssh and the new creds at this stage.

2. New LCM instance, go to the locker app, and then click for passwords, you will see just icons on the left hand side but you can click the >> to expand the navigation pane.

3. Add your new credentials and save

4. Go back to Requests, find your failed task under “invokemigration” and select to retry.

You will be given an option to select which credentials you want to retry with, select your new credentials object and hit submit!

(This type of feature where you can respecify the variables on a retry is something I’ve asked for a lot!).

5. And fingers crossed you will then see the request complete successfully.

Regards

Dean

VMware vRealize LifeCycle Manager 8 – Migration Process Screenshots

VMware vRealize LifeCycle Manager 8 released earlier this week, 17th October 2019.

Note the official name and abbreviation, its a long one!

  • vRSLCM (vRealize Suite LifeCycle Manager)

You can find the supporting official documentation here;

What's New Blog Link:
What's New Blog Post

Download Link:
Product Download

Release Notes:
Release Notes

Documentation Link:
Resources
Migration Process

The best news about this release is the “easy installer“, which also allows you to migrate from older versions. In this post, I’ve documented the screenshots in steps for you, as I know many of you out there like to see the end to end process before you undergo an update yourself, so you know what to expect.

During this migration process the following will happen;

  1. New LCM virtual appliance deployed
  2. New IDM appliance deployed (unless you select to link to an existing environment)
  3. Existing LCM settings and content will be migrated
Migration Process Screenshots

1. Load up the Easy Installer UI and select the Migrate option

Procedure
1. After you download the file, mount the vra-lcm-installer.iso file.
2. Browse to the folder vrlcm-ui-installer inside the CD-ROM.
3. The folder contains three sub-folder for three operating systems. Based on your operating system, browse to the corresponding operating system folder inside vrlcm-ui-installer folder.
4. Run the executable as per the correct steps for your OS.

2. You’ll get the below introduction page explaining the Migrate option, and some pre-req info.

3. As with every software, there is a EULA to accept.

4. Select the target vCenter environment where you want the LifeCycle Manager and Identity Manager (if needed) appliances to be deployed.

You will be asked to confirm the connection SSL Thumbprint for the vCenter provided.

5. Select the Datacenter or VM folder within your target vCenter that you want to deploy the Virtual Appliances.

6. Select the compute resource within your target vCenter to deploy the Virtual Appliances

7.  Select the storage location for your virtual appliances, at this stage I am unaware if you are able to select different datastores for each Virtual Appliance.

7. Provide details for the Network configuration for the new virtual appliances. For the easy installer it is assumed you will be deploying both to the same network subnet range.

8. Provide the default passwords for both virtual appliances, this password will be used for the following accounts;

  • vRealize LifeCycle Manager
    • Root Password
    • Admin Password
  • VMware Identity Manager
    • Root Password
    • Admin Password
    • sshuser password
    • Default Configuration User Password (You will configure the name of this account later)

9. Configuration of the LifeCycle Appliance deployment

  • Name of VM in vCenter
  • IP address
  • Hostname  (FDQN as in DNS)

10. Next you will provide the details of the existing LifeCycle Manager you are migrating from. The wizard does not seem to do any prechecks of the information provided, like it does when you connect to vCenter at the start.

11.Configuration of the VMware Identity Manager appliance

  • Install a New Identity Manager
    • Name of VM in vCenter
    • IP address
    • Hostname  (FDQN as in DNS)
    • Default Configuration Admin (Provide a name that is not root or admin)
  • Import Existing VMware Identity Manager
    • No configuration necessary, it will be pulled across from the existing LCM environment.

12. The usual Summary page of all the options you have selected/configured.

13. Finally as the process runs, you will get a progress bar with the various stages. And then once complete, a link to the new vRSLCM (vRealize Suite LifeCycle Manager) UI and the request that’s created to migrate the data from the old vRSLCM environment.

So this concludes the post!

Regards

Dean

VMware Ports and Protocols – All in one place!

Getting hold of all the VMware products various ports and protocols has sometimes come across like pulling blood from a stone.

However as announced yesterday at VMworld 2019, we have a new dedicated website just for this subject!

https://ports.vmware.com/

At a high-level, this system offers the following features:

  • A centralized system that is easy for our product teams to publish and modify and update the ports and protocols for each product.
  • Provides capability to customers with several Search filters on products, releases, port number, protocol, purpose and so on.
  • Download feature filtered data for offline view.

And for all you dark theme lovers out there, yes there is also a dark theme option!

Regards

Dean

VMware Cloud Foundation – SDDC Manager License key is not valid

Quick blog post, there has been known issue in VMware Cloud Foundation 3.5, 3.7 & 3.8 that reports your SDDC manager license key as invalid as the below screenshot shows.

This has been listed in the “known issues” section of the release notes for each version, but there is no KB article for this.

Validation fails on SDDC Manager license.During the bringup process, the validation for the SDDC Manager license fails.

Workaround: Enter a blank license and proceed. You can enter the correct license value later in the process.

 

Regards

Dean

PowerCLI with a GUI – Clone a machine, add DHCP Reservations, alter CPUID

In this blog post, I am going to break down a PowerShell code I have created (with help from some colleagues). The functions of this PowerShell code are;

  • Present a GUI form to the end user
    • Connect to a vCenter
    • Select the virtual machine to be cloned
    • Select the datastore the new VM is to be stored on (display DS free space)
    • Select the host for this VM to be created against (display free memory on the host)
    • Set the new VM name
    • Create an IP reservation in both the Production and DR DHCP Scopes

Below are some functional screenshots of the code’s GUI and also a rough flowchart of what I needed to achieve.

You can skip to the end to find the full code.

A little more background on the script

So my customer had a dedicated environment for hosting their custom application, however these applications were built and running inside an old unsupported OS which expected to be running on a particular era of CPU’s to run correctly, for example todays Intel Skylake would cause the OS to panic and not run. As you can also imagine with this type of older OS, there are no VM Tools support either.

Here is the architecture diagram;

Providing DR around this environment was interesting, we could protect the VM using SRM and storage array LUN replication. But this also presented some issues, when the VM boots in DR. “what happens with networking?” hence we setup a DHCP reservation on both Production and DR. Meaning we know the VMs IP regardless of where its booted.

To get around our little deployment issues, I built this script so that my customer could easily clone a base VM and configure it to boot grabbing a known controlled IP from DHCP and have the right masked CPU. Removing the need to have an intimate knowledge of the steps to setup a new application tier and the intricacies involved.

Other information around the environment. Cloudflare offers DNS with a low TTL on records, also if you happy to hit the wrong web server, there is a configuration to bounce you to the correct one anyway. In a DR event, we change the DR web server configuration to not bounce your connection to the production side. The DB servers are setup with application level replication in real-time.

All of this environment is sat in a completely separate network zone, and some special configuration is in place on the firewall and DHCP server to allow for remote PowerShell to work from the administration network to the DHCP Servers only. There is no access to any other part of the above architecture due to zoning/firewalling/networking.

Building the GUI

This is one of the areas that I’ll happily admit to having help on, which was building the GUI. You can easily achieve something very similar by using https://poshgui.com/

We have split the code into function, making the code more portable for future use; A function is basically a named block of code. When you call the function name, the script block within that function runs. (WindowsITPro).

The first function builds the GUI form, and the first boxes that appear allowing us to connect to the vCenter

function PickVcenter()
{
#Text label for vCenter selection
$dropDownLabel = New-Object System.Windows.Forms.Label
$dropDownLabel.Location = New-Object System.Drawing.Size(10,12)
$dropDownLabel.Size = New-Object System.Drawing.Size(120, 20)
$dropDownLabel.Text = "Select vCenter Server"
$form.Controls.Add($dropDownLabel)

#Dropdown list for vCenter names
$dropDownList = New-Object System.Windows.Forms.ComboBox
$dropDownList.Location = New-Object System.Drawing.Size(150,10)
$dropDownList.Size = New-Object System.Drawing.Size(150,30)
$dropDownList.Items.Add("vCenter1")
$dropDownList.Items.Add("vCenter2")
$form.Controls.Add($dropDownList)

#Button to click off connect to selected vCenter
$button = New-Object System.Windows.Forms.Button
$button.Location = New-Object System.Drawing.Size(310, 10)
$button.Size = New-Object System.Drawing.Size(60, 20)
$button.Text = "Connect"
$button.Add_Click({ConnectVIServer})
$form.Controls.Add($button)

#Dialog title name of GUI Form, and size of form
$form.Text = "Clone Me Baby!"
$form.Size = New-Object System.Drawing.Size(1000,500)
$form.StartPosition = "CenterScreen"

$form.ShowDialog()

}

Next is the second function whereby we take the text selected in the drop down box to a string, and use this as a variable to connect to the vCenter in question.

function ConnectVIServer() {
#takes the selection in the previous function to a string, to be used as a variable
$choice = $dropDownList.SelectedItem.ToString()
try {
$viServer = Connect-VIServer $choice
if ($viServer -eq $null) { return }
#Loads the ShowVMs function should a connection be made
ShowVMs
}
catch { Write-Host -ForegroundColor Red "Exception: $_" }
}

Once authentication to your selected vCenter is running, we create the rest of the form, which will build the rest of the information as selectable variables which eventually will pipe into our final clone command when we get to press the big button!

  • List the available VM’s to clone
  • List the Datastores to select as a target
  • Which Host to clone the VM to (show memory utilization of the host)
  • The VM Name
  • The DHCP settings
function ShowVMs() {

#Text Label for listbox of VMs returned
$listBoxVMsLabel = New-Object System.Windows.Forms.Label
$listBoxVMsLabel.Location = New-Object System.Drawing.Size(10,60)
$listBoxVMsLabel.Text = "Select VM"
$form.Controls.Add($listBoxVMsLabel)
#Listbox populated with VMs
$listBoxVMs.Location = New-Object System.Drawing.Size(10,85)
$listBoxVMs.Size = New-Object System.Drawing.Size(200,20)
$listBoxVMs.Height = 200
#Command used to pull VMs from vCenter filtered on vCenter tag, this can be any mixture of the Get-VM command

Get-VM -Tag "Clone-VM" | % {
$listBoxVMs.Items.Add($_.Name)
}
$form.Controls.Add($listBoxVMs)

#Datastore list box label
$listBoxDSLabel = New-Object System.Windows.Forms.Label
$listBoxDSLabel.Location = New-Object System.Drawing.Size(250,60)
$listBoxDSLabel.Text = "Select Datastore"
$form.Controls.Add($listBoxDSLabel)
#Datastore list box
$listBoxDS.Location = New-Object System.Drawing.Size(250,85)
$listBoxDS.Size = New-Object System.Drawing.Size(200,20)
$listBoxDS.Height = 200
#Powershell command used to retrieve the datastore details
Get-DataStore | % {

$freeGB = [string]::Format("{0:#,##0}", $_.FreeSpaceGB)
$listBoxDS.Items.Add("$($_.Name) | $freeGB GB Free")
}
$form.Controls.Add($listBoxDS)

#Host list box label
$listBoxHostLabel = New-Object System.Windows.Forms.Label
$listBoxHostLabel.Location = New-Object System.Drawing.Size(475,60)
$listBoxHostLabel.Text = "Select Target Host"
$form.Controls.Add($listBoxHostLabel)
#Host list box
$listBoxHost.Location = New-Object System.Drawing.Size(475,85)
$listBoxHost.Size = New-Object System.Drawing.Size(200,20)
$listBoxHost.Height = 200
#Powershell command to find hosts, and also parse the information so we can display the free memory on the host
Get-VMHost | % {

$memoryTotalMB = $_.MemoryTotalMB
$memoryUsageMB = $_.MemoryUsageMB
$memoryFreeMB = $_.MemoryTotalMB - $_.MemoryUsageMB
$memoryFreePerc = 0.0
try { $memoryFreePerc = $memoryFreeMB / $memoryTotalMB}
catch { }
$freeMB = [string]::Format("{0:#,##0.0%}", $memoryFreePerc)
$listBoxHost.Items.Add("$($_.Name) | $freeMB Free")

}
$form.Controls.Add($listBoxHost)

#New Virtual Machine name label
$newNameLabel = New-Object System.Windows.Forms.Label
$newNameLabel.Location = New-Object System.Drawing.Size(700, 60)
$newNameLabel.Text = "New VM Name"
$form.Controls.Add($newNameLabel)
#New virtual machine name text box
$newNameTextBox.Location = New-Object System.Drawing.Size(700, 85)
$newNameTextBox.Size = New-Object System.Drawing.Size(200, 20)
$form.Controls.Add($newNameTextBox)

#Clone button on the GUI form
$cloneButton = New-Object System.Windows.Forms.Button
$cloneButton.Location = New-Object System.Drawing.Size(700, 120)
$cloneButton.Size = New-Object System.Drawing.Size(60, 20)
$cloneButton.Text = "Clone"
#Confirming actions to be taken when button is clicked
$cloneButton.Add_Click({CloneVM $listBoxVMs $listBoxDS $listBoxHost $newNameTextBox $VMIPAddrTextBox $DHCPScopeBox})
$form.Controls.Add($cloneButton)

#Virtual Machine IP address on Production
$VMIPaddr = New-Object system.windows.Forms.Label
$VMIPaddr.Text = "Production IP address"
$VMIPaddr.AutoSize = $true
$VMIPaddr.Width = 25
$VMIPaddr.Height = 10
$VMIPaddr.location = new-object system.drawing.point(700,170)
$Form.controls.Add($VMIPaddr)
#Virtual Machine IP address text box
$VMIPAddrTextBox.Width = 110
$VMIPAddrTextBox.Height = 20
$VMIPAddrTextBox.location = new-object system.drawing.point(700,190)
$Form.controls.Add($VMIPAddrTextBox)

#Production DHCP Server label
$DHCPscopes = New-Object system.windows.Forms.Label
$DHCPscopes.Text = "Production DHCP Scopes"
$DHCPscopes.AutoSize = $true
$DHCPscopes.Width = 25
$DHCPscopes.Height = 10
$DHCPscopes.location = new-object system.drawing.point(700,215)
$Form.controls.Add($DHCPscopes)

#DHCP list box for production
$DHCPScopeBox.Text = "listBox"
$DHCPScopeBox.Width = 115
$DHCPScopeBox.Height = 75
$DHCPScopeBox.location = new-object system.drawing.point(700,240)
#Specifying a varible where the credentials to be used are stored securely
$MyCredentials=IMPORT-CLIXML C:\Scripts\SecureCredentials.xml
#Powershell command to remotely connect to a server in the DMZ
Invoke-command -computername 10.10.1.1 -Scriptblock {get-dhcpserverv4scope } -credential $MyCredentials | % {
$DHCPScopeBox.Items.Add("$($_.ScopeId)")}
$Form.controls.Add($DHCPScopeBox)

#Label for text box, for IP address to be removed from the DHCP scope
$DBVMIPaddr = New-Object system.windows.Forms.Label
$DBVMIPaddr.Text = "Prod DB IP address"
$DBVMIPaddr.AutoSize = $true
$DBVMIPaddr.Width = 25
$DBVMIPaddr.Height = 10
$DBVMIPaddr.location = new-object system.drawing.point(700,330)
$Form.controls.Add($DBVMIPaddr)

#Text box, for IP address that is to be removed from DHCP scope
$DBVMIPAddrTextBox.Width = 110
$DBVMIPAddrTextBox.Height = 20
$DBVMIPAddrTextBox.location = new-object system.drawing.point(700,350)
$Form.controls.Add($DBVMIPAddrTextBox)

#Label for text box, virtual Machine IP address on DR environment
$DRVMIPaddr = New-Object system.windows.Forms.Label
$DRVMIPaddr.Text = "DR IP address"
$DRVMIPaddr.AutoSize = $true
$DRVMIPaddr.Width = 25
$DRVMIPaddr.Height = 10
$DRVMIPaddr.location = new-object system.drawing.point(820,170)
$Form.controls.Add($DRVMIPaddr)

#Text box, for IP address that is to be reserved in DHCP
$DRVMIPAddrTextBox.Width = 110
$DRVMIPAddrTextBox.Height = 20
$DRVMIPAddrTextBox.location = new-object system.drawing.point(820,190)
$Form.controls.Add($DRVMIPAddrTextBox)

#DR DHCP Server label
$DRDHCPscopes = New-Object system.windows.Forms.Label
$DRDHCPscopes.Text = "DR DHCP Scopes"
$DRDHCPscopes.AutoSize = $true
$DRDHCPscopes.Width = 25
$DRDHCPscopes.Height = 10
$DRDHCPscopes.location = new-object system.drawing.point(820,215)
$Form.controls.Add($DRDHCPscopes)

#DR DHCP server scopes
$DRDHCPScopeBox.Text = "listBox"
$DRDHCPScopeBox.Width = 115
$DRDHCPScopeBox.Height = 75
$DRDHCPScopeBox.location = new-object system.drawing.point(820,240)
#Secure creds location to be used as variable for connection to server
$DRMyCredentials=IMPORT-CLIXML C:\Scripts\DRSecureCredentials.xml
#Command used to connect to DR DHCP server
Invoke-command -computername 10.50.1.1 -Scriptblock {get-dhcpserverv4scope } -credential $DRMyCredentials | % {
$DRDHCPScopeBox.Items.Add("$($_.ScopeId)")}
$Form.controls.Add($DRDHCPScopeBox)

#Label for text box, for IP address to be removed from the DR DHCP scope
$DRDBVMIPaddr = New-Object system.windows.Forms.Label
$DRDBVMIPaddr.Text = "DR DB IP address"
$DRDBVMIPaddr.AutoSize = $true
$DRDBVMIPaddr.Width = 25
$DRDBVMIPaddr.Height = 10
$DRDBVMIPaddr.location = new-object system.drawing.point(820,330)
$Form.controls.Add($DBVMIPaddr)

#Text box, for IP address that is to be removed from DR DHCP scope
$DRDBVMIPAddrTextBox.Width = 110
$DRDBVMIPAddrTextBox.Height = 20
$DRDBVMIPAddrTextBox.location = new-object system.drawing.point(820,350)
$Form.controls.Add($DRDBVMIPAddrTextBox)

}

We now build a function which will take the returned values presented from the earlier parts of the form and convert them to usable strings for use with the PowerCLI Clone VM command.

function CloneVM()
{  
$vmName = $listBoxVMs.Items[$listBoxVMs.SelectedIndex].ToString()   
$dsName = $listBoxDS.Items[$listBoxDS.SelectedIndex].ToString().Split("|")[0].ToString().Trim()
$hostName = $listBoxHost.Items[$listBoxHost.SelectedIndex].ToString().Split("|")[0].ToString().Trim()
$DHCPscope = $DHCPScopeBox.Items[$DHCPScopeBox.SelectedIndex].ToString()
$IPaddvm = $VMIPAddrTextBox.Text
$DRIPaddvm = $DRVMIPAddrTextBox.Text
$DBIPaddvm = $DBVMIPAddrTextBox.Text
DRDHCPscope = $DRDHCPScopeBox.Items[$DRDHCPScopeBox.SelectedIndex].ToString()
	$DRDBIPaddvm = $DRDBVMIPAddrTextBox.Text
$newName = $newNameTextBox.Text

#Basic error checking, unable to continue if the below items are empty
if ($vmName.Length -eq 0) { return }
if ($dsName.Length -eq 0) { return }
if ($hostName.Length -eq 0) { return }
if ($newName.Length -eq 0) { return }
if ($DHCPScope.Length -eq 0) { return }
if ($IPaddvm.Length -eq 0) { return }
if ($DRIPaddvm.Length -eq 0) {return}

#Capture current date for logging purposes
$Date = get-date

#Output message which reads back the selected variables and confirms the action taken upon hitting the "clone" button
$message = [string]::Format("Cloning {0} on DS {1}, host {2}, named {3}", $vmName, $dsName, $hostName, $newName)
$OutputTextBox = New-Object System.Windows.Forms.TextBox
$OutputTextBox.Location = New-Object System.Drawing.Size(10, 300)
$OutputTextBox.Size = New-Object System.Drawing.Size(600, 200)
$OutputTextBox.Text = $message
$form.Controls.Add($OutputTextBox)

### Code to clone VM from selected objects ###
$VM = New-VM -VM $vmName -Name $newName -VMHost $hostName -DiskStorageFormat Thick -Datastore $dsName -Notes "Clone created $(whoami) $Date"

### Configure CPUID in VMX File ###
#Setup a connection to the datastore where the VM is located
New-PSDrive -Location $ds -Name DS -PSProvider VimDatastore -Root "\"
#copies VMX file from vmware datastore location to temporary location on c: drive
copy-datastoreitem -item "DS:$vmname\$vmname.vmx" -destination "c:\scripts\$vmname.vmx" -Force
#Regex to place CPUID settings into vmx file, thank you Ian Morris for the continued assistance with this!
get-childitem "c:\scripts\$vmname.vmx" | % {
$content = get-content $_
[System.Text.StringBuilder] $newContent = New-Object -TypeName "System.Text.StringBuilder"
[int] $i = 0
$stream = [System.IO.StreamWriter] $_.FullName
foreach ($s in $content)
{
if ([Regex]::IsMatch($s, "cpuid\.") -eq $false)
{
$stream.WriteLine($s)
[void]$newContent.Append($s)
}
}
#These are the CPUID lines mask the VM into thinking it has a Intel Xeon 2.7 GHz chip from around 2007
$stream.WriteLine("cpuid.2.eax=`"00000101101100001011000100000001`"")
$stream.WriteLine("cpuid.2.ebx=`"00000000010101100101011111110000`"")
$stream.WriteLine("cpuid.2.ecx=`"00000000000000000000000000000000`"")
$stream.WriteLine("cpuid.2.edx=`"00101100101101000011000001001000`"")
$stream.Close()
} 
#Copy the modified VMX back to the datastore, by default any duplicate named will will be replaced
copy-datastoreitem -item "c:\scripts\$vmname.vmx" -destination "DS:$vmname\$vmname.vmx"
#close the PS Drive connection to the datastore
Remove-PSDrive -Name DS
#Delete the modified VMX file from the windows machine running the script
Remove-item "c:\scripts\$vmname.vmx"

### Setup DHCP Scopes ###
#Need to recall the secure stored credentials to connect to the DHCP Servers
$MyCredentials=IMPORT-CLIXML C:\Scripts\SecureCredentials.xml
$DRMyCredentials=IMPORT-CLIXML C:\Scripts\DRSecureCredentials.xml
#Grab the new VMs MAC address and convert the string to a format usable with the DHCP Powershell Module
$vmmac = get-vm $vmName | get-networkadapter
$vmmacalt = $vmmac.MacAddress -replace ":"

#Using invoke-command to connect to DHCP servers and setup the DHCP scope reservations and remove the IP address from the available range as a precaution
Invoke-command -computername 10.10.1.1 -Scriptblock {Add-DhcpServerv4Reservation -ScopeId $Using:DHCPScope -IPAddress $Using:IPAddvm -Name $Using:vmName -ClientId $Using:vmmacalt} -credential $MyCredentials
Invoke-command -computername 10.50.1.1 -Scriptblock {Add-DhcpServerv4Reservation -ScopeId $Using:DRDHCPScope -IPAddress $Using:DRIPAddvm -Name $Using:vmName -ClientId $Using:vmmacalt} -credential $DRMyCredentials
Invoke-command -computername 10.10.1.1 -Scriptblock {Add-DhcpServerv4ExclusionRange -ScopeID $Using:DHCPScope -StartRange $Using:DBIPAddvm -EndRange $Using:DBIPAddvm } -credential $MyCredentials
Invoke-command -computername 10.50.1.1 -Scriptblock {Add-DhcpServerv4ExclusionRange -ScopeID $Using:DRDHCPScope -StartRange $Using:DRDBIPAddvm -EndRange $Using:DRDBIPAddvm } -credential $DRMyCredentials
}

Final part of the script is essentially the “Go” commands when we build run the script, this calls the necessary modules and builds the form itself from the earlier configuration.

There is also a code snippet from this blog post, which hides the console once the form is built.

### Start form
#Add necessary modules and .Net to build GUI form
Import-Module VMware.PowerCLI
Add-Type -AssemblyName System.Windows.Forms

#Hide PowerShell Console > code snippet from http://blog.dbsnet.fr/hide-powershell-console-from-a-gui
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
$consolePtr = [Console.Window]::GetConsoleWindow()
[Console.Window]::ShowWindow($consolePtr, 0)

#Build form based on earlier configuration
$form = New-Object System.Windows.Forms.Form
$listBoxVMs = New-Object System.Windows.Forms.ListBox
$listBoxDS = New-Object System.Windows.Forms.ListBox
$listBoxHost = New-Object System.Windows.Forms.ListBox
$newNameTextBox = New-Object System.Windows.Forms.TextBox
$VMIPAddrTextBox = New-Object System.Windows.Forms.TextBox
$DHCPScopeBox  = New-Object System.Windows.Forms.ListBox
$DRVMIPAddrTextBox = New-Object System.Windows.Forms.TextBox
$DRDHCPScopeBox  = New-Object System.Windows.Forms.ListBox
$DBVMIPAddrTextBox = New-Object System.Windows.Forms.TextBox
$DRDBVMIPAddrTextBox = New-Object System.Windows.Forms.TextBox

PickVcenter

This code went through a good number of revisions, and could still be improved further and expanded even more, however it does achieve its main goal and was a great learning experience for me.

In terms of troubleshooting, I occasionally had some issues with connectivity to the DHCP servers, so I’d just run up PowerShell ISE, run the section of code with “write-host $variableselected” to see what was happening, below is a screenshot of my butchered code at a time I was doing this (I know it’s in visual code this time not ISE).

I’d like to thank my ex-colleague Ian Morris whom worked with me over many weeks on this script at the time of the project, without his help it wouldn’t have been possible.

Below is the full code
function PickVcenter()
{
#Text label for vCenter selection
$dropDownLabel = New-Object System.Windows.Forms.Label
$dropDownLabel.Location = New-Object System.Drawing.Size(10,12)
$dropDownLabel.Size = New-Object System.Drawing.Size(120, 20)
$dropDownLabel.Text = "Select vCenter Server"
$form.Controls.Add($dropDownLabel)

#Dropdown list for vCenter names
$dropDownList = New-Object System.Windows.Forms.ComboBox
$dropDownList.Location = New-Object System.Drawing.Size(150,10)
$dropDownList.Size = New-Object System.Drawing.Size(150,30)
$dropDownList.Items.Add("vCenter01.vEducate.co.uk")
$dropDownList.Items.Add("vCenter02.vEducate.co.uk")
$form.Controls.Add($dropDownList)

#Button to click off connect to selected vCenter
$button = New-Object System.Windows.Forms.Button
$button.Location = New-Object System.Drawing.Size(310, 10)
$button.Size = New-Object System.Drawing.Size(60, 20)
$button.Text = "Connect"
$button.Add_Click({ConnectVIServer})
$form.Controls.Add($button)

#Dialog title name of GUI Form, and size of form
$form.Text = "Clone Me Baby!"
$form.Size = New-Object System.Drawing.Size(1000,500)
$form.StartPosition = "CenterScreen"

$form.ShowDialog()

}

function ConnectVIServer() {
#takes the selection in the previous function to a string, to be used as a variable
$choice = $dropDownList.SelectedItem.ToString()
try {
$viServer = Connect-VIServer $choice
if ($viServer -eq $null) { return }
#Loads the ShowVMs function should a connection be made
ShowVMs
}
catch { Write-Host -ForegroundColor Red "Exception: $_" }
}

function ShowVMs() {

#Text Label for listbox of VMs returned
$listBoxVMsLabel = New-Object System.Windows.Forms.Label
$listBoxVMsLabel.Location = New-Object System.Drawing.Size(10,60)
$listBoxVMsLabel.Text = "Select VM"
$form.Controls.Add($listBoxVMsLabel)
#Listbox populated with VMs
$listBoxVMs.Location = New-Object System.Drawing.Size(10,85)
$listBoxVMs.Size = New-Object System.Drawing.Size(200,20)
$listBoxVMs.Height = 200
#Command used to pull VMs from vCenter filtered on vCenter tag, this can be any mixture of the Get-VM command

Get-VM -Tag "Clone-VM" | % {
$listBoxVMs.Items.Add($_.Name)
}
$form.Controls.Add($listBoxVMs)

#Datastore list box label
$listBoxDSLabel = New-Object System.Windows.Forms.Label
$listBoxDSLabel.Location = New-Object System.Drawing.Size(250,60)
$listBoxDSLabel.Text = "Select Datastore"
$form.Controls.Add($listBoxDSLabel)
#Datastore list box
$listBoxDS.Location = New-Object System.Drawing.Size(250,85)
$listBoxDS.Size = New-Object System.Drawing.Size(200,20)
$listBoxDS.Height = 200
#Powershell command used to retrieve the datastore details
Get-DataStore | % {

$freeGB = [string]::Format("{0:#,##0}", $_.FreeSpaceGB)
$listBoxDS.Items.Add("$($_.Name) | $freeGB GB Free")
}
$form.Controls.Add($listBoxDS)

#Host list box label
$listBoxHostLabel = New-Object System.Windows.Forms.Label
$listBoxHostLabel.Location = New-Object System.Drawing.Size(475,60)
$listBoxHostLabel.Text = "Select Target Host"
$form.Controls.Add($listBoxHostLabel)
#Host list box
$listBoxHost.Location = New-Object System.Drawing.Size(475,85)
$listBoxHost.Size = New-Object System.Drawing.Size(200,20)
$listBoxHost.Height = 200
#Powershell command to find hosts, and also parse the information so we can display the free memory on the host
Get-VMHost | % {

$memoryTotalMB = $_.MemoryTotalMB
$memoryUsageMB = $_.MemoryUsageMB
$memoryFreeMB = $_.MemoryTotalMB - $_.MemoryUsageMB
$memoryFreePerc = 0.0
try { $memoryFreePerc = $memoryFreeMB / $memoryTotalMB}
catch { }
$freeMB = [string]::Format("{0:#,##0.0%}", $memoryFreePerc)
$listBoxHost.Items.Add("$($_.Name) | $freeMB Free")

}
$form.Controls.Add($listBoxHost)

#New Virtual Machine name label
$newNameLabel = New-Object System.Windows.Forms.Label
$newNameLabel.Location = New-Object System.Drawing.Size(700, 60)
$newNameLabel.Text = "New VM Name"
$form.Controls.Add($newNameLabel)
#New virtual machine name text box
$newNameTextBox.Location = New-Object System.Drawing.Size(700, 85)
$newNameTextBox.Size = New-Object System.Drawing.Size(200, 20)
$form.Controls.Add($newNameTextBox)

#Clone button on the GUI form
$cloneButton = New-Object System.Windows.Forms.Button
$cloneButton.Location = New-Object System.Drawing.Size(700, 120)
$cloneButton.Size = New-Object System.Drawing.Size(60, 20)
$cloneButton.Text = "Clone"
#Confirming actions to be taken when button is clicked
$cloneButton.Add_Click({CloneVM $listBoxVMs $listBoxDS $listBoxHost $newNameTextBox $VMIPAddrTextBox $DHCPScopeBox})
$form.Controls.Add($cloneButton)

#Virtual Machine IP address on Production
$VMIPaddr = New-Object system.windows.Forms.Label
$VMIPaddr.Text = "Production IP address"
$VMIPaddr.AutoSize = $true
$VMIPaddr.Width = 25
$VMIPaddr.Height = 10
$VMIPaddr.location = new-object system.drawing.point(700,170)
$Form.controls.Add($VMIPaddr)
#Virtual Machine IP address text box
$VMIPAddrTextBox.Width = 110
$VMIPAddrTextBox.Height = 20
$VMIPAddrTextBox.location = new-object system.drawing.point(700,190)
$Form.controls.Add($VMIPAddrTextBox)

#Production DHCP Server label
$DHCPscopes = New-Object system.windows.Forms.Label
$DHCPscopes.Text = "Production DHCP Scopes"
$DHCPscopes.AutoSize = $true
$DHCPscopes.Width = 25
$DHCPscopes.Height = 10
$DHCPscopes.location = new-object system.drawing.point(700,215)
$Form.controls.Add($DHCPscopes)

#DHCP list box for production
$DHCPScopeBox.Text = "listBox"
$DHCPScopeBox.Width = 115
$DHCPScopeBox.Height = 75
$DHCPScopeBox.location = new-object system.drawing.point(700,240)
#Specifying a varible where the credentials to be used are stored securely
$MyCredentials=IMPORT-CLIXML C:\Scripts\SecureCredentials.xml
#Powershell command to remotely connect to a server in the DMZ
Invoke-command -computername 10.10.1.1 -Scriptblock {get-dhcpserverv4scope } -credential $MyCredentials | % {
$DHCPScopeBox.Items.Add("$($_.ScopeId)")}
$Form.controls.Add($DHCPScopeBox)

#Label for text box, for IP address to be removed from the DHCP scope
$DBVMIPaddr = New-Object system.windows.Forms.Label
$DBVMIPaddr.Text = "Prod DB IP address"
$DBVMIPaddr.AutoSize = $true
$DBVMIPaddr.Width = 25
$DBVMIPaddr.Height = 10
$DBVMIPaddr.location = new-object system.drawing.point(700,330)
$Form.controls.Add($DBVMIPaddr)

#Text box, for IP address that is to be removed from DHCP scope
$DBVMIPAddrTextBox.Width = 110
$DBVMIPAddrTextBox.Height = 20
$DBVMIPAddrTextBox.location = new-object system.drawing.point(700,350)
$Form.controls.Add($DBVMIPAddrTextBox)

#Label for text box, virtual Machine IP address on DR environment
$DRVMIPaddr = New-Object system.windows.Forms.Label
$DRVMIPaddr.Text = "DR IP address"
$DRVMIPaddr.AutoSize = $true
$DRVMIPaddr.Width = 25
$DRVMIPaddr.Height = 10
$DRVMIPaddr.location = new-object system.drawing.point(820,170)
$Form.controls.Add($DRVMIPaddr)

#Text box, for IP address that is to be reserved in DHCP
$DRVMIPAddrTextBox.Width = 110
$DRVMIPAddrTextBox.Height = 20
$DRVMIPAddrTextBox.location = new-object system.drawing.point(820,190)
$Form.controls.Add($DRVMIPAddrTextBox)

#DR DHCP Server label
$DRDHCPscopes = New-Object system.windows.Forms.Label
$DRDHCPscopes.Text = "DR DHCP Scopes"
$DRDHCPscopes.AutoSize = $true
$DRDHCPscopes.Width = 25
$DRDHCPscopes.Height = 10
$DRDHCPscopes.location = new-object system.drawing.point(820,215)
$Form.controls.Add($DRDHCPscopes)

#DR DHCP server scopes
$DRDHCPScopeBox.Text = "listBox"
$DRDHCPScopeBox.Width = 115
$DRDHCPScopeBox.Height = 75
$DRDHCPScopeBox.location = new-object system.drawing.point(820,240)
#Secure creds location to be used as variable for connection to server
$DRMyCredentials=IMPORT-CLIXML C:\Scripts\DRSecureCredentials.xml
#Command used to connect to DR DHCP server
Invoke-command -computername 10.50.1.1 -Scriptblock {get-dhcpserverv4scope } -credential $DRMyCredentials | % {
$DRDHCPScopeBox.Items.Add("$($_.ScopeId)")}
$Form.controls.Add($DRDHCPScopeBox)

#Label for text box, for IP address to be removed from the DR DHCP scope
$DRDBVMIPaddr = New-Object system.windows.Forms.Label
$DRDBVMIPaddr.Text = "DR DB IP address"
$DRDBVMIPaddr.AutoSize = $true
$DRDBVMIPaddr.Width = 25
$DRDBVMIPaddr.Height = 10
$DRDBVMIPaddr.location = new-object system.drawing.point(820,330)
$Form.controls.Add($DBVMIPaddr)

#Text box, for IP address that is to be removed from DR DHCP scope
$DRDBVMIPAddrTextBox.Width = 110
$DRDBVMIPAddrTextBox.Height = 20
$DRDBVMIPAddrTextBox.location = new-object system.drawing.point(820,350)
$Form.controls.Add($DRDBVMIPAddrTextBox)

}


function CloneVM()
{  
$vmName = $listBoxVMs.Items[$listBoxVMs.SelectedIndex].ToString()   
$dsName = $listBoxDS.Items[$listBoxDS.SelectedIndex].ToString().Split("|")[0].ToString().Trim()
$hostName = $listBoxHost.Items[$listBoxHost.SelectedIndex].ToString().Split("|")[0].ToString().Trim()
$DHCPscope = $DHCPScopeBox.Items[$DHCPScopeBox.SelectedIndex].ToString()
$IPaddvm = $VMIPAddrTextBox.Text
$DRIPaddvm = $DRVMIPAddrTextBox.Text
$DBIPaddvm = $DBVMIPAddrTextBox.Text
DRDHCPscope = $DRDHCPScopeBox.Items[$DRDHCPScopeBox.SelectedIndex].ToString()
	$DRDBIPaddvm = $DRDBVMIPAddrTextBox.Text
$newName = $newNameTextBox.Text

#Basic error checking, unable to continue if the below items are empty
if ($vmName.Length -eq 0) { return }
if ($dsName.Length -eq 0) { return }
if ($hostName.Length -eq 0) { return }
if ($newName.Length -eq 0) { return }
if ($DHCPScope.Length -eq 0) { return }
if ($IPaddvm.Length -eq 0) { return }
if ($DRIPaddvm.Length -eq 0) {return}

#Capture current date for logging purposes
$Date = get-date

#Output message which reads back the selected variables and confirms the action taken upon hitting the "clone" button
$message = [string]::Format("Cloning {0} on DS {1}, host {2}, named {3}", $vmName, $dsName, $hostName, $newName)
$OutputTextBox = New-Object System.Windows.Forms.TextBox
$OutputTextBox.Location = New-Object System.Drawing.Size(10, 300)
$OutputTextBox.Size = New-Object System.Drawing.Size(600, 200)
$OutputTextBox.Text = $message
$form.Controls.Add($OutputTextBox)

### Code to clone VM from selected objects ###
$VM = New-VM -VM $vmName -Name $newName -VMHost $hostName -DiskStorageFormat Thick -Datastore $dsName -Notes "Clone created $(whoami) $Date"

### Configure CPUID in VMX File ###
#Setup a connection to the datastore where the VM is located
New-PSDrive -Location $ds -Name DS -PSProvider VimDatastore -Root "\"
#copies VMX file from vmware datastore location to temporary location on c: drive
copy-datastoreitem -item "DS:$vmname\$vmname.vmx" -destination "c:\scripts\$vmname.vmx" -Force
#Regex to place CPUID settings into vmx file, thank you Ian Morris for the continued assistance with this!
get-childitem "c:\scripts\$vmname.vmx" | % {
$content = get-content $_
[System.Text.StringBuilder] $newContent = New-Object -TypeName "System.Text.StringBuilder"
[int] $i = 0
$stream = [System.IO.StreamWriter] $_.FullName
foreach ($s in $content)
{
if ([Regex]::IsMatch($s, "cpuid\.") -eq $false)
{
$stream.WriteLine($s)
[void]$newContent.Append($s)
}
}
#These are the CPUID lines mask the VM into thinking it has a Intel Xeon 2.7 GHz chip from around 2007
$stream.WriteLine("cpuid.2.eax=`"00000101101100001011000100000001`"")
$stream.WriteLine("cpuid.2.ebx=`"00000000010101100101011111110000`"")
$stream.WriteLine("cpuid.2.ecx=`"00000000000000000000000000000000`"")
$stream.WriteLine("cpuid.2.edx=`"00101100101101000011000001001000`"")
$stream.Close()
} 
#Copy the modified VMX back to the datastore, by default any duplicate named will will be replaced
copy-datastoreitem -item "c:\scripts\$vmname.vmx" -destination "DS:$vmname\$vmname.vmx"
#close the PS Drive connection to the datastore
Remove-PSDrive -Name DS
#Delete the modified VMX file from the windows machine running the script
Remove-item "c:\scripts\$vmname.vmx"

### Setup DHCP Scopes ###
#Need to recall the secure stored credentials to connect to the DHCP Servers
$MyCredentials=IMPORT-CLIXML C:\Scripts\SecureCredentials.xml
$DRMyCredentials=IMPORT-CLIXML C:\Scripts\DRSecureCredentials.xml
#Grab the new VMs MAC address and convert the string to a format usable with the DHCP Powershell Module
$vmmac = get-vm $vmName | get-networkadapter
$vmmacalt = $vmmac.MacAddress -replace ":"

#Using invoke-command to connect to DHCP servers and setup the DHCP scope reservations and remove the IP address from the available range as a precaution
Invoke-command -computername 10.10.1.1 -Scriptblock {Add-DhcpServerv4Reservation -ScopeId $Using:DHCPScope -IPAddress $Using:IPAddvm -Name $Using:vmName -ClientId $Using:vmmacalt} -credential $MyCredentials
Invoke-command -computername 10.50.1.1 -Scriptblock {Add-DhcpServerv4Reservation -ScopeId $Using:DRDHCPScope -IPAddress $Using:DRIPAddvm -Name $Using:vmName -ClientId $Using:vmmacalt} -credential $DRMyCredentials
Invoke-command -computername 10.10.1.1 -Scriptblock {Add-DhcpServerv4ExclusionRange -ScopeID $Using:DHCPScope -StartRange $Using:DBIPAddvm -EndRange $Using:DBIPAddvm } -credential $MyCredentials
Invoke-command -computername 10.50.1.1 -Scriptblock {Add-DhcpServerv4ExclusionRange -ScopeID $Using:DRDHCPScope -StartRange $Using:DRDBIPAddvm -EndRange $Using:DRDBIPAddvm } -credential $DRMyCredentials
}

### Start form
#Add necessary modules and .Net to build GUI form
Import-Module VMware.PowerCLI
Add-Type -AssemblyName System.Windows.Forms

#Hide PowerShell Console > code snippet from http://blog.dbsnet.fr/hide-powershell-console-from-a-gui
Add-Type -Name Window -Namespace Console -MemberDefinition '
[DllImport("Kernel32.dll")]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
'
$consolePtr = [Console.Window]::GetConsoleWindow()
[Console.Window]::ShowWindow($consolePtr, 0)

#Build form based on earlier configuration

PickVcenter