Tuesday, October 12, 2010

Vsphere PowerCLI script to clone and customize Linux guest OS

PowerShell is very powerful object-orientated programming language inWindows, the comparable counterpart in Unix/Linux is Python.
I couldn’t find a PowerCLI script to clone Linux and assign ip addresses to multiple adapters so I decided to write my own.
If you are new to Vsphere PowerCLI, you may need to check my previous post to learn how to connect to vi-server via PowerCLI at first.

There are 2 scripts:
discover-info.ps1  # Discovery information of target viserver/ESX host and generate template file to be used for clonevm.ps1
vmclone.ps1 #clone after read information from template file generated by discover-info.ps1 script
The scripts has been tested in VMware vSphere PowerCLI 4.0 for VMs in ESX 3.5/ESX4.0 managed by Virtual Center Server.
Scripts in action
[vSphere PowerCLI] D:\Temp> .\discover-info.ps1
Discovered ESX hosts:esx01 esx02 
Please enter target ESX hostname: esx01
..
Template file: vmdata.txt has been generated. Edit the file then run 'clonevm.ps1 -f file-name' to start cloning
[vSphere PowerCLI] D:\Temp> cat vmdata.txt (after customization)
============
####---------------- Discovered information
#PortGroups='Service Console' 'vlan-01' 'vlan-02'
#Folders='host' 'Discovered Virtual Machine' 'Datacenters' 'vm'
#DataStores= 'nfs_storage' 'esx01:storage1'
#resourcepools=Name: Resources ID:ResourcePool-resgroup-7 Name: Resources ID:ResourcePool-resgroup-65
####----------------
####----------------Customize your VM values using the template 
esxhost=esx01
##Resource pool id is  NOT required for using virtual machine as source, but IS required for  using template as source
#resourcepoolid=
#vm means Virtual Machine; vt means template (  resourcepoolid  is required for vt)
SrcName=vm,linuxtemplate
DstName=pstest1
##Use vm as folder if no user folders defined
Folder=vm
DataStore=nfs_storage
##Gateway is optional, enter - to skip this value
##Networkadapter type  is optional (Auto choosen for guest OS) , enter - to skip this value
##Supported adapter types: e.g e1000, Flexible, Vmxnet, EnhancedVmxnet
Network=EnhancedVmxnet,vlan-01 ,172.19.1.1,255.255.248.0,172.19.1.10 ; -,vlan-02,172.20.1.1,255.255.255.0,-
DNS=10.1.1.1,10.1.1.2
Domain=example.com
#Poweron=yes
####---------------- 
[vSphere PowerCLI] D:\Temp> .\clonevm.ps1 -f .\vmdata.txt
==> Retrieving info for source VM: linuxtemplate
==> Starting clone process ...
4%  10%  16%  21%  27%  33%  38%  45%  51%  57%  62%  68%  74%  79%  85%  91%  %
Completed Task: Task-task-2366
==> Checking new VM ...
Name                 PowerState Num CPUs Memory (MB)
----                 ---------- -------- -----------
pstest1              PoweredOff 1        4096
==> Removing existing network adapters ...
==> Adding  2 new  network adapters ...
..
==> Customizing guest OS ...
%
Completed Task: Task-task-2370
==>  Checking new VM ...
pstest1              PoweredOff 1        4096


Discover-info.ps1    download


# *Execute the script in a live PowerCLI VIsserver session*
# Generate template file to be used for clonevm.ps1
# DATE: 12 Oct 2010 
# Author: http://honglus.blogspot.com     
$template_file="vmdata.txt"
$hosts=get-vmhost | foreach { $_.name }
write-host "Discovered ESX hosts: $hosts"
switch  ($hosts.length) {
0 {  write-host "no ESX host found!  exiting.. " ; exit}
1 {  $esxhost=$hosts[0]  }
default { 
$esxhost=read-host "Please enter target ESX hostname"
if ( -not $($hosts -contains "$esxhost")  ) { write-host "Unknown host: '$esxhost', exiting.. " ; exit }
}
}
$pgs=Get-VirtualPortGroup -vmhost $esxhost |  foreach  { "'$_'" }
$folders=get-folder |  foreach  { "'$_'" }
$dss=get-datastore -vmhost $esxhost | foreach { "'$_'"}
$rspool=get-resourcepool |  foreach  { "Name: " + $_.name + " ID:" + $_.id }
write-output   " 
####---------------- Discovered information
#PortGroups=$pgs
#Folders=$folders
#DataStores= $dss
#resourcepools=$rspool
####----------------
####----------------Customize your VM values using the template 
esxhost=$esxhost
##Resource pool id is  NOT required for using virtual machine as source, but IS required for  using template as source
#resourcepoolid=
#vm means Virtual Machine; vt means template (  resourcepoolid  is required for vt)
SrcName=vm,clone1
DstName=guest1
##Use vm as folder if no user folders defined
Folder=vm
DataStore=data_store
##Gateway is optional, enter - to skip this value
##Networkadapter type  is optional (Auto choosen for guest OS) , enter - to skip this value
##Supported adapter types: e.g e1000, Flexible, Vmxnet, EnhancedVmxnet
Network=adapter-type,port-group1,ip-address1,net-mask1,gateway1 ; EnhancedVmxnet,port-group2,ip-address2,net-mask2,gateway2 ; ...
DNS=dns1,dns2
Domain=example.com
Poweron=yes
####---------------- 
"  | tee $template_file
write-host "Template file: $template_file has been generated. Edit the file then run 'clonevm.ps1 -f file-name' to start cloning"


clonevm.ps1 download


# *Execute the script in a live PowerCLI VIsserver session*
##### Clone VM for Linux guest
#Read information from template file generated by discover-info.ps1 script
#Set target host/datastore for Clone ;Start Clone
#Delete existing adapters; add new adapters; Customize guest OS; powerone VM (Optional)
#DATE: 12 Oct 2010 
#Author: http://honglus.blogspot.com     
if ( ( $args.count -ne 2 ) -or   (  $args[0] -ne "-f" ) ) { write-host "Usage: .\program -f template-filename" ; exit}
$file1=$args[1]
function Retrieve-Values {
$strings=$args[0]
$item=$args[1]
$line=[string] ($strings |  select-string -pattern "$item")
$line=$line.trim()
return $line.Substring( $line.LastIndexOf("=")+1 )
}
function wait-for-task {
if ( $args[0] -eq $null ) {write-host "failed " ;exit }
$tskid=$args[0]
$progress=get-task  -status running | foreach { if ( $_.id -eq  $tskid ) {  $_.PercentComplete } }
while ( $progress -ne $null ) {
sleep 30
$progress=get-task  -status running | foreach { if ( $_.id -eq  $tskid ) {  $_.PercentComplete} }
write-host "$progress%  " -nonewline
}
write-host  ""
write-host "Completed Task:" $tskid
}
$lines=get-content $file1 -ErrorAction stop |  select-string -notmatch "(^#|^$)"   -ErrorAction stop 
$s_esxhost=Retrieve-values $lines  "esxhost"
$s_rspoolid=Retrieve-values $lines  "resourcepoolid"
$s_srcname=Retrieve-values $lines  "SrcName"
$s_dstname=Retrieve-values $lines  "Dstname"
$s_folder=Retrieve-values $lines  "Folder"
$s_datastore=Retrieve-values $lines  "DataStore"
$s_domain=Retrieve-values $lines  "Domain"
$s_network=Retrieve-values $lines  "Network"
$s_poweron=Retrieve-values $lines  "Poweron"
$s_dns=Retrieve-values $lines  "DNS"
$s_dns=$s_dns.split(",")
$array_temp=$s_network.split(";")
$nic_cnt=$array_temp.count
#Initialize hash table
$h_network=@{atype=(1..$nic_cnt); pgroup=(1..$nic_cnt); ip=(1..$nic_cnt); mask=(1..$nic_cnt); gateway=(1..$nic_cnt) }
For ($i=0; $i -lt $nic_cnt ; $i++) {
$array_temp2=$array_temp[$i].split(",")
if ( $array_temp2.count -ne  5 ) { write-host "Expected the number of values for network is 5, given"  $array_temp2.count ; exit}
$h_network.atype[$i]=$array_temp2[0].trim() 
$h_network.pgroup[$i]=$array_temp2[1].trim()
$h_network.ip[$i]=$array_temp2[2].trim()
$h_network.mask[$i]=$array_temp2[3].trim()  
$h_network.gateway[$i]=$array_temp2[4].trim()  
}
$vmclonespec_host = New-Object VMware.Vim.VirtualMachineCloneSpec
#### Customize  Target host /Target datastore
$vmclonespec_host.location = New-Object  VMware.Vim.VirtualMachineRelocateSpec
$vmclonespec_host.location.host = (Get-vmHost -Name $s_esxhost | Get-View).MoRef
$vmclonespec_host.location.datastore = (Get-Datastore -Name $s_datastore| Get-View).MoRef
if ( $s_rspoolid.length -gt 0 )  {
$vmclonespec_host.location.pool= (Get-ResourcePool -id $s_rspoolid  -ErrorAction stop|get-view).MoRef 
}
####  Customize Guest OS
$vmclonespec_os= New-Object  VMware.Vim.CustomizationSpec
## Identity for Linux 
$vmclonespec_os.identity= New-Object VMware.Vim.CustomizationLinuxPrep
$vmclonespec_os.identity.hostname= New-Object VMware.Vim.CustomizationFixedName
$vmclonespec_os.identity.hostname.name= $s_dstname
$vmclonespec_os.identity.domain=$s_domain
#GlobalIPSettings
$vmclonespec_os.GlobalIPSettings = New-Object VMware.Vim.CustomizationGlobalIPSettings
$vmclonespec_os.GlobalIPSettings.dnsServerList=$s_dns
$vmclonespec_os.GlobalIPSettings.dnsSuffixList=$s_domain
# adapter mapping
for ( $i=0;  $i -lt $nic_cnt; $i++ )  {
$vmclonespec_os.NicSettingMap += @(New-Object VMware.Vim.CustomizationAdapterMapping) 
$vmclonespec_os.NicSettingMap[$i].Adapter = New-Object VMware.Vim.CustomizationIPSettings
$vmclonespec_os.NicSettingMap[$i].Adapter = New-Object VMware.Vim.CustomizationIPSettings
# FixedIP
$vmclonespec_os.NicSettingMap[$i].Adapter.Ip = New-Object VMware.Vim.CustomizationFixedIp
$vmclonespec_os.NicSettingMap[$i].Adapter.Ip.IpAddress = $h_network.ip[$i]
$vmclonespec_os.NicSettingMap[$i].Adapter.SubnetMask = $h_network.mask[$i]
if ( $h_network.gateway[$i] -ne "-" ) {
$vmclonespec_os.NicSettingMap[$i].Adapter.Gateway = $h_network.gateway[$i] 
}
}
$s_srcname_pre=$s_srcName.substring(0,3)
$s_srcname=$s_srcname.Substring( $s_srcname.LastIndexOf(",")+1 )
# Template  or Virtual Machine from which the guest will be cloned
write-host "==> Retrieving info for source VM: $s_srcname"
if ( $s_srcname_pre -eq "vm," ) {
$svm = Get-View (get-vm  $s_srcname -ErrorAction stop ).ID 
}
else {
$svm = Get-View (get-template  $s_srcname -ErrorAction stop ).ID  
}
$tfld = Get-Folder -Name $s_folder -ErrorAction stop  | Get-View
write-host "==> Starting clone process ... "
$taskid=$svm.CloneVM_Task($tfld.moref,$s_dstname,$vmclonespec_host)   | foreach {$_.value}
Wait-for-Task  "Task-$taskid"
write-host "==> Checking new VM ..." 
get-vm $s_dstname -ErrorAction stop 
write-host "==> Removing existing network adapters ...  "
$adapter = Get-NetworkAdapter -VM "$s_dstname"   -ErrorAction stop
if ( $adapter -ne $null ) { Remove-NetworkAdapter -NetworkAdapter $adapter -confirm:$false }
write-host "==> Adding "  $nic_cnt "new  network adapters ...  "
for ( $i=0;  $i -lt $nic_cnt; $i++ )  {
if ( $h_network.atype[$i]  -ne "-" ) {
New-NetworkAdapter -Type  $h_network.atype[$i]   -NetworkName $h_network.pgroup[$i] -VM $s_dstname  -StartConnected -ErrorAction stop
}
else {
New-NetworkAdapter     -NetworkName $h_network.pgroup[$i] -VM $s_dstname  -StartConnected -ErrorAction stop
}
}
write-host "==> Customizing guest OS ...  "
$newvm = Get-View (get-vm  $s_dstname -ErrorAction stop ).ID 
$taskid=$newvm.CustomizeVM_Task($vmclonespec_os) | foreach {$_.value}
Wait-for-Task  "Task-$taskid"
write-host "==>  Checking new VM ..." 
sleep 4
get-vm $s_dstname -ErrorAction stop
if ( $S_poweron -eq "yes" ) { start-vm $s_dstname }


Related Documents:

1. vSphere PowerCLI Quick Start


2. Master-PowerShell 
3. VMware vSphere Web Services SDK 4.0 (Supported languages: PowerShell  C# JAVA Perl )

1 comment:

  1. Discover-info.ps1:
    to work properly you have to explicit declare the $hosts variable as an array:

    $hosts = @(get-vmhost | foreach { $_.name } | Sort-Object)

    if not, the .lenght property, in case you have only one items, counts the characters numbers of the string instead 1 array element

    ReplyDelete