Tag Archives: Powershell

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

 

Interview – Chris Wahl – Author, Blogger, IT Consultant

My fourth blog posts is with one of my favourite online personalities, Chris Wahl, a person whom I’ve followed the blog of for a long time. I won’t add too much preface to this, and dive straight in.

1. So Chris, you’ve are known in the community for two main subject area’s, networking and scripting, as well as technical author, but can you give me a quick run down of yourself for those whom are not completely familiar of your work?

Over the past 18 years of being employed in the technology sector, I’d boil it down to spending a lot of time problem solving as either a customer or consultant in various environments. I’m most proud of having published Networking for VMware Administrators with my friend Steve Pantol, achieving the VMware Certified Design Expert (VCDX) certification, and publishing over 70 episodes of the Datanauts Podcast with my co-host Ethan Banks. I use the words “snazzy” and “groovy” frequently while also borrowing quotes and images from my hero, SpongeBob, while focusing on the wonderful world of Startup Life at Rubrik as the Chief Technical Evangelist.

2. What is the biggest challenge you have in your job day-to-day at the moment.

Imagine a formula one racecar zooming down the motorway. It’s really fast, right? Now, imagine that Ellon Musk strapped a pair of SpaceX rockets to the sides. That’s a bit what it’s like to work at a startup – very fast paced. My biggest challenge is keeping up to date with engineering, product, sales, marketing, and support while traveling the world to spend time and attention on customers, their needs, and how they can be met by the team. Every job I’ve ever held has eventually become boring, but I think I’ve finally met my match for finding something that is as interesting as it is challenging.

3. If you’re hiring, what are you looking for in the candidate?

Finding people with the ability to be self-sufficient and take the initiative is my biggest focus. I prefer to set a goal and let someone figure out the best way to achieve it while being available for assistance of guidance when required. My experience has taught me that most everything else will fall into place if someone has the will and energy to get their work done when they know that I’m not watching their every move.

4. How do you expect the IT landscape to change over the next 5 years, and how do you expect this to affect your role?

I think it’s really all about the various applications that we build and maintain, and the evolution in how we build and maintain them. All of the change we’re going through is really focused on those two things. In five years, I would expect a lot more of the world to operate in a Kubernetes type model – build pools, assign units of work, execute in the pools, and store data where a policy engine dictates.

Those that can help organizations with this process will prosper, which is one of my main focuses at Rubrik – both in term of our software, but also how I approach engaging with other IT professionals. Embracing the concepts required to build and maintain the next generation of applications – such as building automation tasks using an API and planting those into an orchestration engine – are the future. How much of this future applies to any one individual is variable, but the overall model makes a lot of sense and is the only real way to construct applications for the needs of 2020 and beyond.

5. What’s the costliest mistake you’ve made in your career?

I once pushed a script into production that accidentally wiped the system32 directory from any Active Directory attached computer object that pulled down a gpupdate. Even though I caught the mistake quickly, it required my team and me to stream new OS images to over 100 PCs over the course of a day. It may not have been the most expensive from a dollars perspective, but it taught me the lesson of testing and not being too avant-garde with automation. It took me a while to bounce back from this mistake and feel confident in my abilities as a systems administrator.

6. What have been the successes and failures of your blog site so far?

I’ve never really thought of my blog in those terms. Based on the comments, I think people are able to read the content and learn a thing or two, which is the fuel that keeps me going. I certainly have looked things up on my blog on more than one occasion. Beyond that, it continues to be a place where I can explore my own thoughts and keep from forgetting the things that I’ve learned. I’m happy that the virtualization community has been kind enough to vote for the site in a handful of ranking systems – such as Eric Siebert’s Top vBlog survey – but am not particularly motivated on a day-by-day basis for such things.

My worst failure is anytime I get something wrong on the site. It makes me feel nauseous thinking that I misinformed anyone. Fortunately, most readers are quite lovely people who offer constructive feedback and I try to fix any mistakes promptly.

7. What tips can you provide to anyone blogging or thinking about starting?

Some ideas off the top off my head:

  • Don’t worry about creating content about a topic that others have written about.
  • Offer your opinions – the why of something is almost always more interesting than the what of something.
  • Be honest about why you are writing something.
  • Be nice.
  • If you can’t think of a topic, visit Reddit / VMTN forum / Slack / Twitter and see what sorts of questions are being asked. I used the VMTN forums for years to answer questions in long-form on the blog. People seemed to like that.
8. Any tips for people getting started in IT, or looking for a focus/direction?

Technology is a vast and multi-faceted environment. Try to find something that resonates with you personally. I started as a developer writing COBOL and hated it (although the COBOL probably had more to do with it than anything else). I switched majors and became a network engineer because it was so much fun to me! Now, I’m enjoying a little bit of both worlds. There’s also a bazillion free learning sites, and some really inexpensive non-free learning sites (Pluralsight), which really kill off any excuses to get started in just about any area of technology.

9. Powershell is definitely a skill that future engineers need to know, what were you’re first steps into coding?

Hah. Well, I’d certainly like to think that PowerShell is a definite skill to learn, but I think it’s one of many great frameworks out there to choose from. But, if you do decide to go down the PowerShell route, I’d say that starting backwards helps. My first bits of code in PowerShell were to solve existing problems, such as building Active Directory accounts or starting a Windows service. It’s hard to learn a language without a focus. Start with those little tasks and use them to build your knowledge of the syntax and commands. From there, the rest of the journey is all about structure, formatting, and efficient ways to create code.

I didn’t have many resources to pull from when I first started to learn PowerShell, but now days there are a plethora of books and online courses to view. My advice to my younger self would be to learn more about the structure of writing good code as early as possible – such as building functions and modules with comments, limiting a function to a single set of inputs and outputs, and keeping the logic statements to a minimum for code re-use.

10. The majority of the traditional infrastructure stack can be configured and managed through the likes of PowerShell these days, but what caveats should people be looking for, or aware of?

The major one is the expectation of stability. Try to write your code as if nothing can be taken for granted. Especially not the inputs given to you from others (people or systems). Sanitize everything, test everything, and make sure that what parameters you are requesting are always the ones you expect. If you limit the hazards available from user error, it makes life easier for everyone.

Also, never hard code anything in your scripts or functions. I tend to abstract those into parameters or some sort of external configuration file. This keeps you from having to edit the code for when your infrastructure changes or the environmental configuration changes. This was a lesson I learned over time, and I still wince when I see some of my old functions from the past 8 years.

11. What’s next for Chris Wahl in 2017, what personal and work goals have you set yourself?

My main goals at work are to grow my team by several more people, scale-out the work that is being done to cover the massive global demand, and branch out to new communities across events covering cloud providers, technology stacks, and developer groups.

I plan to attend Microsoft Ignite; DevOps Enterprise Summit; and AWS re:Invent for the first time ever. while still attending as many of the VMware events (VMUG UserCons and VMworld) as I can. However, I also want to send my team to cover a lot of these events to build their brands and relationships in those communities.

My personal goals remain fairly simple – spend as much time with friends and family as possible, cross off more Bourbons and Scotches from my “try it” list, and continue to keep personal fitness as a top priority. The groovy thing about Austin is that it aligns nicely with all three of these goals, and allows me to attend a lot of snazzy tech events – such as OpenStack Summit and Tech Field Days – while also getting to check out SXSW and ACL for some great music.

Chris is a keen blogger, and is how we connected online and in person. Having followed Chris’ blog for a long time as our areas of interest were very similar, networking and virtualisation. A few years ago I attended the UK VMUG, and managed to meet Chris in person, I found him to be just as likeable and helpful in person as he is online via twitter. This is seen further in his interview responses, who else would admit to accidentally wiping the system32 folder of their companies machines.

Chris has found his place working for the vendor Rubrik. Focusing his efforts on the IT community and automation has his subject matter, I think it speaks volumes that Rubrik took Chris on; who are in the world of backups, and Chris a highly experienced engineer in various areas.

He might not have been the obvious choice to go and work for a company in the backup industry. But when you dig below the surface, when you are an agile company ripping up the rule book, Chris is certainly one of the experts you want on your team. 
Regards
Dean


Powershell – Get-ADuser and output the homedrives to CSV file

I had a customer with around 27 file servers used as locations for AD home drives. We needed to do some analysis on which users used which server, as things like DFS or a strategy of where to place users were not in place. So PowerShell to the rescue.

A simple version of this script is;

get-aduser -Filter * -properties * | select DisplayName,Enabled,HomeDirectory,LastLogonDate,CanonicalName | Export-csv -path c:\scripts\userhomefolder.csv

I created this more complex script after the amount of unique objects exceeded the maximum filter within excel, so by splitting into a file per server fixed this.

First off, create an array with the multiple file servers, then used the “foreach” command to loop a PowerShell command with each file server name.

We look into all user’s in AD and output to a CSV file any users with file server X into a CSV file.

#Add the AD module into the Powershell session
Import-module ActiveDirectory

#Array containing each File Server, can be FQDN or short name
$fileservers = 'FS1','FS2','FS3'

#Loop to run a script for each object in the array against all AD users, outputs in CSV to C:\ folder
Foreach ($fileserver in $Fileservers)
{
get-aduser -Filter * -properties * | select DisplayName,Enabled,HomeDirectory,LastLogonDate,CanonicalName | Where {$_.HomeDirectory -like "*$fileserver*"} |Export-csv -path c:\scripts\userhomefolder2-$fileserver.csv
}

 

Regards

 

Dean

Further ESXi 6.0 CBT bug info – Reset your CBT!!!

Following on from the recent (November 2015) ESXi 6.0 CBT bug, which has now been fixed in the latest released patch ESXi600-201511401-BG, some further information has come to light, provided by Anton Gostev, of Veeam.

You can read the snippet of important information from the Veeam forum post following the issue (Official Veeam KB2075);

All, we have completed the first day of testing in the same exact lab and using the same heavy write I/O test that made the original issue easily reproducible. After a few TB of increments, the above-mention patch appears to fully resolve the original issue when installed on ESX 6.0 Update 1a build 3073146.

However, we found that simply installing the patch is not sufficient, and CBT reset is required for all of your VMs. This is because existing CBT map files may contain issues created earlier due to the original bug, which may result in inconsistent full backups in future. Having CBT reset will also force the following job run use "full scan" incremental pass, thus fixing any existing inconsistencies in backups and replicas, as discussed earlier in this topic.

Provided CBT reset has been performed, Active Full backups is not required.

Performing Active Full backups by itself cannot be considered as a substitute to CBT reset with this particular CBT issue.

Thanks!

You can either follow the CBT Reset instructions from Veeam or look over to Chris Wahl’s latest blog post “Resetting VMware’s Changed Block Tracking (CBT) File with PowerCLI”.

Regards

Dean

More Blogs and sites I’ve been reading and sharing

My Firefox tabs have filled up again, some of these tabs have been open since the start of 2014!!!

So time to share!!!

First off, proud to announce that Cisco asked me to produce a blog post, and decided it was good enough to release into the wild on their site!!!

Get Certified, or get left behind!!!!

PowerShell and Scripting

http://explainshell.com/ – write down a command-line to see the help text that matches each argument

scriptcop.start-automating.com – ScriptCop is a tool to help make sure your scripts follow the rules. ScriptCop performs static analysis on your PowerShell, and provides tools for automating testing with PowerShell. Continue reading More Blogs and sites I’ve been reading and sharing