VMWare: Creazione Massiva di Virtual Machine e Redistribuzione su Datastore
L’implementazione di più macchine virtuali (VMs) VMware, da template, può semplificare la gestione della virtualizzazione e far risparmiare tempo. Come implementare in modo efficace più VMs da template?
Un metodo consiste nell’utilizzare utilizzare i modelli in vSphere personalizzando il sistema operativo guest durante la distribuzione di nuove macchine virtuali dal template, ma si tratta comunque di un’operazione lunga e noiosa, oppure un altro metodo consiste nel creare uno script PowerCLI (Powershell) che automatizzi completamente tale operazione.
Lo script PowerCLI, indicato in seguito, utilizzando un file CSV contenente i parametri specifici permette l’implementazione massiva di Virtual Machines assicurando che il deploy avvenga:
- su datastore specifici (uno o più) escludendone (eventualmente) altri, assicurando che sia mantenuto lo spazio residuo “vitale” sul datastore stesso.
- installazione/aggiornamento dei vmware tools
- assegnazione della scheda di rete specifica
- assegnazione della vlan specifica
- assegnazione del nome host (Sistema Operativo),
- join a dominio
File: BulkVMFromTemplate.csv (dati di esempio)
"vmname","domain","vlan","vmlocation","template","datacenter","cluster" VM-TEST01,company.loc,VLAN 551 Company-SmartWorking,TEST-DIR,WIN10-CLEAN,DATACENTER,TESTCLUSTER VM-TEST02,company.loc,VLAN 551 Company-SmartWorking,TEST-DIR,WIN10-CLEAN,DATACENTER,TESTCLUSTER VM-TEST03,company.loc,VLAN 551 Company-SmartWorking,TEST-DIR,WIN10-CLEAN,DATACENTER,TESTCLUSTER VM-TEST04,company.loc,VLAN 551 Company-SmartWorking,TEST-DIR,WIN10-CLEAN,DATACENTER,TESTCLUSTER
Script: BulkVMFromTemplate.ps1
#----------------------------------------------------------------------------
# - File Name : BulkVMFromTemplate.ps1
# - Author : Nicola Montemurro
# - Administrator : Nicola Montemurro -
# - Create : 07/02/2022
# - Last Update : 18/02/2022
# - Description : Crea e Ditribuisce VM in modo automatizzato
# - Position : C:\vmtools\ps
# - Note : NON modificare senza AUTORIZZAZIONE dell'AMMINISTRATORE
#----------------------------------------------------------------------------
#### USER DEFINED VARIABLES #################################################
## Versione 1.0.0
$viUser = "svcdeployvm@vsphere.local"
$viPassword = "***Deploy1.***"
$DEFvmname = "RAND-" # VMNAME - if empty in csv will be randomized by function
$DEFmsdomain = "MSDomain" # AD Domain to join - if empty in csv this will be used
$DEFvlan = "VLAN 152" # vlan (portgroup) to use for VM - if empty in csv this will be used
$DEFvmlocation = "vmlocation" # vCenter location (dir) to deploy VM - if empty in csv this will be used
$DEFvmtemplate = "WIN10CLEAN" # vCenter template to deploy VM - if empty in csv this will be used
$DEFCluster = "CLUSTER1" # vCenter cluster to deploy VM - if empty in csv this will be used
$filename = "C:\vcc_tools\Scripts\DeployVM\BulkVMFromTemplate.csv" # Name and Location of file .csv
#Account locale del Template - deve essere previsto
$GuestUser = "vmware"
$GuestUserPassword = "VMPassword"
#Active Directory Account - deve essere censito in ogni dominio per la join con diritti di Account Operators
$DomainUser = "domainuser"
$DomainPassword = "s^&tK8Ih^XZ@t=K?y5w}jztq"
############# DATASTORE
### GB
$RetSpace = 1024 # Datastore space excluded from provisionable space in GB
$dsexcluded = @("DELL002_LUN_12") # Datastore excluded by provisioning es. --> $dsexcluded = @("DELL002_LUN_12", "DELL002_LUN_04")
### LINKS
# https://developer.vmware.com/home
# https://docs.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-arrays?view=powershell-7.2#arrays-of-objects
# https://vdc-repo.vmware.com/vmwb-repository/dcr-public/cdbbd51c-4824-4a1b-ad43-45df55a76a76/8cb3ed93-cac2-46aa-b329-db5a096af5bc/doc/GUID-60396B03-9589-4E40-8D36-383638653C5C.html
[Array]$VMList = @()
[array]$NetAdapterType = @('Unknown', 'e1000', 'e1000e', 'Flexible', 'Vmxnet', 'EnhancedVmxnet', 'Vmxnet3')
$header = @("vmname", "domain", "vlan", "vmlocation", "template", "datacenter", "cluster")
Import-Csv $fileName -Header $header -Delimiter "`," | select -skip 1 | Foreach-Object {
foreach ($row in $fileName) {
# UNSET Variables
$vmname = $null
$domain = $null
write-host "primo dominio"
$domain
$vlan = $null
$vmlocation = $null
$template = $null
$datacenter = $null
$cluster = $null
# ASSIGN Values to Variables
$vmname = "$($_.vmname)"
$domain = "$($_.domain)"
write-host "secondo dominio"
$domain
$vlan = "$($_.vlan)"
$vmlocation = "$($_.vmlocation)"
$template = "$($_.template)"
$datacenter = "$($_.datacenter)"
$cluster = "$($_.cluster)"
if (!$vmname) {
$objRand = new-object random
$vmname = $DEFvmname + $objRand.next(100,9999)
}
elseif (!$vlan) {$vlan = $DEFvlan}
elseif (!$vmlocation) {$vlan = $DEFvmlocation}
elseif (!$template) {$template = $DEFtemplate}
elseif (!$datacenter) {$datacenter = $DEFdatacenter}
elseif (!$cluster) {$cluster = $DEFcluster}
# The Name my be exists in the list
if ( -not ($VMList -match $vmname )) {
$VMList += @([pscustomobject]@{
vmname=$vmname
domain=$domain
vlan=$vlan
vmlocation=$vmlocation
template=$template
cluster=$cluster
}
)
}
}
}
function GetDatastoreSpace {
[Array]$DSList = @()
$datastores = Get-Datastore -ErrorAction SilentlyContinue
Foreach ($datastore in $datastores) {
if ( -not ($datastore -in $dsexcluded) -and (-not ($datastore.name -match "DSMIL1" )) ) {
## Provisionable Space
$dspspace = [Math]::Round($datastore.FreeSpaceGB - $RetSpace, 2)
$DSList += @([pscustomobject]@{
datastore=$datastore
name = $datastore.Name
dspspace=$dspspace
}
)
}
}
Return [array]$DSList
}
function GetMostCapabilityDatastore() {
[array]$DSList = @(GetDatastoreSpace | where {$_})
$max = 0
$DSList | Foreach-Object {
if ($max -le $_.dspspace){
$max = $_.dspspace
$RetVal = $_
}
}
Return $RetVal
}
function WaitForTools {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
function RefreshExtensionData {
Return $VMExtensionData = Get-VM -Name $VM | Get-VMToolsStatus
}
RefreshExtensionData
$PowerStatus = Get-VM -Name $VM
if ( $PowerStatus -eq "PoweredOn") {
if ($VMExtensionData.ToolsStatus -eq "toolsNotRunning") {
do {
# Wait 5 seconds
$Wait = 5
Start-Sleep -s $Wait
# Check the Max Time to sleep
$MaxTimeToSleep = $MaxTimeToSleep + $wait
#Write-Host "$wait, $MaxTimeToSleep"
RefreshExtensionData
Write-Host "Waiting for starting VMWare Tools on $VM" -ForegroundColor Yellow -BackgroundColor Black
}
until( ($VMExtensionData.ToolsStatus -eq "toolsOk") -or ( $MaxTimeToSleep -ge 60) ) {
}
}
}
}
function Get-VMToolsStatus {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM,
$VMObj
)
$VMObj = $VM.ExtensionData.Guest
Return $VMObj
}
function SetVideoCard {
param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM,
[Parameter(Mandatory=$false)][bool]$Autodetect=$true,
[Parameter(Mandatory=$false)][int32]$MemoryMB
)
$spec = New-Object VMware.Vim.VirtualMachineConfigSpec
$Config = New-Object VMware.Vim.VirtualDeviceConfigSpec
Get-VM -name $VM | Foreach { $VideoAdapter = $_.ExtensionData.Config.Hardware.Device | Where {$_.GetType().Name -eq "VirtualMachineVideoCard"}
$Config.device = $VideoAdapter
if ( $Config.device.useAutoDetect -ne $Autodetect ) {
if ($MemoryMB) {
$Config.device.videoRamSizeInKB = $MemoryMB * 1KB
}
if ($AutoDetect) {
$Config.device.useAutoDetect = $true
}
else
{
$Config.device.useAutoDetect = $false
}
$Config.operation = "edit"
$spec.deviceChange += $Config
$VMView = $_ | Get-View
Write-Host "Setting Video Display for $VM"
$VMView.ReconfigVM($spec)
}
}
}
function SetNetworkCardType {
[CmdletBinding()]
Param( [Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM,
[Parameter(Mandatory=$false)][VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.VirtualNetworkAdapterType]$NetAdapterType='Vmxnet3',
[Parameter(Mandatory=$true)][String] $vlan,
[Parameter(Mandatory=$false)]$WOL=$false #WakeOnLan
)
# Unknown, e1000, e1000e, Flexible, Vmxnet, EnhancedVmxnet, Vmxnet3, SriovEthernetCard, Vmxnet3Vrdma
if ( ($VM | Get-NetworkAdapter -Name "Network adapter 1" | where { $_.Type -ne $NetAdapterType }) ) {
$VM | Get-NetworkAdapter -Name "Network adapter 1" | Set-NetworkAdapter -Type $NetAdapterType -NetworkName $vlan -StartConnected $True -Confirm:$false
Write-Host "$VM - Setting Network Adapter Type to $NetAdapterType"
}
}
function StartAndGo {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
$PowerStatus = Get-VM -Name $VM
if ($PowerStatus.PowerState -eq "PoweredOff") {
Write-Host "Start $VM and GO"
[bool]$RetBool = Start-VM $VM -Confirm:$false
}
Return $RetBool
}
function StartAndWait {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
function RefreshExtensionData {
$RetExtensionData = Get-VM -Name $VM | Get-VMToolsStatus
Return $RetExtensionData
}
$VMExtensionData = RefreshExtensionData
$PowerStatus = Get-VM -Name $VM
if ($PowerStatus.PowerState -eq "PoweredOff") {
Write-Host "Powering On $VM"
Start-VM $VM -Confirm:$false
Write-Host "Please wait while $VM is started" -ForegroundColor Yellow -BackgroundColor Black
do {
# Wait 5 seconds
$Wait = 5
Start-Sleep -s $Wait
# Check the Max Time to sleep
$MaxTimeToSleep = $MaxTimeToSleep + $wait
#Write-Host "$wait, $MaxTimeToSleep"
$VMExtensionData = RefreshExtensionData
Write-Host "Please wait while $VM is started" -ForegroundColor Yellow -BackgroundColor Black
}
until ( ($VMExtensionData.ToolsStatus -eq "toolsOk") -or ($MaxTimeToSleep -ge 120) ) {
}
}
}
function ShutdownVM {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
function RefreshPowerStatus {
$RetPowerStatus = Get-VM -Name $VM
Return $RetPowerStatus
}
$PowerStatus = RefreshPowerStatus
if ($PowerStatus.PowerState -eq "PoweredOn") {
Write-Host "Powering Off $VM"
Shutdown-VMGuest $VM -Confirm:$false
Write-Host "Please wait while $VM is stopped" -ForegroundColor Yellow -BackgroundColor Black
do {
# Wait 5 seconds
$Wait = 5
Start-Sleep -s $Wait
# Check the Max Time to sleep
$MaxTimeToSleep = $MaxTimeToSleep + $wait
#Write-Host "$wait, $MaxTimeToSleep"
# Check the power status
$PowerStatus = RefreshPowerStatus
## $VM.PowerState
Write-Host "Please wait while $VM is stopped" -ForegroundColor Yellow -BackgroundColor Black
}
until (($PowerStatus.PowerState -eq "PoweredOff") -or ($MaxTimeToSleep -ge 120)) {
}
}
}
function RestartVM {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
function RefreshExtensionData {
$RetExtensionDate = Get-VM -Name $VM | Get-VMToolsStatus
Return $RetExtensionDate
}
$VMExtensionData = RefreshExtensionData
$PowerStatus = Get-VM -Name $VM
if ($PowerStatus.PowerState -eq "PoweredOn") {
Write-Host "Restarting $VM"
Restart-VMGuest $VM -Confirm:$false
Write-Host "Please wait while $VM is restarted" -ForegroundColor Yellow -BackgroundColor Black
do {
# Wait 5 seconds
$Wait = 5
Start-Sleep -s $Wait
# Check the Max Time to sleep
$MaxTimeToSleep = $MaxTimeToSleep + $wait
#Write-Host "$wait, $MaxTimeToSleep"
$VMExtensionData = RefreshExtensionData
Write-Host "Please wait while $VM is restarted" -ForegroundColor Yellow -BackgroundColor Black
}
until ( ($PowerStatus.PowerState -eq "PoweredOn") -and (($VMExtensionData.ToolsStatus -eq "toolsOk") -or ($MaxTimeToSleep -ge 120)) ) {
}
}
}
## https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere-lifecycle-manager.doc/GUID-12649CBB-F69A-4199-8957-B4C0170B4E9A.html
function GetVMToolsInstalled {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
$RetBool = $true
$PowerStatus = Get-VM -Name $VM
$VMExtensionData = Get-VM -Name $VM | Get-VMToolsStatus
if ( $PowerStatus.PowerState -eq "PoweredOn") {
### Se non sono installati o sono obsoleti False
if (($VMExtensionData.ToolsStatus -eq "toolsNotInstalled") -or ($VMExtensionData.ToolsVersionStatus -ne "guestToolsCurrent")) {
$RetBool = $False
}
}
Return $RetBool
}
function ToolsUpgrade {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
Update-Tools -VM $VM -NoReboot | Wait-Tools
}
#https://docs.vmware.com/en/VMware-Tools/11.3.0/com.vmware.vsphere.vmwaretools.doc/GUID-E45C572D-6448-410F-BFA2-F729F2CDA8AC.html
function ToolsInstall {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM
)
Mount-Tools -VM $VM
Invoke-VMScript -VM $VM -ScriptText {'D:\setup64.exe /s /v" /qb addlocal=all remove=hgfs,VSS,AppDefense,NetworkIntrospection"'} -ScriptType Powershell -GuestUser $GuestUser -GuestPassword $GuestUserPassword
Dismount-Tools -VM $VM
}
function SetVlan {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM,
[Parameter(Mandatory=$true)]$vlan
)
if ( Get-VM | where { ($_ | Get-NetworkAdapter | where ({($_.Name -eq "Network adapter 1") -and ($_.NetworkName -ne $vlan) })) } ) {
Get-NetworkAdapter -VM $VM | where {$_.Name -eq "Network adapter 1"} | Set-NetworkAdapter -NetworkName $vlan -WakeOnLan $False -Confirm:$false
}
}
## https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/rename-computer?view=powershell-7.2
function SetComputerName {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM,
[Parameter(Mandatory=$true)][String]$ComputerName
)
$ScriptText = "Rename-Computer -NewName $($ComputerName)"
$ScriptObj = Invoke-VMScript -VM $VM -ScriptText $ScriptText -ScriptType Powershell -GuestUser $GuestUser -GuestPassword $GuestUserPassword
$ScriptObj.ScriptOutput
$ScriptObj.ExitCode
}
## https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/dd349804(v=ws.10)#BKMK_4
## https://docs.microsoft.com/en-us/powershell/scripting/learn/deep-dives/add-credentials-to-powershell-functions?view=powershell-7.2
function JoinToDomain {
[CmdletBinding()]
Param([Parameter(Mandatory=$true,ValueFromPipeline=$True, Position=0)][VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]] $VM,
[Parameter(Mandatory=$true)][String]$Domain,
[Parameter(Mandatory=$true)][String]$DomainUser,
[Parameter(Mandatory=$true)][String]$DomainPassword
)
$User = "$DomainUser@$Domain"
$SecPasswd = ConvertTo-SecureString $DomainPassword -AsPlainText -Force
$Credential = $User,($DomainPassword | ConvertTo-SecureString -AsPlainText -Force)
$ScriptText = "Add-computer -DomainName $($Domain) -Credential $($Credential)"
Write-Host "Invoke-VMScript -VM $VM -ScriptText $ScriptText -ScriptType Powershell -GuestUser "$GuestUser" -GuestPassword $GuestUserPassword"
$ScriptObj = Invoke-VMScript -VM $VM -ScriptText $ScriptText -ScriptType Powershell -GuestUser $VM\$GuestUser -GuestPassword $GuestUserPassword
Write-Host $ScriptObj.ScriptOutput
Write-Host $ScriptObj.ExitCode
}
function DoBulkVM {
$User = "$DomainUser@$Domain"
$StartingTime = $(get-date -f hh:mm:ss)
$SecPasswd = ConvertTo-SecureString $DomainPassword -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($User, $SecPasswd)
write-host "++++++++++++++++++++++++++++++++++++++++++"
write-host "++++++++++++++++++++++++++++++++++++++++++"
write-host "Inserire il Vcenter sul quale iniziare il deploy delle VM - digitare il numero corrispondente e premere invio" -ForegroundColor Red -BackgroundColor Yellow
write-host "." -ForegroundColor Red -BackgroundColor Yellow
write-host "1 -> vcenter11.vcserver.vmdomain.lan" -ForegroundColor Red -BackgroundColor Yellow
write-host "2 -> vcenter10.vcserver.vmdomain.lan" -ForegroundColor Red -BackgroundColor Yellow
write-host "3 -> vcenter13.vcserver.vmdomain.lan" -ForegroundColor Red -BackgroundColor Yellow
write-host "4 -> vcenter14.vcserver.vmdomain.lan" -ForegroundColor Red -BackgroundColor Yellow
$vcenter = read-host
switch ($vcenter)
{
1 {$vcenterserver = "vcenter11.vcserver.vmdomain.lan"}
2 {$vcenterserver = "vcenter10.vcserver.vmdomain.lan"}
3 {$vcenterserver = "vcenter13.vcserver.vmdomain.lan"}
4 {$vcenterserver = "vcenter14.vcserver.vmdomain.lan"}
default{"No Match Found"}
}
Connect-VIServer -Server $vcenterserver -User $viUser -Password $viPassword
for($i=0; $i -lt $VMList.Length; $i++)
{
$VMexists = Get-VM -name $VMList[$i].vmname -ErrorAction SilentlyContinue
$ComputerADexist = $null
$VMList[$i].Domain
$User = "$DomainUser@$Domain"
$SecPasswd = ConvertTo-SecureString $DomainPassword -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential -ArgumentList ($user, $SecPasswd)
$ComputerADexist = get-adcomputer $VMList[$i].vmname -server $VMList[$i].Domain -Credential $($creds)
#write-host "computer exist - $ComputerADexist"
#write-host "vmexist $VMexists"
if (!$VMexists -and !$ComputerADexist) {
$retobject= GetMostCapabilityDatastore
try {
$OSCS = New-OSCustomizationSpec -OSType Windows -ChangeSid -Domain $VMList[$i].Domain -OrgName "Covisian" -DomainCredentials $Credential -NamingScheme fixed -TimeZone "100" -NamingPrefix $VMList[$i].vmname -FullName $GuestUser -AdminPassword $GuestUserPassword
$VM = New-VM -Name $VMList[$i].vmname -Location $VMList[$i].vmlocation -Template $VMList[$i].template -ResourcePool $VMList[$i].cluster -Datastore $retobject.Datastore -DiskStorageFormat EagerZeroedThick -OSCustomizationSpec $OSCS
Write-Host "$VM on $retobject.Name has been created "
# StartAndGo -VM $VM
## SETTING NETWORK CARD
Write-host "SetNetworkCardType on $VM"
SetNetworkCardType -VM $VM -NetAdapterType 'Vmxnet3' -vlan $VMList[$i].vlan
## SETTING VIDEO CARD
Write-host "SetVideoCard on $VM"
SetVideoCard -VM $VM -Autodetect $True
## SETTING VLAN
Write-host "SetVlan $VMList[$i].vlan on $VM"
SetVlan -VM $VM -vlan $VMList[$i].vlan
StartAndGo -VM $VM
}
catch
{
}
$EndingTime = $(get-date -f hh:mm:ss)
}
}
Write-host "Started: " + $StartingTime + " Ended: + $EndingTime"
Disconnect-VIServer -Server $vcenterserver -Confirm:$false
}
### https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep-command-line-options?view=windows-11
### DO BULKING PROCESS
### MAIN ####
DoBulkVM
Considerazioni Finali
Lo script appena mostrato è stato concepito per il deploy di VMs Windows, ma può essere utilizzato come “suggerimento” per la costruzione di altri script per Sistemi Operativi differenti oppure per sistemi VMWare strutturati in modo differente.
Devifare il login per poter inviare un commento.