{"id":258,"date":"2018-04-23T09:23:27","date_gmt":"2018-04-23T14:23:27","guid":{"rendered":"https:\/\/www.indycloudcover.com\/?p=258"},"modified":"2018-04-24T07:49:52","modified_gmt":"2018-04-24T12:49:52","slug":"vm-deployment-with-powercli-part-one","status":"publish","type":"post","link":"https:\/\/www.indycloudcover.com\/?p=258","title":{"rendered":"VM Deployment With PowerCLI part one"},"content":{"rendered":"<p>Tedious, repetitive processes are the number one factor in deciding whether to write a new script. In my current position, we are asked to deploy new VMs on a pretty regular basis and in some cases it may be upwards of 20 in one batch! The standard way we accomplish this is through the following steps:<\/p>\n<p>1. Create a new VM using the vSphere client, setting the general parameters<br \/>\n2. Attach the vendor created ISO that will do all the customization to the selected OS. (this step takes 30-60 minutes)<br \/>\n3. Set SNMP properties<br \/>\n4. Set IP and DNS settings<br \/>\n5. Disable IPV6<br \/>\n6. Add DNS search suffixes<br \/>\n7. Rename local Admin and Guest accounts<br \/>\n8. Install SCCM 2007 Client<br \/>\n9. Add our AD security group to the Local Administrators group<br \/>\n10. Install Cisco AMP<\/p>\n<p>As you can see, there are a lot of steps in the original build process. For one off VMs, this might be acceptable. However, when you need 24 VMs for a big project, it could easily take several work days of your time. This is when my distaste for repetitive, manual processes comes into play.<\/p>\n<p>The first thing that comes to mind, is how much of this can be streamlined through PowerCLI. As it turns out, most of it!  Now, having no official automation tools or even support from within to accomplish this task, I had to improvise.  This means there are a few factors other than PowerCLI in play.  I have a .CSV file containing all the fields necessary to complete the build.  Batch files and small Powershell scripts to do some customization.  Custom VM Templates and Customization Specifications.  As you can see, quite a few tools being used to achieve our modest goals.<br \/>\nThe .csv file contains the following fields:<\/p>\n<p>Name &#8211; Name of the VM<br \/>\nTemplate &#8211; Name of the Template we are using<br \/>\nCluster &#8211; Which Cluster are we putting this VM in<br \/>\nDatastore &#8211; What Datastore should we use<br \/>\nCustomization &#8211; This is the Customization Specification<br \/>\nvCPU &#8211; Number of vCPUs<br \/>\nMemory &#8211; Amount of memory<br \/>\nNetwork &#8211; Network the VM should be on<br \/>\nDiskformat &#8211; Thick or Thin<br \/>\nLocation &#8211; Which folder the VM should be in<br \/>\nipaddress &#8211; The IP address<br \/>\nmask &#8211; Subnet mask<br \/>\ngateway &#8211; Default Gateway<br \/>\ndns1 &#8211; DNS server<br \/>\ndns2 &#8211; DNS server<br \/>\ndisk1 &#8211; Disk one size<br \/>\ndisk2 &#8211; Disk two size (if no disk two, 0 or leave blank)<\/p>\n<p>The .CSV file has the above names as the first set of values. So the next step in the new process is: create\/edit the .CSV, in my case, it is named <em>NEWVMS.CVS<\/em><\/p>\n<p>Now we are ready to start the actual script.<\/p>\n<p>First we need to get the data from the .CSV<\/p>\n<pre title=\"Get External Data\" class=\"toolbar:1 lang:ps decode:true \">$vms = Import-CSV \"c:\\Scripts\\deploy\\NewVMs.csv\"<\/pre>\n<p>Something I decided to add was a time\/date stamp and who built the VM, so we need to get the username of the person running the script (another thing you may consider adding is a project name or sponsor.<\/p>\n<pre title=\"Get the current AD user\" class=\"toolbar:1 lang:ps decode:true \">$name = get-aduser -identity $env:username -Properties name | Select-Object -ExpandProperty name<\/pre>\n<p>Now we get to the fun stuff, importing the data and assigning it to variables<\/p>\n<pre title=\"Import Data and assign it to variables\" class=\"toolbar:1 lang:ps decode:true \">foreach ($vm in $vms)\r\n{\r\n\t#Assign Variables\r\n\t$VMName = $vm.Name\r\n\t$Template = Get-Template -name $vm.Template \r\n\t$Cluster = $vm.Cluster\r\n\t$Datastore = Get-Datastore -Name $vm.Datastore\r\n\t$Custom = Get-OSCustomizationSpec -Name $vm.Customization\r\n\t$vCPU = $vm.vCPU\r\n\t$Memory = $vm.Memory\r\n\t$Network = $vm.Network\r\n\t$Diskformat = $vm.Diskformat\r\n    $Location = $vm.Location\r\n\t\r\n\t$ipaddress = $vm.ipaddress\r\n\t$mask = $vm.mask\r\n\t$gateway = $vm.gateway\r\n\t$dns1 = $vm.dns1\r\n\t$dns2 = $vm.dns2\r\n\r\n    $disk1 = $vm.disk1\r\n    $disk2 = $vm.disk2<\/pre>\n<p>One more step before we build the VM, Set the IP, subnet mask, default gateway and DNS servers in the Customization Specification. If we don&#8217;t, we cannot have the VM join the domain using the guest customization routines.<\/p>\n<pre title=\"setup nic maping in the custimzation specification\" class=\"toolbar:1 lang:ps decode:true \"># setup nic maping in the custimzation specification\r\n\tGet-OSCustomizationSpec $Custom | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIp -IpAddress $ipaddress -SubnetMask $mask -DefaultGateway $gateway -Dns $dns1,$dns2<\/pre>\n<p>Now we can finally call the New-VM cmdlet and build the VM<\/p>\n<pre title=\"Build the VM\" class=\"toolbar:1 lang:ps decode:true \">#Build the VM\r\n\tNew-VM -Name $VMName -Template $Template -ResourcePool (Get-Cluster $Cluster | Get-ResourcePool) -location $location -StorageFormat $Diskformat -Datastore $Datastore -OSCustomizationSpec $Custom\r\n\tStart-Sleep -Seconds 10<\/pre>\n<p>Now we will set the VM name, Amount of Memory, Number of CPUs and the Network the Primary NIC will be on<\/p>\n<pre title=\"Set the vCPU, memory, and network\" class=\"toolbar:1 lang:ps decode:true \">#Set the vCPU, memory, and network\r\n\t$NewVM = Get-VM -Name $VMName\r\n\t$NewVM | Set-VM -MemoryGB $Memory -NumCpu $vCPU -Confirm:$false\r\n    $NewVM | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $Network -Confirm:$false<\/pre>\n<p>the last few steps are add the second disk (if it exists), set vm built date and builder in the notes field and start the VM!<\/p>\n<pre title=\"Final steps\" class=\"toolbar:1 lang:ps decode:true \">#Add Disk 2 if it exists\r\n       if ($disk2 -ne $null)\r\n        { $NewVM | New-HardDisk -CapacityGB $disk2 -StorageFormat $Diskformat }\r\n        else { }\r\n\r\n   \r\n    #Set vm build date \r\n    $notes = \"Built By:   \" + $name + \"`r`nBuild Date: \" + (get-date)\r\n    set-vm -vm $newvm -description $notes -Confirm:$false\r\n    \r\n    #Start the VM to continue the customization process\r\n    $NewVM | Start-VM\t\t\r\n}\r\n<\/pre>\n<p>At this point the VM build process is complete. But wait! What about the other steps? I will cover those in detail in my next post. for now, here is the complete script:<\/p>\n<pre title=\"deployvms.ps1\" class=\"toolbar:1 lang:ps decode:true \">$vms = Import-CSV \"c:\\Scripts\\deploy\\NewVMs.csv\"\r\n$name = get-aduser -identity $env:username -Properties name | Select-Object -ExpandProperty name\r\n\r\n\r\n\r\n\r\nforeach ($vm in $vms)\r\n{\r\n\t#Assign Variables\r\n\t$VMName = $vm.Name\r\n\t$Template = Get-Template -name $vm.Template \r\n\t$Cluster = $vm.Cluster\r\n\t$Datastore = Get-Datastore -Name $vm.Datastore\r\n\t$Custom = Get-OSCustomizationSpec -Name $vm.Customization\r\n\t$vCPU = $vm.vCPU\r\n\t$Memory = $vm.Memory\r\n\t$Network = $vm.Network\r\n\t$Diskformat = $vm.Diskformat\r\n    $Location = $vm.Location\r\n\t\r\n\t$ipaddress = $vm.ipaddress\r\n\t$mask = $vm.mask\r\n\t$gateway = $vm.gateway\r\n\t$dns1 = $vm.dns1\r\n\t$dns2 = $vm.dns2\r\n\r\n    $disk1 = $vm.disk1\r\n    $disk2 = $vm.disk2\r\n\t\r\n\t#setup nic maping in the custimzation specification\r\n\tGet-OSCustomizationSpec $Custom | Get-OSCustomizationNicMapping | Set-OSCustomizationNicMapping -IpMode UseStaticIp -IpAddress $ipaddress -SubnetMask $mask -DefaultGateway $gateway -Dns $dns1,$dns2 \r\n\t\r\n\t\r\n\t#Build the VM\r\n\tNew-VM -Name $VMName -Template $Template -ResourcePool (Get-Cluster $Cluster | Get-ResourcePool) -location $location -StorageFormat $Diskformat -Datastore $Datastore -OSCustomizationSpec $Custom\r\n\tStart-Sleep -Seconds 10\r\n\t\r\n\t#Set the vCPU, memory, and network\r\n\t$NewVM = Get-VM -Name $VMName\r\n\t$NewVM | Set-VM -MemoryGB $Memory -NumCpu $vCPU -Confirm:$false\r\n    $NewVM | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $Network -Confirm:$false\r\n\t \r\n\r\n    #Add Disk 2 if it exists\r\n       if ($disk2 -ne $null)\r\n        { $NewVM | New-HardDisk -CapacityGB $disk2 -StorageFormat $Diskformat }\r\n        else { }\r\n\r\n   \r\n    #Set vm build date \r\n    $notes = \"Built By:   \" + $name + \"`r`nBuild Date: \" + (get-date)\r\n    set-vm -vm $newvm -description $notes -Confirm:$false\r\n    \r\n    #Start the VM to continue the customization process\r\n    $NewVM | Start-VM\r\n\t\r\n\t\r\n\t\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Tedious, repetitive processes are the number one factor in deciding whether to write a new script. In my current position, we are asked to deploy new VMs on a pretty regular basis and in some cases it may be upwards of 20 in one batch! The standard way we accomplish this is through the following [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[],"_links":{"self":[{"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=\/wp\/v2\/posts\/258"}],"collection":[{"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=258"}],"version-history":[{"count":8,"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=\/wp\/v2\/posts\/258\/revisions"}],"predecessor-version":[{"id":265,"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=\/wp\/v2\/posts\/258\/revisions\/265"}],"wp:attachment":[{"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=258"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=258"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.indycloudcover.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=258"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}