Automate the provisioning of VMs across multiple platforms

Published on 14 December 2018

In terms of moving away from "point-and-click" administration, one of the first tasks should be to look at what can be automated. If you can automate it, you reduce human error, and you can allow your "customer" to initiate tasks. While virtualization has promised the creation of resources in minutes, the reality is that the processes, approvals, and internal delays, often turn minutes into weeks. So when you automate it, not only is it less error prone, you can turn the task into "standard work", which should mean you can get it done as an ITIL standard change, reducing the delays even further. In ITIL, standard changes don't need to go via a CAB, they are pre-approved.

The best way to do that, is normally using something like VMware vRealize Automation, Terraform, or similar, but if your Ops department don't have the skills to use those, you can put something together with Powershell and a Windows Server fairly easily. You will also need the AWS Powershell module, and PowerCLI, both downloadable from their respective websites.

Basically, you can create a folder structure, that is monitored by scheduled tasks, and then a Powershell command is executed depending on what is in the folder. In the example described here, I use a .CSV file (because it is easy to create manually), but you might want to use JSON or similar at some point. Additionally, you could use some kind of front end to create the CSV file and drop it in the folder. This post is going to look at "create-vm", which can be used to create a VM on either VMware or AWS, depending on the contents of the CSV file.

So the folder structure looks like this:

Provisioning1

Once you have you folder structure (we are only talking about create-vm here though), you need to create a task to check it. I do that with a scheduled task, which is scheduled to run a .PS1 file.

Provisioning2

A typical CSV file could look like this for a VMware VM:

    Platform,Name,EOL,Owner,template,datastore,CPU,RAM
    vmware,TestVM-123,15/3/19,GregH,Windows Server 2012 R2 Standard,Datastore-1

Or like this for an AWS VM:

    platform,name,EOL,owner,template,cpu,ram
    aws,TestVM-123,12/3/19,GregH,ami-009d6802948d06e52

Then you just need to create the Powershell code to do the work! There are several key decisions to make here, such as whether you want to create a new VM from scratch, or clone it from a template. The Powershell script below will allow for both. You can either specify a template, or not. For AWS, you really have to have one, so it will choose a template anyway (coded at the moment, but could be changed to be more dynamic). Also, it will apply tags for the VM. For AWS, this will include the name, but for both, it will also include the "owner" and an EOL date. This is useful for tracking, and also so you can then remove servers after a specific period of time, which is very useful for development servers.

So here is the code for create-vm.ps1:

            $path = 'c:\scripts\input\create-vm' # where to look for request files
            $logpath = 'c:\scripts' # where the log is created
            $CSVprops = 'Platform', 'Name', 'EOL', 'Owner' # These are the properties *required* in the CSV file.
            $vc = "vcs-651.orion.infra.com" # For VMware only
            $credentialsPath = 'c:\scripts\Credentials' # For VMware only 
            $region = 'us-east-1' # For AWS only
            $AWSprofile = 'GregH' # For AWS only
            $AWSKey = 'GregH2O-USEast-KP' # For AWS only
            if (test-path -Path $path\* -PathType leaf) {
                    $files = get-childitem -path $path
                    foreach ($file in $files) {
                    $now = get-date -format s
                    "$now - File found, $file " >> $logpath\create-vm.log 
                    if (!$file.Extension -match 'csv') {
                            $now = get-date -format s
                            "$now - ERROR: $file is not a CSV file, exiting. " >> $logpath\create-vm.log
                            remove-item -path "$path\$file" -Force
                            exit
                    }
                    $vm = import-csv $path\$file
                    # Check the file for the Properties required
                    foreach ($prop in $CSVprops) {
                    $now = get-date -format s
                    # "$now - Checking for $prop property in CSV file. " >> $logpath\create-vm.log
                    if (!$vm.$prop) {
                            "$now - ERROR: $prop property NOT found in CSV file, exiting. " >> $logpath\create-vm.log
                            remove-item -path "$path\$file"
                            exit
                    } 
                    }
                    # Create VMware VM if vm platform is VMware
                    if ($vm.Platform -eq "VMware") {
                    $vmname = $vm.name
                    $now = get-date -format s
                    "$now - Importing VMware module." >> $logpath\create-vm.log 
                    import-module vmware.vimautomation.core
                    Connect-Viserver $vc -Credential (Import-clixml "$credentialsPath\administrator@vsphere.local.clixml")
                    $now = get-date -format s
                    "$now - Connected to vCenter Server, $vc" >> $logpath\create-vm.log
                    # Connect-Viserver $vc -Credential (Import-clixml "$credentialsPath\admrm1200.clixml")
                    "$now - Checking for VM request file" >> $logpath\create-vm.log 
                    $now = get-date -format s
                    "$now - Choosing VMhost" >> $logpath\create-vm.log 
                    $vmhost = (get-vmhost)[0]
                    If ($vm.template) {
                            try {
                            $vmTemplate = Get-Template -Name $vm.template
                            $vmDatastore = get-datastore -name $vm.datastore
                            $vmCustomSpec = Get-OSCustomizationSpec -name $vm.template 
                            new-vm -name $vm.Name -template $vmTemplate -datastore $vmDatastore -OSCustomizationSpec $vmCustomSpec -host $vmhost -ResourcePool (get-resourcepool production)
                            $now = get-date -format s
                            "$now - VMware VM created - $vmname" >> $logpath\create-vm.log 
                            }
                            Catch {
                            $now = get-date -format s
                            "$now - ERROR: VMware VM NOT created - $vmname. The following was returned:" >> $logpath\create-vm.log 
                            "$_.Exception" >> $logpath\create-vm.log
                            }
                    }
                    else {
                            # no template specified
                            try {
                            $vmDatastore = get-datastore -name $vm.datastore
                            new-vm -name $vm.Name -NumCpu $vm.CPU -MemoryGB $vm.RAM -datastore $vmDatastore -host $vmhost -ResourcePool (get-resourcepool production)
                            $now = get-date -format s
                            "$now - VMware VM created - $vmname" >> $logpath\create-vm.log 
                            }
                            Catch {
                            $now = get-date -format s
                            "$now - ERROR: VMware VM NOT created - $vmname. The following was returned:" >> $logpath\create-vm.log 
                            "$_.Exception" >> $logpath\create-vm.log
                            }
                    }
                    # apply tags
                    $vmEOL = $vm.eol
                    if ($vmeol) {
                            $eoltag = get-tagcategory -name "EOL"
                            if ($eoltag) {
                            # tag category exists, apply tag
                            write-host "tag exists"
                            new-tag -category "EOL" -Name "$vmEOL" 
                            New-TagAssignment -tag "$vmeol" -Entity $vm.name
                            }
                            else {
                            # tag category doesn't exist, create it, then apply it
                            write-host "tag doesn't exit"
                            new-tagcategory -name "EOL" -description "Can be used as an end-of-life marker for the VM"
                            new-tag -category "EOL" -Name "$vmEOL" 
                            New-TagAssignment -tag "$vmeol" -Entity $vm.name
                            }
                    } 
                    $vmOwner = $vm.owner
                    if ($vmOwner) {
                            $Ownertag = get-tagcategory -name "Owner"
                            if ($Ownertag) {
                            # tag category exists, apply tag
                            write-host "tag exists"
                            new-tag -category "Owner" -Name "$vmOwner" 
                            New-TagAssignment -tag "$vmOwner" -Entity $vm.name
                            }
                            else {
                            # tag category doesn't exist, create it, then apply it
                            write-host "tag doesn't exit"
                            new-tagcategory -name "Owner" -description "The person who is the contact for the VM"
                            new-tag -category "Owner" -Name "$vmOwner" 
                            New-TagAssignment -tag "$vmOwner" -Entity $vm.name
                            }
                    } 
                    disconnect-viserver $vc -Force -Confirm:$false
                    }
                    # Create AWS VM
                    if ($vm.Platform -eq "AWS") {
                    import-module awspowershell
                    Initialize-AWSDefaults -ProfileName $AWSprofile -region $region
                    $vmname = $vm.name
                    $eol = $vm.eol
                    $owner = $vm.owner
                    $tag1 = @{ Key = "Name"; Value = "$vmname" }
                    $tag2 = @{ Key = "EOL"; Value = "$eol" }
                    $tag3 = @{ Key = "Owner"; Value = "$owner" }
                    $now = get-date -format s
                    "$now - Creating tags - " + $tag1.key >> $logpath\create-vm.log 
                    $tagspec1 = new-object Amazon.EC2.Model.TagSpecification
                    $tagspec1.ResourceType = "instance"
                    $tagspec1.tags.add($tag1)
                    $now = get-date -format s
                    "$now - Connecting to AWS $region - $vmname" >> $logpath\create-vm.log
                    Try {
                            #New-EC2instance -imageID "ami-009d6802948d06e52" -keyname $AWSKey -maxcount 1 -instancetype "t2.micro" -subnetid "subnet-aa4fdf84" -TagSpecification $tagspec1
                            New-EC2instance -imageID $vm.template -keyname $AWSKey -maxcount 1 -instancetype "t2.micro" -subnetid "subnet-aa4fdf84" -TagSpecification $tagspec1
                            $now = get-date -format s
                            "$now - AWS VM created - $vmname" >> $logpath\create-vm.log 
                    }
                    Catch {
                            $now = get-date -format s
                            "$now - ERROR: AWS VM NOT created - $vmname" >> $logpath\create-vm.log 
                    }
                    }
            }
            # Cleanup - remove request file
            remove-item -path "$path\$file"
            $now = get-date -format s
            "$now - Request file, $file, deleted." >> $logpath\create-vm.log 
            }


            

It should be fairly self-explanatory, but it is split into two sections, one for when the platform is AWS, and one for when it is VMware.

Next step is to put it into a function, and add additional tags. In the philosophy of Agile, rather than Waterfall though, you want to start with something and improve it iteratively, rather than waiting until it is pefect! Once you have setup the folder and tested it, you can then work on the other common tasks (tearing down the VM, managing snapshots, etc). Because you are using a standard process, using standard templates, on a standard platform, you should quickly to able to get this done without having to go through a Change Advisory Board (CAB) for every VM, potentially reducing the delivery time from weeks, to minutes. Not only that, you are freeing up time to work on other things besides creating VMs (and then going back to fix the problems because you did it manually!).

comments powered by Disqus