Hello,
I am attempting to deploy multiple VMs at the same time using powercli. I believe I need to had the -RunAsynch switch, howerver this does not appear to be working, instead the VMs are deploying one at a time. Here is my code:
Connect-VIServer -Server vc1.MYDOMAIN.local -User MYDOMAIN\MYACCOUNT -Password MYPASSWORD
New-vm -vmhost prodh1.MYDOMAIN.local -Name TEST-SVR01 -Template W2K8R2SP1 -Datastore IOMEGA -OSCustomizationspec W2K8R2SP1 -Location _Tobedeleted| Start-VM -RunAsync
New-vm -vmhost prodh1.MYDOMAIN.local -Name TEST-SVR02 -Template W2K8R2SP1 -Datastore IOMEGA -OSCustomizationspec W2K8R2SP1 -Location _Tobedeleted| Start-VM -RunAsync
New-vm -vmhost prodh1.MYDOMAIN.local -Name TEST-SVR03 -Template W2K8R2SP1 -Datastore IOMEGA -OSCustomizationspec W2K8R2SP1 -Location _Tobedeleted| Start-VM -RunAsync
New-vm -vmhost prodh1.MYDOMAIN.local -Name TEST-SVR04 -Template W2K8R2SP1 -Datastore IOMEGA -OSCustomizationspec W2K8R2SP1 -Location _Tobedeleted| Start-VM -RunAsync
Start-Sleep -Seconds 300
get-vm "TEST-SVR01" | Get-VMGuestNetworkInterface -Guestuser Administrator -GuestPassword "MYPASSWORD" |?{$_.name -eq "Local Area Connection 3"} | set-vmguestnetworkinterface -Guestuser Administrator -GuestPassword "MYPASSWORD" -IPPolicy static -IP 192.168.1.25 -Netmask 255.255.255.0 -Gateway 192.168.1.1 -DNS 192.168.1.2,192.168.1.3 -RunAsync
get-vm "TEST-SVR02" | Get-VMGuestNetworkInterface -Guestuser Administrator -GuestPassword "MYPASSWORD" |?{$_.name -eq "Local Area Connection 3"} | set-vmguestnetworkinterface -Guestuser Administrator -GuestPassword "MYPASSWORD" -IPPolicy static -IP 192.168.1.25 -Netmask 255.255.255.0 -Gateway 192.168.1.1 -DNS 192.168.1.2,192.168.1.3 -RunAsync
get-vm "TEST-SVR03" | Get-VMGuestNetworkInterface -Guestuser Administrator -GuestPassword "MYPASSWORD" |?{$_.name -eq "Local Area Connection 3"} | set-vmguestnetworkinterface -Guestuser Administrator -GuestPassword "MYPASSWORD" -IPPolicy static -IP 192.168.1.25 -Netmask 255.255.255.0 -Gateway 192.168.1.1 -DNS 192.168.1.2,192.168.1.3 -RunAsync
get-vm "TEST-SVR04" | Get-VMGuestNetworkInterface -Guestuser Administrator -GuestPassword "MYPASSWORD" |?{$_.name -eq "Local Area Connection 3"} | set-vmguestnetworkinterface -Guestuser Administrator -GuestPassword "MYPASSWORD" -IPPolicy static -IP 192.168.1.25 -Netmask 255.255.255.0 -Gateway 192.168.1.1 -DNS 192.168.1.2,192.168.1.3 -RunAsync
Get-NetworkAdapter "TEST-SVR01" | Set-NetworkAdapter -NetworkName VM1 -Confirm:$false
Get-NetworkAdapter "TEST-SVR02" | Set-NetworkAdapter -NetworkName VM1 -Confirm:$false
Get-NetworkAdapter "TEST-SVR03" | Set-NetworkAdapter -NetworkName VM1 -Confirm:$false
Get-NetworkAdapter "TEST-SVR04" | Set-NetworkAdapter -NetworkName VM1 -Confirm:$false
Can anyone assist?
Thanks,
Duncan.
Yes, you are right.
By specifying the -VM (Get-VM $modelVM) param you create the new VM from an existing one.
Just change the params of the New-VM cmdlet according to your needs and leave the -VM away.
This one creates your VMs based on a template and a customization specification.
$esxName = "prodh1.MYDOMAIN.local"
$template = "W2K8R2SP1"
$datastore = "IOMEGA"
$newVmList = "TEST-SRV01", "TEST-SRV02", "TEST-SRV03", "TEST-SRV04"
$custSpec = "W2K8R2SP1"
$location = "_Tobedeleted"
$taskTab = @{}
# Create all the VMs specified in $newVmList
foreach($Name in $newVmList) {
$taskTab[(New-VM -Name $Name -VMHost (Get-VMHost -Name $esxName) -Template $template -Datastore $datastore -OSCustomizationSpec $custSpec -Location $location -RunAsync).Id] = $Name
}
Of course, you can write it like before. Then you have to change only the variable $newVmList in the original script.
foreach($Name in $newVmList) {
$taskTab[(New-VM -Name $Name -VMHost "prodh1.MYDOMAIN.local" -Template" W2K8R2SP1" -Datastore" IOMEGA" -OSCustomizationSpec "W2K8R2SP1" -Location "_Tobedeleted" -RunAsync).Id] = $Name
}
You also need to insert the remaining part with the while loop and your network customizations!
http://www.lucd.info/2010/02/21/about-async-tasks-the-get-task-cmdlet-and-a-hash-table/
Regards
Emanuel
Hi Duncan,
You applied the -RunAsync param to the Start-VM cmdlet. but the script is waiting for New-VM to finish. Because of this, only one deploy at a time.
Of course you can apply the -RunAsync param to the New-VM cmdlet instead, but the downside of running one or more tasks as a Job, is that you have to keep an eye on the progress.
Simple said, you can't start the VMs or apply any customization to them before the deploy is finished.
LucD has published a great script for this purpose.
About Async tasks, the Get-Task cmdlet and a hash table | LucD notes
Regards,
Emanuel
Hi Eco91,
The script works great, but I'm brand new to scripting and I'm really struggling to understand a couple of things. I want to deploy VMs from template and apply a customization to them, but this script clones from an existing VM instead. I would imagine it's fairly simple to change this, but I don't even understand where it's getting the command to clone vs deploy, I'm assuming it's this line here:
# Create all the VMs specified in $newVmList |
07 | foreach ( $Name in $newVmList ){ |
08 | $taskTab [(New -VM -VM (Get -VM $modelVm ) -Name $Name -VMHost (Get -VMHost -Name $esxName ) -RunAsync ).Id] = $Name |
09 | } |
Is there a simple way to change this task from clone to deploy from template + apply OS customization?
Thanks!
Yes, you are right.
By specifying the -VM (Get-VM $modelVM) param you create the new VM from an existing one.
Just change the params of the New-VM cmdlet according to your needs and leave the -VM away.
This one creates your VMs based on a template and a customization specification.
$esxName = "prodh1.MYDOMAIN.local"
$template = "W2K8R2SP1"
$datastore = "IOMEGA"
$newVmList = "TEST-SRV01", "TEST-SRV02", "TEST-SRV03", "TEST-SRV04"
$custSpec = "W2K8R2SP1"
$location = "_Tobedeleted"
$taskTab = @{}
# Create all the VMs specified in $newVmList
foreach($Name in $newVmList) {
$taskTab[(New-VM -Name $Name -VMHost (Get-VMHost -Name $esxName) -Template $template -Datastore $datastore -OSCustomizationSpec $custSpec -Location $location -RunAsync).Id] = $Name
}
Of course, you can write it like before. Then you have to change only the variable $newVmList in the original script.
foreach($Name in $newVmList) {
$taskTab[(New-VM -Name $Name -VMHost "prodh1.MYDOMAIN.local" -Template" W2K8R2SP1" -Datastore" IOMEGA" -OSCustomizationSpec "W2K8R2SP1" -Location "_Tobedeleted" -RunAsync).Id] = $Name
}
You also need to insert the remaining part with the while loop and your network customizations!
http://www.lucd.info/2010/02/21/about-async-tasks-the-get-task-cmdlet-and-a-hash-table/
Regards
Emanuel
Thanks so much! Works great.
Sorry, one last question, could you point me in the right direction in regards to adding the network customizations using the while loop? It needs to wait 3 minutes after the VM has been deployed in order to allow enough time for the OS customization to run.
Paste your code starting at
Start-Sleep -Seconds 300
after the whole while loop. Should look like this
# Create Vms
foreach($Name in $newVmList) {
...
}
# Start each VM that is completed
$runningTasks = $taskTab.Count
while($runningTasks -gt 0) {
...
}
# START HERE
# Wait for OS Customization
Start-Sleep -Seconds 300
# Customize network
get-vm "TEST-SRV01" | Get-VMGuestNetworkInterface -Guestuser Administrator -GuestPassword "MYPASSWORD" | ? ...............
Regards
Emanuel
Thanks so much.
I'm very close, the VMs are deploying, but the network customizations are all failing. Here is the code:
$esxName = "ESXHOST1.DOMAIN.local"
$template = "W2K8R2SP1"
$datastore = "IOMEGA"
$newVmList = "SRV01", "SRV02", "SRV03", "SRV04"
$custSpec = "W2K8R2SP1"
$location = "_Tobedeleted"
$taskTab = @{}
Connect-VIServer -Server vc1.DOMAIN.local -User DOMAIN\ADMINISTRATOR -Password MYPASSWORD
# Create all the VMs specified in $newVmList
foreach($Name in $newVmList) {
$taskTab[(New-VM -Name $Name -VMHost (Get-VMHost -Name $esxName) -Template $template -Datastore $datastore -OSCustomizationSpec $custSpec -Location $location -RunAsync).Id] = $Name
}
# Start each VM that is completed
$runningTasks = $taskTab.Count
while($runningTasks -gt 0){
Get-Task | % {
if($taskTab.ContainsKey($_.Id) -and $_.State -eq "Success"){
Get-VM $taskTab[$_.Id] | Start-VM
$taskTab.Remove($_.Id)
$runningTasks--
}
elseif($taskTab.ContainsKey($_.Id) -and $_.State -eq "Error"){
$taskTab.Remove($_.Id)
$runningTasks--
}
}
Start-Sleep -Seconds 15
}
# START HERE
# Wait for OS Customization
Start-Sleep -Seconds 300
# Customize network
Get-NetworkAdapter -Name $Name | Set-NetworkAdapter -NetworkName VM2 -Confirm:$false] = $Name
get-vm -Name $Name | Get-VMGuestNetworkInterface -Guestuser Administrator -GuestPassword "MYPASSWORD" |?{$_.name -eq "Local Area Connection 3"} | set-vmguestnetworkinterface -Guestuser Administrator -GuestPassword "MYPASSWORD" -IPPolicy static -IP 192.168.1.155 -Netmask 255.255.255.0 -Gateway 192.168.1.1 -DNS 192.168.1.2,192.168.1.3]
$Name is defined only within the foreach. You can't use it outside.
To customize the network, you have two possibilities.
-- The simple one
Enter for every VM the appropriate command into the script (Set-NetworkAdapter and Set-VMGuestNetworkInterface) => Just copy/paste and adjust the ip addresses.
This will get very unhandy with increasing VM count.
-- The complex one
Adjust the $newVmList to an array, with a hash table for each vm containing the network data, and loop through (same as you did before).
With this method, you also could provide the VM data from a csv file or an other source.
But you should get used with hash tables, otherwise it could be confusing.
Windows PowerShell Tip: Working with Hash Tables
$newVmList = @(
@{"Name" = "SRV01"; "IP" = "192.168.1.100"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; },
@{"Name" = "SRV02"; "IP" = "192.168.1.101"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; },
@{"Name" = "SRV03"; "IP" = "192.168.1.102"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; },
@{"Name" = "SRV04"; "IP" = "192.168.1.103"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; }
)
Then you have to make a small change to the New-VM foreach. Should then look like this
foreach($VM in $newVmList) {
$taskTab[(New-VM -Name $VM.Name -VMHost (Get-VMHost -Name $esxName) -Template $template -Datastore $datastore -OSCustomizationSpec $custSpec -Location $location -RunAsync).Id] = $VM.Name
}
After the Start-Sleep -Seconds 300, you could loop through the $newVmList again to customize the network for each VM.
foreach($VM in $newVmList) {
Get-NetworkAdapter -VM $VM.Name | Set-NetworkAdapter -NetworkName $VM.NetworkName -Confirm:$false
Get-VM -Name $VM.Name | Get-VMGuestNetworkInterface -Guestuser "Administrator" -GuestPassword "MYPASSWORD" | ? { $_.name -eq "Local Area Connection 3" } | Set-VMGuestNetworkInterface -Guestuser "Administrator" -GuestPassword "MYPASSWORD" -IPPolicy static -IP $VM.IP -Netmask $VM.Netmask -Gateway $VM.Gateway -DNS $VM.DNS
}
Regards
Emanuel
That's got it. Just in case anyone else is trying to do the same thing here's the final code:
$esxName = "prodh1.MYDOMAIN.local"
$template = "W2K8R2SP1"
$datastore = "IOMEGA"
$newVmList = @(
@{"Name" = "SRV01"; "IP" = "192.168.1.100"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; },
@{"Name" = "SRV02"; "IP" = "192.168.1.101"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; },
@{"Name" = "SRV03"; "IP" = "192.168.1.102"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; },
@{"Name" = "SRV04"; "IP" = "192.168.1.103"; "Netmask" = "255.255.255.0"; "Gateway" = "192.168.1.1"; "DNS" = @("192.168.1.2", "192.168.1.3"); "NetworkName" = "VM2"; }
)
$custSpec = "W2K8R2SP1"
$location = "_Tobedeleted"
$taskTab = @{}
Connect-VIServer -Server VIRTUALCENTERSERVER.MYDOMAIN.local -User MYDOMAIN\MYUSER -Password MYPASSWORD
# Create all the VMs specified in $newVmList
foreach($VM in $newVmList) {
$taskTab[(New-VM -Name $VM.Name -VMHost (Get-VMHost -Name $esxName) -Template $template -Datastore $datastore -OSCustomizationSpec $custSpec -Location $location -RunAsync).Id]=$VM.Name
}
# Start each VM that is completed
$runningTasks = $taskTab.Count
while($runningTasks -gt 0){
Get-Task | % {
if($taskTab.ContainsKey($_.Id) -and $_.State -eq "Success"){
Get-VM $taskTab[$_.Id] | Start-VM
$taskTab.Remove($_.Id)
$runningTasks--
}
elseif($taskTab.ContainsKey($_.Id) -and $_.State -eq "Error"){
$taskTab.Remove($_.Id)
$runningTasks--
}
}
Start-Sleep -Seconds 15
}
# START HERE
# Wait for OS Customization
Start-Sleep -Seconds 1020
# Customize network
foreach($VM in $newVmList) {
Get-NetworkAdapter -VM $VM.Name | Set-NetworkAdapter -NetworkName $VM.NetworkName -Confirm:$false
Get-VM -Name $VM.Name | Get-VMGuestNetworkInterface -Guestuser "Administrator" -GuestPassword "MYPASSWORD" | ? { $_.name -eq "Local Area Connection 3" } | Set-VMGuestNetworkInterface -Guestuser "Administrator" -GuestPassword "MYPASSWORD" -IPPolicy static -IP $VM.IP -Netmask $VM.Netmask -Gateway $VM.Gateway -DNS $VM.DNS
}
One change I had to make was to crank the sleep time way up in order to allow the OS Customization to run.
Thanks so much for all your help Emanuel!!
Hi,
I know this is too late to start this again, but I just started working on an assignment and I've to deploy 300 VMs and I am worst at scripting.
Here's my case:
I have got an ESXi server, say "TEST1", in which I've created a windows10 master image say "MASTER-W10" and I need to create 300 images of this, with customization of hostname, IP configs etc.
from the code below,
I've the esx name, datastore (which I am assuming "storage" that I see under the resources when I go the summary tab). what would be the template name?
is it gonna be the same as name of Master virtual machine i.e. "MASTER-W10" ??
Also, what does the $custSpec and $location mean here?
FYI, I am accessing ESXi directly through vSphere Client (not vCenter).