Today, I will explain the script that creates the Sysprep answer file, copies the file to the new VM and executes it. At the end, I'll outline the wrapper script, which actually puts all three scripts together.

Here comes the third and final part of my series where I explain how to clone an Azure VM. In my previous posts I explained how to clone the create a copy of the VM and how to copy the data drives. We now have to sysprep with the help of an answer file which is the purpose of the PowerShell script below.

[CmdletBinding()]
Param(
  [Parameter(Mandatory=$True)]
   [string]$NewVMName,
  [Parameter(Mandatory=$True)]
   [string]$SrcHostName
   )

$dt = Get-Date -UFormat %c
Write-Host "Creating Sysprep file for Azure clone" -ForegroundColor Yellow
Write-Output "Creating Sysprep file for Azure clone, timestamp: $dt" >> $file

$attfile = 'C:\Scripts\AzureClone\2.0\skiprearm_Rename_Only - Copy.xml'
Copy-Item -Path $attfile -Destination "C:\Scripts\AzureClone\2.0\$NewVMName.xml" -Force
$newfile = "C:\Scripts\AzureClone\2.0\$NewVMName.xml"
$xml = [xml](Get-Content $newfile)
$Node = $xml.unattend.settings.component | ? {$_.Name -eq "Microsoft-Windows-Shell-Setup"}
$Node.ComputerName = $NewVMName
$xml.Save($newfile)
$Dest = "\\" + $SrcHostName + "\c$\windows\temp"

Write-Host "Copying file to the VM and restarting" -ForegroundColor Yellow
Write-Output "Copying file to the VM and restarting, timestamp: $dt" >> $file

Copy-Item -Path $newfile -Destination $Dest
$filename = $newfile | Split-Path -Leaf
$sysprepSTR = "C:\windows\system32\sysprep\sysprep.exe /generalize /oobe /reboot /quiet /unattend:"+"c:\windows\temp\"+$filename
$SysPrepRunOnce = "reg add \\$SrcHostName\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce /v sysprep /t REG_SZ /d ""$sysprepSTR"""
Invoke-Expression $SysPrepRunOnce
Restart-Computer -ComputerName $SrcHostName -Force

Write-Host "Please login to the VM using the original VM name to start Sysprep" -ForegroundColor Yellow

Let's have a look at the script.

[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$NewVMName,
[Parameter(Mandatory=$True)]
[string]$SrcHostName
)

If you've read previous posts, you should already be familiar with these parameters. The first one states the new VM name and the second one is for the source host name.

$dt = Get-Date -UFormat %c
Write-Host "Creating Sysprep file for Azure clone" -ForegroundColor Yellow
Write-Output "Creating Sysprep file for Azure clone, timestamp: $dt" >> $file

As usual, I update the console and log file with the current operation.

$attfile = 'C:\Scripts\AzureClone\2.0\skiprearm_Rename_Only - Copy.xml'
Copy-Item -Path $attfile -Destination "C:\Scripts\AzureClone\2.0\$NewVMName.xml" –Force

Here, I'm getting the Sysprep template file (see below) and making a copy from it to the file with the same name as the newly created VM.

$newfile = "C:\Scripts\AzureClone\2.0\$NewVMName.xml"
$xml = [xml](Get-Content $newfile)
$Node = $xml.unattend.settings.component | ? {$_.Name -eq "Microsoft-Windows-Shell-Setup"}
$Node.ComputerName = $NewVMName
$xml.Save($newfile)
$Dest = "\\" + $SrcHostName + "\c$\windows\temp"

Then I store the new XML file path in the $newfile variable and put its content into the XML object using the Get-Content cmdlet.

Since the only thing that needs to be changed in that file is the computer name, I save the node where this data is stored in the $Node variable and change it to the new VM name. Then I save the XML file.

After that, I prepare the $Dest variable, which will serve as the path for copying the XML file to the new VM. There is a dirty trick here actually. As you can see, I'm using the source host name instead of the new VM name. This is because the new host is an exact copy of the source VM. It has its name and IP address while the source VM is still down. And it has to be down until Sysprep runs on the new VM. If I were to power it up, it would create a mess, since there would be two duplicate hosts on the network.

The rest of the XML allows me to accept the user license agreement silently and skip the out-of-box experience (OOBE) procedure. It also has the JoinDomain section configured, so the new host can join the domain automatically.

Note that there is a serious security issue in that section. Domain joining requires the user name and password, and they appear in plain text in the script. I suppose you can use the Windows System Image Manager to encrypt it. Anyway, I don't recommend storing files with plain-text passwords anywhere.

Write-Host "Copying file to the VM and restarting" -ForegroundColor Yellow
Write-Output "Copying file to the VM and restarting, timestamp: $dt" >> $file
Copy-Item -Path $newfile -Destination $Dest

I now update the console and log file again and copy the Sysprep XML answer file to the new VM.

$filename = $newfile | Split-Path -Leaf
$sysprepSTR = "C:\windows\system32\sysprep\sysprep.exe /generalize /oobe /reboot /quiet /unattend:"+"c:\windows\temp\"+$filename
$SysPrepRunOnce = "reg add \\$SrcHostName\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce /v sysprep /t REG_SZ /d ""$sysprepSTR"""
Invoke-Expression $SysPrepRunOnce
Restart-Computer -ComputerName $SrcHostName -Force
Write-Host "Please log in to the VM using the original VM name to start Sysprep." -ForegroundColor Yellow

In the final part of the script, I get the XML file name from the $newfile variable using the Split-Path cmdlet. Then I prepare the string that runs Sysprep on the new VM using the XML answer file I just copied there. I store this string in the $sysprepSTR variable. After that, I create the registry command string that stores the Sysprep string in the RunOnce registry key. This ensures the Sysprep process runs as soon as I log in to the VM. When this string is complete, I use the Invoke-Expression cmdlet to execute the command string. Then I restart the VM and update the console.

When I log in to the new VM, Sysprep will run immediately. After it completes, the VM has a new name and has joined the domain.

Sysprep is running after loggin in

Sysprep is running after loggin in

Now the cloning process is complete and it is now safe to power up the source VM. One more thing to discuss is the wrapper script I mentioned earlier in this series.

Subscribe to 4sysops newsletter!

[CmdletBinding()]
Param(
  [Parameter(Mandatory=$True)]
   [string]$SvcName,
  [Parameter(Mandatory=$True)]
   [string]$SourceVMName,
  [Parameter(Mandatory=$True)]
   [string]$NewVMName,
  [Parameter(Mandatory=$True)]
   [string]$Subscription,
  [Parameter(Mandatory=$False)]
   [string]$NewSvcName,
  [Parameter(Mandatory=$False)]
   [string]$InstanceSize,
  [Parameter(Mandatory=$False)]
   [string]$newSubnet,
  [Parameter(Mandatory=$True)]
   [string]$SrcHostName
)

$file = "c:\temp\Cloning_Azure_OS_VM_{0:MMddyyyy_HHmm}.log" -f (Get-Date)
new-item -path $file -type file -force

$OSClone = "C:\Scripts\AzureClone\2.0\Copy_Azure_HDD.ps1 -SvcName $SvcName -SourceVMName $SourceVMName -Subscription $Subscription -NewVMName $NewVMName -SrcHostName $SrcHostName"
Invoke-Expression $OSClone

sleep 60

$CopyData = "C:\Scripts\AzureClone\2.0\Copy_Azure_Data_HDDs.ps1 -SvcName $SvcName -SourceVMName $SourceVMName -NewVMName $NewVMName”
Invoke-Expression $CopyData

sleep 60

$RunSysPrep = "C:\Scripts\AzureClone\2.0\Run_Sysprep_On_Clone.ps1 -NewVMName $NewVMName -SrcHostName $SrcHostName"
Invoke-Expression $RunSysPrep

This script is fairly simple, so I won't take too much time explaining it. It just accepts the parameters I already explained during this series, establishes a log file and uses the Invoke-Expression cmdlet to run the three scripts one by one with a 60-second break between them.

4 Comments
  1. Michael Glashouwer 5 years ago

    Dear Alex,

    I tried your tutorial, but I’m running into a problem I really don’t know how to solve:

    In the 3rd Part, the script creates a XML file it tries to copy to the new Azure VM using the  $Dest variable: Sourcehostname. Now that is where I get the error:

    Copy-Item -Path $newfile -Destination $Dest
    Copying file to the VM and restarting
    Copy-Item : Kan het netwerkpad niet vinden
    At line:6 char:1
    + Copy-Item -Path $newfile -Destination $Dest
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [Copy-Item], IOException
    + FullyQualifiedErrorId : System.IO.IOException,Microsoft.PowerShell.Commands.CopyItemCommand

    It cant find the networkpath \\hostname.cloudapp.net\C$\Windows\Temp.  What to do? I have really no idea. Part 1 and Part 2 ran without issues. The new VM is running…

    Hopefully you can give me a good hint 🙂

    Cheers,

    Michael

    • Michael Pietroforte 5 years ago

      Looks like you have an issue with admin shares. Try to access the network path in Explorer. If this doesn’t work, please read my article about administrative shares. The article is about Windows 8, but I think it still applies. Another (better) option is to work with a “regular” network share.

  2. Josh 4 years ago

    Your XML  missing “<” at the beginning!

Leave a reply

Your email address will not be published.

*

© 4sysops 2006 - 2022

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account