<# Create Unix VM's. PREREQUISITES: - A VM that has been installed with an OS that supports cloud-init - cloud-init is installed on the vm How it works: - Asks for a bunch of parameters - Creates a disk from parent vhdx for speed - Gets the vm name from the params and the existing vm with matching name - Creates a cloud-init config for the vm (currently sets only hostname and ansible user ssh key) - Sets up everything then starts the vm - if you ask for more than one, it creates in sequence WHAT TO DO: - Replace the values for baseVMpath, parentDiskPath and sshPubKey then run the script. FUTURE THINGS TO WORK ON : - Adding parameters for baseVMpath, parentDiskPath and sshPubKey - Adding the option to pass the other info in parameters as well so you don't have to redo it by hand -> with 2 parameter sets, depending on dynamic memory or not Helpful links (much thanks to the people who wrote these) : - https://computingforgeeks.com/how-to-create-centos-8-kvm-image-template-for-openstack/ - https://devops.ionos.com/tutorials/deploy-a-centos-server-customized-with-cloud-init/ - https://cloudinit.readthedocs.io/en/latest/topics/examples.html - https://github.com/MicrosoftDocs/Virtualization-Documentation/blob/master/hyperv-samples/benarm-powershell/Ubuntu-VM-Build/BaseUbuntuBuild.ps1 #> function Get-Info { clear $DynamicMemory = Read-Host "Dynamic Memory ? (y/n) " Switch ($DynamicMemory) { Y {Write-host -foreground green "Yes, dynamic memory!`n"; $dynMem=$true} N {Write-Host -foreground yellow "No, I want to set min and max and startup memory.`n"; $dynMem=$false} Default {Write-Host -foreground yellow "Default is that we're using it, because we believe in the power of Myth !`n"; $dynMem=$true} } if($dynMem) { $maxMem = Read-Host "Maximum memory ? (GB) " $minMem = Read-Host "Minimum memory ? (GB) " $startMem = Read-Host "Startup memory ? (GB) " } else { $startMem = Read-Host "Memory ? (GB) " } $cores = Read-Host "vCPU's ? (Cores) " $prefix = Read-Host "Prefix to use for these ? (A string) " $VMObject = New-Object -TypeName psobject $VMObject | Add-Member -MemberType NoteProperty -Name "UseDynamicMemory" -Value $dynMem $VMObject | Add-Member -MemberType NoteProperty -Name "maxMemory" -Value $([int]$maxMem) $VMObject | Add-Member -MemberType NoteProperty -Name "minMemory" -Value $([int]$minMem) $VMObject | Add-Member -MemberType NoteProperty -Name "startMemory" -Value $([int]$startMem) $VMObject | Add-Member -MemberType NoteProperty -Name "cores" -Value $([int]$cores) $VMObject | Add-Member -MemberType NoteProperty -Name "nestedVirtualization" -Value $false $VMObject | Add-Member -MemberType NoteProperty -Name "prefix" -Value $prefix return $VMObject } function Setup-VM { param($VMObject, $basepath, $parent, $sshPubKey) $vmname = Get-NextVMNumber($VMObject.prefix) $path = "$basepath\$vmname" write-host "Starting script...`n`nThis will create a VM with name $vmname.`nLet's go!`n`n" Add-VMVHD -vmname $vmname -path $path -parent $parent Create-VM -VMObject $VMObject -vmname $vmname -path $path Create-CloudInit -vmname $vmname -path $path -sshPubKey $sshPubKey Set-VMProperties -VMObject $VMObject -vmname $vmname Set-VMNetworkProperties -vmname $vmname Add-VMDvdDrive -VMName $vmname Set-VMDvdDrive -VMName $vmname -path "$($path)\metadata.iso" Start-VM $vmname } function Create-VM { param($VMObject, $vmname, $path) Write-host "Creating new virtual machine." -NoNewline New-VM -Name $vmname -MemoryStartupBytes ([int]($VMInfo.startMemory)*1GB) -BootDevice VHD -VHDPath "$path\Virtual Hard Disks\$vmname.vhdx" -Path "$path\" -Generation 2 -SwitchName (Get-VMSwitch -SwitchType External).name | out-null Write-Host -ForegroundColor Green " Done." } function Create-CloudInit { param( $vmname, $path, $sshPubKey ) $oscdimgPath = "C:\Program Files (x86)\Windows Kits\8.1\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe" $metaDataIso = "$($path)\metadata.iso" $metadata = @" instance-id: uuid-$([GUID]::NewGuid()) local-hostname: $($vmname) "@ $userdata = @" #cloud-config users: - name: ansible gecos: ansible sudo: ALL=(ALL) NOPASSWD:ALL groups: users, admin ssh_import_id: None lock_passwd: true ssh_authorized_keys: - $sshPubKey "@ # Output meta and user data to files if(-not (Test-Path "$($path)\Bits")) {New-Item -ItemType Directory "$($path)\Bits" | out-null} sc "$($path)\Bits\meta-data" ([byte[]][char[]] "$metadata") -Encoding Byte sc "$($path)\Bits\user-data" ([byte[]][char[]] "$userdata") -Encoding Byte # Create meta data ISO image - this thing apparently outputs in stderr so it shows as red but it's not errors, it's just the progress for it. & $oscdimgPath "$($path)\Bits" $metaDataIso -j2 -lcidata | Out-null } function Get-NextVMNumber { param($prefix) if((Get-VM -name "$prefix*").count -gt 0){ $prefix += (([int](get-vm -name "$prefix*" | select @{ Label = 'Number' ;Expression = { $_.VMName.Substring($prefix.length,2) } } | sort number | select -Last 1).number) + 1).tostring().padleft(2,"0") } else { $prefix += "01" } return $prefix.ToUpper() } function Add-VMVHD { param($vmname, $path, $parent) if(-not (Test-Path "$path\Virtual Hard Disks\$vmname.vhdx")) { if (-not (Test-Path "$path\Virtual Hard Disks")) { New-Item -Force -ItemType Directory -Path "$path\Virtual Hard Disks" | out-null } Write-host "Creating new VHD from parent $parent." -NoNewline New-VHD -Path "$path\Virtual Hard Disks\$vmname.vhdx" -ParentPath $parent -Differencing | Out-null Write-Host -ForegroundColor Green " Done." } } function Set-VMProperties { param($VMObject, $vmname) Write-host "Customizing virtual machine." -NoNewline if($VMInfo.UseDynamicMemory) { Set-VMMemory -VMName $vmname -MaximumBytes ([int]($VMInfo.maxMemory)*1GB) -DynamicMemoryEnabled $true -MinimumBytes ([int]($VMInfo.minMemory)*1GB) } Set-VM -VMname $vmname -ProcessorCount $VMInfo.cores -AutomaticStopAction ShutDown -AutomaticStartAction StartIfRunning -AutomaticStartDelay (Get-Random -Minimum 100 -Maximum 800) Set-VMFirmware -VMName $vmname -EnableSecureBoot Off -FirstBootDevice (get-VMHardDiskDrive -VMName $vmname) Get-VM -VMname $vmname | Enable-VMIntegrationService -Name * if($VMInfo.nestedvirtualisation) { Set-VMProcessor -VMName $vmname -ExposeVirtualizationExtensions $true } Write-Host -ForegroundColor Green " Done." } function Set-VMNetworkProperties { param($vmname) # This function sets the VLAN Write-host "Customizing networking." -NoNewline Set-VMNetworkAdapterVlan -VlanId 1 -VMName $vmname -Access Write-Host -ForegroundColor Green " Done." } #===================================== # The fun starts here #===================================== $baseVMpath = "D:\Virtual Machines\" $parentDiskPath = "D:\Virtual Machines Templates\Centos8.vhdx" # this is a full public key : ssh-rsa AAA[...]==" $sshPubKey = "" $VMInfo = Get-Info $total = Read-Host "How many VM do you want ? (1-...) " Write-Host "Starting timer.`n" $stopwatch = [system.diagnostics.stopwatch]::StartNew() For ($i=1; $i -le $total; $i++) { Write-host "Creating VM $i of $total." Setup-VM -VMObject $VMInfo -basepath $baseVMpath -parent $parentDiskPath -sshPubKey $sshPubKey } $stopwatch.Stop() Write-host "Total time : $([math]::Round($stopwatch.Elapsed.TotalSeconds,0)) seconds for $many Virtual machines."