Create multiple Virtual Machines and configure them with PowerCLI

As a follow up to yesterday’s post, here is the first serious PowerCLI script I wrote. I took ideas from so many sources that I can’t properly give credit. Still, it was the project that got me hooked on scripting.

This script will create multiple virtual machines on specified hosts based on the contents of a CSV file. It reads the file, then creates the VM with the specifications. I really love this script because it makes new deployments without existing templates easy. I couple this with a specially crafted ISO file of the windows OS of my choice to make installation automated. Another powershell script inside the OS makes configuring it a snap. In a few minutes I can go from a set of ISOs to several fully deployed VMs.

On with the script:

<# ===============================================================
Purpose:	To create VMs from a CSV file, attach the ISO, and power it on.
Author:		Michael Kenning (mjkenning@gmail.com)
Version:	1.1 (31 OCT 2013)
Usage: 		.\createVMs.ps1 [INPUT FILENAME] [VCENTER SERVER]
Example: 	.\createVMs.ps1 .\vm_list.csv VC-CITY

Notes:		Make sure the CSV file is formatted as per the following:
 			vmname,vmhost,iso,isodatastore,datastore,cpu,mem,disksize,disktype,netname
			Tested this script on:
			1) Powershell v3
			2) PowerCLI 5.1-5.5
			3) vSphere 5.1-5.5U1
# ===============================================================
#>

param (
	[Parameter(Mandatory=$true,Position=0)]
	[ValidateNotNullOrEmpty()]
	[String]
	$path,
	[Parameter(Mandatory=$true,Position=1)]
	[ValidateNotNullOrEmpty()]
	[String]
	$myVC
)

####### Functions #########
# This function returns the iSCSI Host Bindings
function Get-VMHostiSCSIBinding {
<#
 .SYNOPSIS
 Function to get the iSCSI Binding of a VMHost.
 
 .DESCRIPTION
 Function to get the iSCSI Binding of a VMHost.
 
 .PARAMETER VMHost
 VMHost to get iSCSI Binding for.
 
.PARAMETER HBA
 HBA to use for iSCSI
 
.INPUTS
 String.
 System.Management.Automation.PSObject.
 
.OUTPUTS
 VMware.VimAutomation.ViCore.Impl.V1.EsxCli.EsxCliObjectImpl.
 
.EXAMPLE
 PS> Get-VMHostiSCSIBinding -VMHost ESXi01 -HBA "vmhba32"
 
 .EXAMPLE
 PS> Get-VMHost ESXi01,ESXi02 | Get-VMHostiSCSIBinding -HBA "vmhba32"
#>
[CmdletBinding()][OutputType('VMware.VimAutomation.ViCore.Impl.V1.EsxCli.EsxCliObjectImpl')]
 
Param
 (
 
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
 [ValidateNotNullOrEmpty()]
 [PSObject[]]$VMHost,
 [parameter(Mandatory=$true,ValueFromPipeline=$false)]
 [ValidateNotNullOrEmpty()]
 [String]$HBA
 )
 
begin {
 
 }
 
 process {
 
 foreach ($ESXiHost in $VMHost) {
 
try {
 
if ($ESXiHost.GetType().Name -eq "string") {
 
 try {
 $ESXiHost = Get-VMHost $ESXiHost -ErrorAction Stop
 }
 catch [Exception]{
 Write-Warning "VMHost $ESXiHost does not exist"
 }
 }
 
 elseif ($ESXiHost -isnot [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]) {
 Write-Warning "You did not pass a string or a VMHost object"
 Return
 }
 
 # --- Check for the iSCSI HBA
 try {
 
$iSCSIHBA = $ESXiHost | Get-VMHostHba -Device $HBA -Type iSCSI
 }
 catch [Exception]{
 
throw "Specified iSCSI HBA does not exist"
 }
 
# --- Set the iSCSI Binding via ESXCli
 Write-Verbose "Getting iSCSI Binding for $ESXiHost"
 $ESXCli = Get-EsxCli -VMHost $ESXiHost
 
$ESXCli.iscsi.networkportal.list($HBA)
 }
 catch [Exception]{
 
 throw "Unable to get iSCSI Binding config"
 }
 }
 }
 end {
 
 }
}

# This function sets various parameters for the Host iSCSI Bindings
function Set-VMHostiSCSIBinding {
<#
 .SYNOPSIS
 Function to set the iSCSI Binding of a VMHost.
 
 .DESCRIPTION
 Function to set the iSCSI Binding of a VMHost.
 
 .PARAMETER VMHost
 VMHost to configure iSCSI Binding for.
 
.PARAMETER HBA
 HBA to use for iSCSI
 
.PARAMETER VMKernel
 VMKernel to bind to
 
.PARAMETER Rescan
 Perform an HBA and VMFS rescan following the changes
 
.INPUTS
 String.
 System.Management.Automation.PSObject.
 
.OUTPUTS
 None.
 
.EXAMPLE
 PS> Set-VMHostiSCSIBinding -HBA "vmhba32" -VMKernel "vmk1" -VMHost ESXi01 -Rescan
 
 .EXAMPLE
 PS> Get-VMHost ESXi01,ESXi02 | Set-VMHostiSCSIBinding -HBA "vmhba32" -VMKernel "vmk1"
#>
[CmdletBinding()]
 
Param
 (
 
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
 [ValidateNotNullOrEmpty()]
 [PSObject[]]$VMHost,
 
[parameter(Mandatory=$true,ValueFromPipeline=$false)]
 [ValidateNotNullOrEmpty()]
 [String]$HBA,
 
[parameter(Mandatory=$true,ValueFromPipeline=$false)]
 [ValidateNotNullOrEmpty()]
 [String]$VMKernel,
 
[parameter(Mandatory=$false,ValueFromPipeline=$false)]
 [Switch]$Rescan
 )
 
begin {
 
}
 
 process {
 
 foreach ($ESXiHost in $VMHost) {
 
try {
 
if ($ESXiHost.GetType().Name -eq "string") {
 
 try {
 $ESXiHost = Get-VMHost $ESXiHost -ErrorAction Stop
 }
 catch [Exception]{
 Write-Warning "VMHost $ESXiHost does not exist"
 }
 }
 
 elseif ($ESXiHost -isnot [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl]) {
 Write-Warning "You did not pass a string or a VMHost object"
 Return
 }
 
 # --- Check for the iSCSI HBA
 try {
 
$iSCSIHBA = $ESXiHost | Get-VMHostHba -Device $HBA -Type iSCSI
 }
 catch [Exception]{
 
throw "Specified iSCSI HBA does not exist"
 }
 
# --- Check for the VMKernel
 try {
 
$VMKernelPort = $ESXiHost | Get-VMHostNetworkAdapter -Name $VMKernel -VMKernel
 }
 catch [Exception]{
 
throw "Specified VMKernel does not exist"
 }
 
# --- Set the iSCSI Binding via ESXCli
 Write-Verbose "Setting iSCSI Binding for $ESXiHost"
 $ESXCli = Get-EsxCli -VMHost $ESXiHost
 
$ESXCli.iscsi.networkportal.add($iSCSIHBA.Device, $false, $VMKernel)
 
Write-Verbose "Successfully set iSCSI Binding for $ESXiHost"
 
# --- Rescan HBA and VMFS if requested
 if ($PSBoundParameters.ContainsKey('Rescan')) {
 
Write-Verbose "Rescanning HBAs and VMFS for $ESXiHost"
 $ESXiHost | Get-VMHostStorage -RescanAllHba -RescanVmfs | Out-Null
 }
 }
 catch [Exception]{
 
 throw "Unable to set iSCSI Binding config"
 }
 }
 }
 end {
 
 }
}

# This function returns all datastores that can be shared amongst ESX hosts.
function Get-ShareableDatastore {
	# Get all datastores.
	$datastores = Get-Datastore

	# Load the HostStorageSystems of all hosts.
	$hosts = Get-VMHost | Get-View -property ConfigManager
	$storageSystems = @()
	foreach ($h in $hosts) {
		$sdi = Get-View $h.ConfigManager.StorageSystem -Property StorageDeviceInfo
		Write-Debug ("GSD: SDI for host $h is " + $sdi)
		$storageSystems += $sdi
	}

	foreach ($dso in $datastores) {
		$ds = $dso | Get-View -Property Info

		# Check if this datastore is NFS.
		$dsInfo = $ds.Info
		Write-Debug ("GSD: Is it NFS? " + $dsInfo.getType())
		if ($dsInfo -is [VMware.Vim.NasDatastoreInfo]) {
			Write-Output $dso
			continue
		}

		# Get the first extent of the datastore.
		$firstExtent = $dsInfo.Vmfs.Extent[0]
		Write-Debug ("GSD: first extent: " + $firstExtent.DiskName)

		# Find a host that maps this LUN.
		foreach ($hss in $storageSystems) {
			$lun = $hss.StorageDeviceInfo.ScsiLun | Where { $_.CanonicalName -eq $firstExtent.DiskName }

			if ($lun) {
				Write-Debug ("GSD: found " + $lun.DeviceName + " on " + $hss.MoRef.Value)
				Write-Debug ("GSD: LUN details: Name:" + $lun.DisplayName + ", Type:" + $lun.DeviceType + ", Vendor:" + $lun.Vendor + ", Model:" + $lun.Model)

				# Search the adapter topology of this host, looking for the LUN.
				$adapterTopology = $hss.StorageDeviceInfo.ScsiTopology.Adapter |
					Where { $_.Target |
						Where { $_.Lun |
							Where { $_.ScsiLun -eq $lun.key }
						}
					} | Select -First 1

				# We've found a host that has this LUN. Find how it maps to an adapter.
				$adapter = $hss.StorageDeviceInfo.HostBusAdapter | Where { $_.Key -eq $adapterTopology.Adapter }
				Write-Debug ("GSD: HBA type is: " + $adapter.getType())

				# It's shared if it's Fibre Channel or iSCSI (we checked for NFS earlier)
				if ($adapter -is [VMware.Vim.HostFibreChannelHba] -or $adapter -is [VMware.Vim.HostInternetScsiHba]) {
					Write-Debug "GSO: $dso is sharable"
					Write-Output $dso
				}

				# Otherwise it's not shared and we quit walking through hosts.
				break
			}
		}
	}
}
####### END FUNCTIONS ########

# Import the VM list
$vm_csv = Import-CSV $path

# --- Connect to the vSphere server
$error.clear()
try {
	Connect-VIServer -Server $myVC -User $adminUser -ErrorAction Stop
}
catch [Exception]{
	Write-warning "Connecting to the vCenter server failed. Please check credentials and try again."
}
if ($error) {
	$Null = Disconnect-VIServer -Server * -confirm:$False
	Throw $error
}

# --- Create the VMs

foreach ($vm in $vm_csv) {

	# Set variables
	$vmname = $vm.vmname
	$vmhost = $vm.vmhost
	$iso = $vm.iso
	$isodatastore = $vm.isodatastore
	$datastore = $vm.datastore
	$cpu = $vm.cpu
	$mem = $vm.mem
	$disksize = $vm.disksize
	$disktype = $vm.disktype
	$netname = $vm.netname
	
	# Create the VMs
	New-VM -VMHost $vmHost `
			-Name $VMname `
			-Datastore $Datastore `
			-NumCpu $CPU `
			-MemoryMB $Mem `
			-DiskMB $diskSize `
			-DiskStorageFormat $diskType `
			-NetworkName $netName

	# --- Set the properties of the VM
	$error.clear()
	try {
		Get-NetworkAdapter -VM $VMname | Set-NetworkAdapter -Type Vmxnet3 -confirm:$False -ErrorAction Stop
	}
	catch {
		Write-warning "Error Setting the Network adapter type."
	}
	if ($error) {
		$Null = Disconnect-VIServer -Server * -confirm:$False
		Throw $error
	}
	$cd = New-CDDrive -VM $VMname -ISOPath "[$isodatastore] $iso"
	$scsiController = Get-HardDisk -VM $VMname | Select -First 1 | Get-ScsiController
	Set-ScsiController -ScsiController $scsiController -Type VirtualLsiLogicSAS -confirm:$false
	
	if (!$error) {
		Start-VM -VM $VMname
		$getvm = Get-VM $VMname
		do {
			#Wait 5 seconds
			Start-Sleep -s 5
			#Check the power status
			$status = $getvm.PowerState
		} until($status -eq "PoweredOn")
		$error.clear()
		try {
			Set-CDDrive -CD $cd -StartConnected $true -Connected $true -confirm:$False
		}
		catch {
			Write-warning "Error setting the CDROM drive properties"
		}
		if ($error) {
			$Null = Disconnect-VIServer -Server * -confirm:$False
			Throw $error
		}
	}
	else {
		$Null = Disconnect-VIServer -Server * -confirm:$False
		Throw $error
	}
	# --- Restart the VM
	if ($getvm.PowerState -eq "PoweredOn") {
		Write-Host "Shutting Down" $VMname
		Stop-VM -VM $VMname -confirm:$False
		#Wait for Shutdown to complete
		$getvm = Get-VM $VMname
		do {
			#Wait 5 seconds
			Start-Sleep -s 5
			#Check the power status
			$status = $getvm.PowerState
		} until($status -eq "PoweredOff")
		Start-VM -VM $VMname
	}
	else {
		Start-VM -VM $VMname
		}
}
$Null = Disconnect-VIServer -Server * -confirm:$False
Advertisements
Tagged with:
Posted in PowerCLI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: