Automat­ing ESXi installs was made much eas­ier after the release of vSphere 4.1 where the Scripted Install fea­ture was added, and by using VMware Auto Deploy from VMware Labs. VMware Auto Deploy requires that you have vCen­ter and Host Pro­files in your envi­ron­ment, and that again requires that you have Enter­prise Plus licenses in your environment.

It is, how­ever, pos­si­ble to deploy ESXi in an auto­mated fash­ion com­pletely with­out vCen­ter and Host Pro­files! By using a com­bi­na­tion of a PXE based instal­la­tion and Pow­er­CLI for automat­ing the setup of ESXi after the ini­tial deploy­ment. As this setup has been put together for a spe­cific work project, my Pow­er­CLI script also copies a VM tem­plate to the deployed ESXi host as well as the vMA for admin­is­tra­tive tasks. There is one caveat with regards to this setup though, and that is that the free ver­sion of ESXi only allows Pow­er­CLI in read-only mode. This means that you will either need to get licenses for the ESXi install, or use trial licenses. With the price drop from VMware on the Remote Office / Branch Office (ROBO) licenses, we’re look­ing at using that licens­ing model for our fleet of vessels.

Overview

The “com­plete pack­age” con­sists of the fol­low­ing components:

  • Deploy­ment VM
    This is a cus­tom VM, built to pro­vide DHCP and PXE ser­vices to do the actual ESXi installation
  • Pow­er­shell + Pow­er­CLI
    Scripts that con­fig­ure the ESXi host, post instal­la­tion, and copy your ini­tial VMs to the new host

Our cur­rent process looks like this:

  1. Con­nect phys­i­cal host to deploy­ment lap­top via ethernet
  2. Start deploy­ment VM on deploy­ment laptop
  3. When deploy­ment VM is fin­ished boot­ing, start phys­i­cal host
  4. Phys­i­cal host boots of net­work and PXE and installs ESXi
  5. When ESXi instal­la­tion fin­ishes, run Pow­er­CLI script against host
  6. Dis­con­nect deploy­ment lap­top and phys­i­cal host, and con­nect phys­i­cal host to ves­sel network
  7. Con­nect vSphere Client to ESXi install and start server VM

Deploy­ment VM

Since our deploy­ment sce­nario might be a bit out of the ordi­nary, we have the deploy­ment VM set up on VMware Work­sta­tion or VMware Player on a lap­top. The rea­son for this is that we need a mobile deploy­ment model as the loca­tions we are deploy­ing this on are not sta­tic. Not only are they mobile, they are actu­ally float­ing around on rather large oceans. That’s right, we’re deploy­ing ESXi hosts on our ves­sels world wide!

Basic VM Setup

The basic setup is a stan­dard Win­dows Server 2008 R2 with IIS installed. We will not be using any of the DHCP or other net­work­ing fea­tures included in Server 2008. In our envi­ron­ment it’s con­fig­ured with a sta­tic ip of 172.16.200.1

DHCP + PXE Setup

For DHCP and PXE ser­vices, we are using Tftpd32 a free and open source appli­ca­tion that pro­vides us with all the required ser­vices for deploy­ment eg. both DHCP and PXE.

Kick­start Script

Our very basic kick­start script — ks.cfg — looks like this

vmac­cep­teula
rootpw pass­word
autopart first­disk over­writevmfs
install url http://172.16.200.1/ESXi
net­work boot­proto=dhcp device=vmnic0
reboot

Basi­cally this sets the root pass­word, auto­mat­i­cally deletes all par­ti­tions and sets up a new vmfs, tells the installer that it will find the instal­la­tion files via http on the server and sets the net­work­ing con­fig­u­ra­tion to DHCP. This will of course need tweak­ing in your envi­ron­ment, but this should at least get you started with build­ing your own. More details on the ks.cfg boot­strap com­mands can be found in the ESX and vCen­ter Server Instal­la­tion Guide

Pow­er­CLI Con­fig­u­ra­tion Script

########################################################
#
# Cre­ated by Chris­t­ian Mohn
# for Seatrans AS
#
# No war­ranty sug­gested or implied
#
########################################################</code>

#con­nect to Vir­tu­al­Cen­ter or ESX host
#
func­tion Reg­is­ter-VMX {
param($enti­ty­Name = $null,$dsNames = $null,$tem­plate = $false,$ignore = $null,$check­NFS = $false,$whatif=$false)

func­tion Get-Usage{
Write-Host “Para­me­ters incor­rect” –Fore­ground­Color red
Write-Host “Register-VMX –enti­ty­Name –dsNames [,…]“
Write-Host “enti­ty­Name : a cluster-, dat­a­cen­ter or ESX host­name (not together with –dsNames)“
Write-Host “dsNames : one or more data­s­tore­name names (not together with –enti­ty­Name)“
Write-Host “ignore : names of fold­ers that shouldn’t be checked“
Write-Host “tem­plate : reg­is­ter guests ($false)or tem­plates ($true) — default : $false“
Write-Host “check­NFS : include NFS data­s­tores — default : $false“
Write-Host “whatif : when $true will only list and not exe­cute — default : $false“
}

if(($enti­ty­Name –ne $null –and $dsNames –ne $null) –or ($enti­ty­Name –eq $null –and $dsNames –eq $null)){
Get-Usage
break
}

if($dsNames –eq $null){
switch((Get-Inven­tory –Name $enti­ty­Name).Get­Type().Name.Replace(“Wrap­per”,””)){
“Clus­ter”{
$dsNames = Get-Clus­ter –Name $enti­ty­Name | Get-VMHost | Get-Data­s­tore | where {$_.Type –eq VMFS –or $check­NFS} | % {$_.Name}
}
“Dat­a­cen­ter”{
$dsNames = Get-Dat­a­cen­ter –Name $enti­ty­Name | Get-Data­s­tore | where {$_.Type –eq VMFS –or $check­NFS} | % {$_.Name}
}
“VMHost”{
$dsNames = Get-VMHost –Name $enti­ty­Name | Get-Data­s­tore | where {$_.Type –eq VMFS –or $check­NFS} | % {$_.Name}
}
Default{
Get-Usage
exit
}
}
}
else{
$dsNames = Get-Data­s­tore –Name $dsNames | where {$_.Type –eq VMFS –or $check­NFS} | Select –Unique | % {$_.Name}
}

$dsNames = $dsNames | Sort-Object
$pat­tern = “*.vmx“
if($tem­plate){
$pat­tern = “*.vmtx“
}

fore­ach($dsName in $dsNames){
Write-Host “Check­ing ” –NoNew­line; Write-Host –Fore­ground­Color red –Back­ground­Color yel­low $dsName
$ds = Get-Data­s­tore $dsName | Select –Unique | Get-View
$dsBrowser = Get-View $ds.Browser
$dc = Get-View $ds.Par­ent
while($dc.MoRef.Type –ne “Dat­a­cen­ter”){
$dc = Get-View $dc.Par­ent
}
$tgt­folder = Get-View $dc.VmFolder
$esx = Get-View $ds.Host[0].Key
$pool = Get-View (Get-View $esx.Par­ent).Resour­ce­Pool

$vms = @()
fore­ach($vmImpl in $ds.Vm){
$vm = Get-View $vmImpl
$vms += $vm.Config.Files.VmPathName
}
$data­s­torepath = “[“ + $ds.Name + ”]“

$search­spec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$search­spec.Match­Pat­tern = $pat­tern

$taskMoRef = $dsBrowser.SearchDatastoreSubFolders_Task($data­s­torePath, $search­Spec)

$task = Get-View $taskMoRef
while (“run­ning”,“queued” –con­tains $task.Info.State){
$task.Updat­e­View­Data(“Info.State”)
}
$task.Updat­e­View­Data(“Info.Result”)
fore­ach ($folder in $task.Info.Result){
if(!($ignore –and (&{$res = $false; $folder.FolderPath.Split(”]”)[1].Trim(” /”).Split(”/”) | %{$res = $res –or ($ignore –con­tains $_)}; $res}))){
$found = $FALSE
if($folder.file –ne $null){
fore­ach($vmx in $vms){
if(($folder.Fold­er­Path + $folder.File[0].Path) –eq $vmx){
$found = $TRUE
}
}
if (–not $found){
if($folder.Fold­er­Path[-1] –ne “/”){$folder.Fold­er­Path += “/”}
$vmx = $folder.Fold­er­Path + $folder.File[0].Path
if($tem­plate){
$params = @($vmx,$null,$true,$null,$esx.MoRef)
}
else{
$params = @($vmx,$null,$false,$pool.MoRef,$null)
}
if(!$whatif){
$taskMoRef = $tgt­folder.Get­Type().Get­Method(“RegisterVM_Task”).Invoke($tgt­folder, $params)
Write-Host ‘t $vmx “reg­is­tered“
}
else{
Write-Host ‘t $vmx “reg­is­tered” –NoNew­line; Write-Host –Fore­ground­Color blue –Back­ground­Color white ” ==> What If“
}
}
}
}
}
Write-Host “Done“
}
}

# Register-VMX –enti­ty­Name “MyDat­a­cen­ter“
# Register-VMX –dsNames “datastore1”,“datastore2“
# Register-VMX –dsNames “datastore1”,“datastore2” –template:$true
# Register-VMX –enti­ty­Name “MyDat­a­cen­ter” –ignore “Some­Folder“
# Register-VMX –dsNames “datastore3”,“datastore4” –ignore “Some­Folder” –checkNFS:$true
# Register-VMX –enti­ty­Name “MyDat­a­cen­ter” –whatif:$true

if ($args[0] –eq $null)
#
{$host­Name = Read-Host “Enter ESX Host Name or IP}
#
else
#
{$host­Name = $args[0]}
#
#
#con­nect to selected Vir­tu­al­cen­ter or host server
#
Con­nect-VIS­erver $host­Name

# Set Data­s­tore Name
$dsName = “datastore1“
$ds = Get-Data­s­tore –Name $dsName
New-PSDrive –Name $dsName –Root \ –PSProvider Vim­Data­s­tore -Data­s­tore $ds

# Copy and Reg­is­ter VM from local drive to ESXi Host
Copy-Data­s­tor­e­Item C:\VMs\MyTestVM\* datastore1:\MyTestVM\ –Force
Reg­is­ter-VMX -dsNames $dsName

# Import vMA ovf
Import-VApp -Source c:\VMs\vMA\vMA-4.1.0.0-268837.ovf -VMHost $host­Name -Data­s­tore $ds

# Lets con­fig­ure the host

########################################################
# Host Con­fig­u­ra­tion #
########################################################

#Dis­able IPv6
Get-VMHost­Net­workAdapter | where { $_.Port­Group­Name –eq “Ser­vice Con­sole 1″ } | Set-VMHost­Net­workAdapter -IPv6Enabled $false

# Con­fig­ure net­work­ing
# Not fin­ished

# Con­fig­ure NTP Server
Add-VMHost­NtpServer -VMHost $host­Name -NtpServer “0.vmware.pool.ntp.org“
Add-VMHost­NtpServer -VMHost $host­Name -NtpServer “1.vmware.pool.ntp.org“
Add-VMHost­NtpServer -VMHost $host­Name -NtpServer “2.vmware.pool.ntp.org“

# Set VM Start Pol­icy
$vmstart­pol­icy = Get-VMStart­Pol­icy -VM MyTestVM
Set-VMStart­Pol­icy -Start­Pol­icy $vmstart­pol­icy -Star­tAc­tion PowerOn

This Pow­er­CLI script is not 100% fin­ished yet, the net­work­ing part remains to be auto­mated to pro­vide cor­rect con­fig­u­ra­tion based on which ves­sel we are deploy­ing to, but in gen­eral it’s pretty much good to go.

Of course, there are loads of ways to extend and improve this process, but for now this suits our needs very well. I’m sure I’ll need to revise it once vSphere.next is out and ready for deployment.

Using a model like this, com­bined with some inter­est­ing usage pat­terns for vMA you can cre­ate an auto­mated ESXi deploy­ment sce­nario that let’s you deploy, patch and man­age your remote vSphere infra­struc­ture in a pretty stream­lined fashion.

Written by . Christian is the owner of vNinja.net and a Senior Consultant for EVRY ASA, specializing in virtualization. Active twitter user and vSoup.net Virtualization Podcast co-host.