Using PowerShell scripting to provision VMs from a golden image

Simple PowerShell scripting makes the process of creating and deploying any number of VMs a routine with minimal effort.

Microsoft PowerShell is a great tool that helps expedite many administrative processes. With a golden image, you can use PowerShell scripting to quickly provision as many virtual machines as you want, on demand. 

In the first part of this series, I described the process of using PowerShell to create and configure VMs on Hyper-V 3. Using those same cmdlets and a few PowerShell scripting principles, I'm going to automate the process so you can quickly create as many VMs as you need. The script is fairly simple, but I will break it down and explain the main points, then show the results of running the script. 

Before you start diving into the script, make sure you have read the first part of this series. If PowerShell scripting is something new to you, consider reading Learn Windows PowerShell 3 In A Month Of Lunches by Don Jones and Jeffery Hicks.

Parameters for flexible PowerShell scripting

The script starts with support for a few simple parameters to make it easier to enter unique information such as computer name and which role or features to install. I don't like editing a script after I know that it works, so I like to use parameters to make it flexible and work similar to a cmdlet. If you're familiar with PowerShell scripting, this simple parameterized script could be written as an Advanced Function, making it operate exactly like a cmdlet. In the example below, the "Param" keyword is where you will create the variables as parameters to pass the computer name and roles for the VM. The default role for my script is "Web-Server."

Param(

    [Parameter(Mandatory=$true)][String]$NewComputerName,

    [Parameter(Mandatory=$False)][String]$Role="Web-Server"

    #Add additional params such as memory settings

)

You can add additional parameters, such as the memory settings for the VM. The idea is that you shouldn't need to edit the script again once it is tested, so add any parameters that you want to change when you execute the script.

The script needs two credentials during the VM setup and configuration process: the local administrator created for the golden image in the first part of this series and an account for the domain that has permission to add computers. Use the Get-Credential cmdlet with the UserName parameter to specify the user accounts. When the script executes, you will be prompted for the password.

$LocalCred=Get-Credential -UserName Administrator -Message "Local computer credentials"

$DomainCred=Get-Credential -USerName Company\Administrator -Message "Domain credentials"

The password is stored securely in $LocalCred and $DomainCred. They will be passed securely later in the script along with the user name. The best part about the Get-Credential cmdlet is that you never need to put confidential accounts and passwords directly into your script.

Creating and starting the new VM

The process of creating the VM and setting the memory is the same as I explained in the first part of this series, however I added the parameter variable $NewComputerName to specify the name of the computer and the name of the new virtual hard disk. In the second line, you will notice the syntax that creates the disk with a name similar to "ComputerName_Disk.vhdx".

New-VHD -ParentPath C:\hyperv\GoldImageCore.vhdx

-Path"c:\hyperv\$($NewComputerName)_Disk.vhdx" -Differencing | Out-Null

 

New-VM -Name $NewComputerName -VHDPath "c:\hyperv\$($NewComputerName)_Disk.vhdx"

-SwitchName (Get-VMSwitch -name inside* | Select-Object -ExpandProperty name) | Out-Null

 

Set-VMMemory -VMName $NewComputerName -DynamicMemoryEnabled $True -StartupBytes 1GB

-MinimumBytes 512mb -MaximumBytes 2GB | Out-Null

As I mentioned earlier, you could easily replace the memory settings with parameters to add more flexibility to the configuration of the virtual machine.

Starting the VM is fairly simple -- just use the Start-VM cmdlet. The problem is that the script needs to pause until the operating system has booted and obtained an IP address from the DHCP. In this script, I used a While loop and checked the VM for a "running" status.

Start-VM -Name $NewComputerName

 

$VMState="Stopped"

While($VmState -ne "Running"){

    $VMState=Get-VM -Name $NewComputerName | Select-Object -ExpandProperty State

    Write-Verbose "Waiting to start...(30s)"

    Start-sleep -Seconds 30

    }

After the VM and the operating system have launched, it's time to join the computer to the domain.

Before you can join the VM to the domain, you need the current IP address of the VM as in the example below. Using the IP address, you can use the Invoke-Command cmdlet to run the Add-Computer cmdlet to join the domain.

$ip=(Get-VMNetworkAdapter -VMName $NewComputerName |

Select -ExpandProperty ipaddresses)[0]

 

Write-Verbose "VM $NEwComputerName Join the Domain"

Invoke-Command -ComputerName $ip -Credential $LocalCred {

Add-Computer -NewName $Using:NewComputerName -DomainName company.loc

-Credential $Using:DomainCred}

Did you notice the strange syntax $Using:NewComputerName? This is a new language feature in PowerShell v3 that passes the variable created in the script to the remote computer.

Restarting and adding roles

The Add-Computer cmdlet has a parameter that will restart the computer after it has joined to the domain. I don't like using that parameter because I have no way of pausing my script for the exact time it takes for the VM to reboot without adding a lot of code. I prefer to use the new Restart-Computer cmdlet with the Wait parameter. This pauses the script until the VM is rebooted and responding on the network.

Restart-Computer -ComputerName $ip -Protocol WSMAN -Credential $LocalCred -wait -force

I specified the Protocol parameter in the above example to use the PowerShell Remoting WS-MAN protocol. This is the same protocol that Invoke-Command uses and is permitted through the default firewall on the VM. If you don't specify the WS-MAN protocol, the default protocol is DCOM, which is not normally open on the firewall.

Usually I don't install roles with this script, but the example below shows how easy it would be to start building a software deployment process. This script will install the Web-server role using the Install-WindowsFeature cmdlet.

Invoke-Command -ComputerName $NewComputerName -Credential $DomainCred {

Install-WindowsFeature $Using:Role | out-null}

The complete script

In the final script at the end of this article, I added Write-Verbose comments to the main sections so that you can tell what's happening when the script runs. In the image below, I launched the script to create a new VM called Test1. I added the Verbose to display the comments.

PowerShell scripting

Every time you need another VM, just run the script again!

Below is the complete script, along with comments using the Write-Verbose cmdlet. Try it out and let me know what you think. 

 

Param(

    [Parameter(Mandatory=$true)][String]$NewComputerName,

    [Parameter(Mandatory=$False)][String]$Role="Web-Server"

    #Add additional params such as memory settings

)

 

Write-Verbose "Get the credentials for the local computer and the domain"

$LocalCred=Get-Credential -UserName Administrator -Message "Local computer credentials"

$DomainCred=Get-Credential -USerName Company\Administrator -Message "Domain credentials"

 

Write-Verbose "Creating and configuring VM $NewComputerName"

New-VHD -ParentPath C:\hyperv\GoldImageCore.vhdx

-Path"c:\hyperv\$($NewComputerName)_Disk.vhdx" -Differencing | Out-Null

 

New-VM -Name $NewComputerName -VHDPath "c:\hyperv\$($NewComputerName)_Disk.vhdx"

-SwitchName (Get-VMSwitch -name inside* | Select-Object -ExpandProperty name) | Out-Null

 

Set-VMMemory -VMName $NewComputerName -DynamicMemoryEnabled $True -StartupBytes 1GB

-MinimumBytes 512mb -MaximumBytes 2GB | Out-Null

 

 

Write-Verbose "Starting VM $NewComputerName"

Start-VM -Name $NewComputerName

 

$VMState="Stopped"

While($VmState -ne "Running"){

    $VMState=Get-VM -Name $NewComputerName | Select-Object -ExpandProperty State

    Write-Verbose "Waiting to start...(30s)"

    Start-sleep -Seconds 30

    }

 

Write-Verbose "Getting DHCP assigned IP address"

$ip=(Get-VMNetworkAdapter -VMName $NewComputerName |

Select -ExpandProperty ipaddresses)[0]

 

Write-Verbose "VM $NEwComputerName IPAddress:$IP"

 

 

 

Write-Verbose "VM $NEwComputerName Join the Domain"

Invoke-Command -ComputerName $ip -Credential $LocalCred {

Add-Computer -NewName $Using:NewComputerName -DomainName company.loc

-Credential $Using:DomainCred}

 

Write-Verbose "Restarting $NewComputerName at IP: $ip"

Restart-Computer -ComputerName $ip -Protocol WSMAN -Credential $LocalCred -wait -force

 

Write-Verbose "Install a the new role $Role"

Invoke-Command -ComputerName $NewComputerName -Credential $DomainCred {

Install-WindowsFeature $Using:Role | out-null}

 

Write-Verbose "Complete"

This was first published in February 2013

Dig deeper on Virtual machine provisioning and configuration

Pro+

Features

Enjoy the benefits of Pro+ membership, learn more and join.

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

-ADS BY GOOGLE

SearchVMware

SearchWindowsServer

SearchCloudComputing

SearchVirtualDesktop

SearchDataCenter

Close