I was working on a project to create a 90 day eval machine that used linked clones. This article isn’t meant to educate on Linked clones although it’s a great concept to have a Parent VM and another disk that contains just changes. The benefits saves a lot of space in storage.
Text only version of script
https://iislogs.com/ss/linkedclone.txt
Here is a couple articles to check on linked clones
Here is the script
param(
[string]$ParentVMName = “UP1”,
[string]$vCenterHostName = “vCenterSS”,
[string]$vCenterUserName= “domainuser”,
[string]$vCenterUserPassword= “UP2”,
[string]$cloneName = “UP3”,
[string]$OSType = “Windows”,
[string]$TimeZone = “035”,
[string]$DHCPPortGroup = “123456”,
[string]$Domain = “example.com”,
[string]$DomainUserName = “[email protected]”,
[string]$DomainPassword = “UP4”,
[string]$AdminPassword = “changeme”,
[string]$FullName = “CompanyA”,
[string]$OrgName = “Example, Inc”,
[string]$CustomizeTemplate1 = “$($cloneName)-Temp1”,
[string]$CustomizeTemplate2 = “$($cloneName)-Temp2”,
[string]$ServerDescription = “UP5”,
[string]$PrimaryDNSSuffix = “example.com”,
#This is a enum VM, PREFIX CUSTOM
[string]$NamingScheme=”VM”
)
function LoadSnapin{
param($PSSnapinName)
if (!(Get-PSSnapin | where {$_.Name -eq $PSSnapinName})){
Add-pssnapin -name $PSSnapinName
}
}
LoadSnapin -PSSnapinName “VMware.VimAutomation.Core”
#Better to pass in an array and randomly select datastore
function GetDataStore
{
$value = (Get-Random) %3
switch ($value)
{
0 { return “datastore1” }
1 { return “datastore2” }
2 { return “datastore3” }
default {return “datastore1”}
}
}
# Constants for status
$STATUS_VM_NOT_STARTED = “VmNotStarted”
$STATUS_CUSTOMIZATION_NOT_STARTED = “CustomizationNotStarted”
$STATUS_STARTED = “CustomizationStarted”
$STATUS_SUCCEEDED = “CustomizationSucceeded”
$STATUS_FAILED = “CustomizationFailed”
$STATUS_NOT_COMPLETED_LIST = @( $STATUS_CUSTOMIZATION_NOT_STARTED, $STATUS_STARTED )
# constants for event types
$EVENT_TYPE_CUSTOMIZATION_STARTED = “VMware.Vim.CustomizationStartedEvent”
$EVENT_TYPE_CUSTOMIZATION_SUCCEEDED = “VMware.Vim.CustomizationSucceeded”
$EVENT_TYPE_CUSTOMIZATION_FAILED = “VMware.Vim.CustomizationFailed”
$EVENT_TYPE_VM_START = “VMware.Vim.VmStartingEvent”
# seconds to sleep before next loop iteration
$WAIT_INTERVAL_SECONDS = 15
[int] $timeoutSeconds = 1200
function GetOSCustomizationSpecStatus($vm, $timeoutSeconds)
{
# the moment in which the script has started
# the maximum time to wait is measured from this moment
$startTime = Get-Date
# we will check for “start vm” events 5 minutes before current moment
$startTimeEventFilter = $startTime.AddMinutes(-5)
# initializing list of helper objects
# each object holds VM, customization status and the last VmStarting event
$vmDescriptors = New-Object System.Collections.ArrayList
Write-Host “Start monitoring customization process for vm ‘$vm’”
$obj = “” | select VM,CustomizationStatus,StartVMEvent
$obj.VM = $vm
# getting all events for the $vm,
# filter them by type,
# sort them by CreatedTime,
# get the last one
$obj.StartVMEvent = Get-VIEvent -Entity $vm -Start $startTimeEventFilter | where { $_ -is $EVENT_TYPE_VM_START } | Sort CreatedTime | Select -Last 1
if (-not $obj.StartVMEvent)
{
$obj.CustomizationStatus = $STATUS_VM_NOT_STARTED
} else
{
$obj.CustomizationStatus = $STATUS_CUSTOMIZATION_NOT_STARTED
}
[void]($vmDescriptors.Add($obj))
# declaring script block which will evaluate whether
# to continue waiting for customization status update
$shouldContinue = {
# is there more virtual machines to wait for customization status update
# we should wait for VMs with status $STATUS_STARTED or $STATUS_CUSTOMIZATION_NOT_STARTED
$notCompletedVms = $vmDescriptors | where { $STATUS_NOT_COMPLETED_LIST -contains $_.CustomizationStatus }
# evaluating the time that has elapsed since the script is running
$currentTime = Get-Date
$timeElapsed = $currentTime – $startTime
$timoutNotElapsed = ($timeElapsed.TotalSeconds -lt $timeoutSeconds)
# returns $true if there are more virtual machines to monitor
# and the timeout is not elapsed
return ( ($notCompletedVms -ne $null) -and ($timoutNotElapsed) )
}
while (& $shouldContinue)
{
foreach ($vmItem in $vmDescriptors)
{
$vmName = $vmItem.VM.Name
switch ($vmItem.CustomizationStatus)
{
$STATUS_CUSTOMIZATION_NOT_STARTED
{
# we should check for customization started event
$vmEvents = Get-VIEvent -Entity $vmItem.VM -Start $vmItem.StartVMEvent.CreatedTime
$startEvent = $vmEvents | where { $_ -is $EVENT_TYPE_CUSTOMIZATION_STARTED }
if ($startEvent) {
$vmItem.CustomizationStatus = $STATUS_STARTED
Write-Host “Customization for VM ‘$($vm)’ has started”
}
break;
}
$STATUS_STARTED
{
# we should check for customization succeeded or failed event
$vmEvents = Get-VIEvent -Entity $vmItem.VM -Start $vmItem.StartVMEvent.CreatedTime
$succeedEvent = $vmEvents | where { $_ -is $EVENT_TYPE_CUSTOMIZATION_SUCCEEDED }
$failedEvent = $vmEvents | where { $_ -is $EVENT_TYPE_CUSTOMIZATION_FAILED }
if ($succeedEvent)
{
$vmItem.CustomizationStatus = $STATUS_SUCCEEDED
Write-Host “Customization for VM ‘$($vm)’ has successfully completed”
}
if ($failedEvent)
{
$vmItem.CustomizationStatus = $STATUS_FAILED
Write-Host “Customization for VM ‘$($vm)’ has failed”
}
break;
}
default
{
# in all other cases there is nothing to do
# $STATUS_VM_NOT_STARTED -> if VM is not started, there’s no point to look for customization events
# $STATUS_SUCCEEDED -> customization is already succeeded
# $STATUS_FAILED -> customization
break;
}
} # end of switch
} # end of the foreach loop
Write-Host “Sleeping for $WAIT_INTERVAL_SECONDS seconds”
Sleep $WAIT_INTERVAL_SECONDS
} # end of while loop
# preparing result, without the helper column StartVMEvent
$result = $vmDescriptors | select VM,CustomizationStatus
Write-Host “I’m here $result[1]”
if($result[1] -ne “CustomizationSucceeded”)
{
write-host “Waiting for VM Tools to Start”
<#
do {
$toolsStatus = (Get-VM $cloneName).extensiondata.Guest.ToolsStatus
write-host $toolsStatus
start-sleep -s 5
} until ( $toolsStatus -eq ‘toolsOk’ )
#>
Write-Host “Tools are running”
$InvokeAdminPassword = ConvertTo-SecureString -String $AdminPassword -asplaintext -force
$cmdAddToDomain = “netdom join /d:$($Domain) $cloneName /ud:$($DomainUserName) /pd:$($DomainPassword) /reboot:30”
If ($ParentVMName -match ‘DEV1’ )
{
$varAddToDomain = Invoke-VMScript -VM $cloneName -ScriptText $cmdAddToDomain -GuestUser “DevAdmin1” -GuestPassword $InvokeAdminPassword
}
Else
{
$varAddToDomain = Invoke-VMScript -VM $cloneName -ScriptText $cmdAddToDomain -GuestUser “Admin1” -GuestPassword $InvokeAdminPassword
}
$varAddToDomain = Invoke-VMScript -VM $cloneName -ScriptText $cmdAddToDomain -GuestUser “Admin1” -GuestPassword $InvokeAdminPassword
Write-Host $varAddToDomain
}
return $result
}
function AddToDomain
{
write-host “Waiting for VM Tools to Start”
<# do {
$toolsStatus = (Get-VM $cloneName).extensiondata.Guest.ToolsStatus
write-host $toolsStatus
start-sleep -s 5
} until ( $toolsStatus -eq ‘toolsOk’ )
#>
Write-Host “Tools are running”
Write-Host “Add to Domain using Netdom”
$InvokeAdminPassword = ConvertTo-SecureString -String $AdminPassword -asplaintext -force
$cmd = “netdom join /d:$($Domain) $cloneName /ud:$($DomainUserName) /pd:$($DomainPassword) /reboot:30”
If ($ParentVMName -match ‘Dev1’ )
{
$netDomOutPut = Invoke-VMScript -VM $cloneName -ScriptText $cmd -GuestUser “DevAdmin1” -GuestPassword $InvokeAdminPassword
}
Else
{
$netDomOutPut = Invoke-VMScript -VM $cloneName -ScriptText $cmd -GuestUser “Admin1” -GuestPassword $InvokeAdminPassword
}
$netDomOutPut = Invoke-VMScript -VM $cloneName -ScriptText $cmd -GuestUser “Admin1” -GuestPassword $InvokeAdminPassword
$date = Get-Date -Format MM-dd-yy
#Adding output of netdom for Scorch to consume
Add-Content -path “F:LogsNetDomOutput$($cloneName)-$($date).txt” -value $netDomOutPut -force
ipconfig /registerdns
}
Connect-VIServer -Server $vCenterHostName -User $vCenterUserName -Password $vCenterUserPassword
#Creates the VM
$sourceVM = Get-VM $ParentVMName | Get-View
$cloneFolder = $sourceVM.parent
$cloneSpec = new-object Vmware.Vim.VirtualMachineCloneSpec
$cloneSpec.Snapshot = $sourceVM.Snapshot.CurrentSnapshot
$cloneSpec.Location = new-object Vmware.Vim.VirtualMachineRelocateSpec
#Creates Linked clone
$cloneSpec.Location.DiskMoveType = [Vmware.Vim.VirtualMachineRelocateDiskMoveOptions]::createNewChildDiskBacking
#Defines the Datastore, calls a function to get a specific datastore
$Datastore = GetDataStore
$cloneSpec.Location.Datastore = (Get-View -ViewType Datastore -Property Name -Filter @{“Name”=$DataStore}).MoRef
$sourceVM.CloneVM_Task( $cloneFolder, $cloneName, $cloneSpec )
$Spec1 = New-OSCustomizationSpec -FullName $FullName -OrgName $OrgName -OSType $OSType -ChangeSID -Name $CustomizeTemplate1 -Type NonPersistent -WorkGroup WK -AdminPassword $AdminPassword -TimeZone $TimeZone -Description $ServerDescription -LicenseMode PerSeat -DnsSuffix $PrimaryDNSSuffix
Start-Sleep -s 4
$Spec1 = Get-OSCustomizationSpec $CustomizeTemplate1
Start-Sleep -s 4
Get-VM $cloneName | Set-VM -OSCustomizationSpec $Spec1 -Confirm:$false -Description $ServerDescription | Start-VM
$NIC = Get-NetworkAdapter -VM $cloneName
Set-NetworkAdapter -NetworkAdapter $NIC -Connected $false -StartConnected $false -Confirm:$false
Start-Sleep -s 4
GetOSCustomizationSpecStatus -Vm $cloneName -timeoutSeconds 1200
$a = Get-VM -Name $cloneName
Stop-VM -VM $a -confirm:0
Start-Sleep -m 250
while ($a.PowerState -eq “PoweredOn”)
{
Write-Host “Sleeping for 5 seconds”
$a = get-vm -Name $cloneName
start-sleep -s 5
}
Get-VM $cloneName | Start-VM
Start-Sleep 30
$NIC = Get-NetworkAdapter -VM $cloneName
Set-NetworkAdapter -NetworkAdapter $NIC -NetworkName $DHCPPortGroup -Connected $true -StartConnected $true -Confirm:$false
Start-Sleep 30
#GetOSCustomizationSpecStatus -Vm $cloneName -timeoutSeconds 1200
AddToDomain
Remove-OSCustomizationSpec -OSCustomizationSpec $Spec1 -Confirm:$false
Disconnect-VIServer -Server $vCenterHostName -Confirm:$false | out-null