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

process files with .TMP file extensions

We received a question how to process with .TMP file extensions.  IISLogs has a feature called Per Directory that was introduced in 2.0.  The feature allows an administrator to have granular control on a per directory basis.  We added the .TMP extension as an option in the Per Directory feature, please review the Per Directory article for complete options.  If you have any questions, feel free to contact our support alias @ [email protected]

Open IISLogsGUI, Select Per Directory option

Fill in the Directory Name and other attributes you need.

Select TMP

Capture

When you save this, in the install folder the data is stored in a file called IISLogsPerDirectory.xml

<?xml version=”1.0″ standalone=”yes”?>
<NewDataSet>
  <Table1>
    <DirectoryName>c:inetpubtemptest</DirectoryName>
    <ZipFile>false</ZipFile>
    <ZipRetentionPeriod>0</ZipRetentionPeriod>
    <DeleteOriginalFile>false</DeleteOriginalFile>
    <DeleteFile>true</DeleteFile>
    <DeleteRetentionPeriod>48</DeleteRetentionPeriod>
    <Recursive>false</Recursive>
    <ProcessRootFolderRecursive>false</ProcessRootFolderRecursive>
    <ZipFilePath>local</ZipFilePath>
    <IncludeComputerName>false</IncludeComputerName>
    <ProcessUnknownExtensions>false</ProcessUnknownExtensions>
    <ProcessTXT>false</ProcessTXT>
    <ProcessBAK>false</ProcessBAK>
    <ProcessDAT>false</ProcessDAT>
    <ProcessXML>false</ProcessXML>
    <NamingConvention>Default</NamingConvention>
    <Delimiter>!</Delimiter>
    <ProcessEXE>false</ProcessEXE>
    <ProcessMSP>false</ProcessMSP>
    <ProcessDLL>false</ProcessDLL>
    <ProcessINI>false</ProcessINI>
    <ProcessCFG>false</ProcessCFG>
    <ProcessTMP>true</ProcessTMP>
    <LogsDWM>1</LogsDWM>
    <PreserveDirPath>true</PreserveDirPath>
  </Table1>
</NewDataSet>

Cheers,

Steve Schofield
Microsoft MVP – IIS