- Reading Azure VM name, IP address, and hostname with PowerShell - Fri, Jul 28 2017
- Automating WSUS with PowerShell - Thu, Jul 13 2017
- Disable SSL and TLS 1.0/1.1 on IIS with PowerShell - Tue, Jun 27 2017
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 $DestI 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 YellowIn 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.
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 $RunSysPrepThis 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.
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
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.
Your XML missing “<” at the beginning!
Thanks! I corrected the text now.