Create a linked clone from a parent vm using vmware sdk

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