I’ve heard many authors talk about how through their writing process the story or the characters change. I guess this is one reason titles aren’t decided on until the very end of the writing process. When I submitted my session proposal for “No Money, No Problem!” I had originally planned on writing about using PowerShell/PowerCLI as an automation/orchestration engine. I’ve learned over the years that it “may” not be the best idea to fight the muse. During the writing process for this presentation I followed where the muse led and in the end this presentation ended up being much more about the potential ways that you can automate the life-cycle of a VM. I’m hopeful that if you attended the talk, that it was still useful for you despite the slight pivot. Fifteen minutes is not a lot of time for a technical talk, so this post is an deeper dive into the content from my VMTN presentation. So without further ado….
Day 1
The activities on Day 1 are all about configuring the environment. The example I chose to use in my session was setting up a vDS. You can just as easily apply the same logic to something like setting up iSCSI datastores, clusters or the like. The overall premise is that by leveraging PowerCLI you can speed up the delivery of your environments, while delivering higher quality infrastructure. Let’s take a quick peek at how this is done in the context of delivering the elements necessary to run a Distributed Switch. Striping out extraneous code and comments, what you’re left with is a five liner that will give you a Distributed Switch.
$DC=Get-Datacenter -Name "NoProblem"
New-VDSwitch -Name "vds_iscsi" -Location $DC -LinkDiscoveryProtocol LLDP -LinkDiscoveryProtocolOperation "Both" -Mtu 9000 -MaxPorts 256
New-VDPortgroup -Name "pg_iscsi_vlan5" -VDSwitch $(Get-VDSwitch -Name "vds_iscsi") -VlanId 5 -NumPorts 16
Add-VDSwitchVMHost -VMHost esx-06a.corp.local -VDSwitch vds_iscsi
$pNic=Get-VMHost esx-06a.corp.local|Get-VMHostNetworkAdapter -Physical -Name vmnic1
Get-VDSwitch -Name "vds_iscsi"|Add-VDSwitchPhysicalNetworkAdapter -VMHostPhysicalNic $pNic -Confirm:$false
So we pretty immediately get to the heart of why I’m such a PowerCLI advocate with this example. When we look at a command like “New-VDSwitch” it’s pretty intuitive what’s going on; we are creating a new vDS. It kind of doesn’t make sense to go through the plethora of switches/options as they are highly dependent on the situation. That being said there are a couple of items I’d like to call out in this example.
- PowerShell allows you to run a command in-line and pass the resulting object directly into a variable. That’s what you see happening here, where the Get-VDSwitch call is wrapped by $(…):
$(Get-VDSwitch -Name "vds_iscsi")
- The power of the PipeLine. By using this powerful tool you can string multiple commands together to create complex actions in a very small amount of real estate.
Day 2
New VM’s 1
It’s my belief that if folks do nothing else but automate the provisioning of VM’s, then then can deliver immense value to their organizations and do it quite quickly. In the code below we create an OSCustomizationSpec, leverage that within a temporary spec with static IP addressing, which is then leveraged in the New-VM example. This is a pretty basic example, but you take it as far as you’d like. In a previous role this simple VM deployment evolved and became the 1700 line basis for our automated deployments.
$DomainCred=Get-Credential -Message "Enter Domain Admin credentials" -UserName "corp.local\Administrator"
New-OSCustomizationSpec -name Win2k12 -Domain "corp.local" -DomainCredentials $DomainCred -Description "Windows 2012 App Server" -FullName "PatG" -OrgName "VMware" -ChangeSid
Get-OSCustomizationSpec "Win2k12" |New-OSCustomizationSpec -Name "Win2k12_temp" -Type NonPersistent
for ($i = 1; $i -le 4; $i++){
Get-OSCustomizationNicMapping-OSCustomizationSpec "Win2k12_temp"|Set-OSCustomizationNicMapping-IpMode UseStaticIP -IpAddress "192.168.10.10$i"-SubnetMask "255.255.255.0"-DefaultGateway "192.168.10.1"-Dns "192.168.10.10"
New-VM-Name "WinApplication0$i"-Template "base-w12-01"-OSCustomizationSpec "Win2k12_temp"-ResourcePool $(Get-Cluster"MyCluster")
}
Walking through this example line by line
Line 1: We enter domain credentials and store them in a PSCredential object for use later on.
Line 2: Using the New-OSCustomizationSpec we create a base OS Customization spec, which is used in …
Line 3: We create a temporary OS Spec which we’ll leverage in the customization and deployment of our VM’s. All of this however is just laying the ground work for
Line 5: Within the loop we take the previously created temporary OS Spec and we customize it for use with the …
Line 6: We get to the meat of the matter where we are deploying a VM using the new-vm cmdlet and our newly created and updated temp OS spec to configure Windows for us.
New VMs – Linux from json
While the previous example will simplify matters, it’s not exactly the prettiest code, not to mention the fact that values are hard coded. If you want to start taking your automation to the next level you have to be able to accept inputs in order for the code to be more portable. Thanks to PowerShell’s ability to interpret json (as well as xml, and host of other formats) we can simply read in the desired configuration, and somewhat dynamically create the VM. If you want to include splatting you can go even further with your abstractions, but that’s a post for another day.
$InputFile = “c:\temp\linux_servers.json”
$Servers = $(Get-Content $InputFile -Raw| ConvertFrom-Json).Servers
foreach ($Server in $Servers)
{
new-vm -name $Server.Name -ResourcePool $Server.Cluster -NumCpu $Server.CPU -MemoryGB $Server.Mem
}
The other Day 2 activity I chose to highlight is reporting. After all, how will you know about the performance and capacity of the environment, if you aren’t taking it’s pulse? Thanks to the kind folks at VMware, statistics can be exposed for use via the get-stat cmdlet which is the star of this example.
$objServers = Get-Cluster Demo | Get-VM
foreach ($server in $objServers) {
if ($server.guest.osfullname -ne $NULL){
if ($server.guest.osfullname.contains("Windows")){
$stats = get-stat -Entity $server -Stat "cpu.usage.average","mem.usage.average" -Start $start -Finish $finish
$ServerInfo = "" | Select-Object vName, OS, Mem, AvgMem, MaxMem, CPU, AvgCPU, MaxCPU, pDisk, Host
$ServerInfo.vName = $server.name
$ServerInfo.OS = $server.guest.osfullname
$ServerInfo.Host = $server.vmhost.name
$ServerInfo.Mem = $server.memoryGB
$ServerInfo.AvgMem = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "mem.usage.average"} | Measure-Object -Property Value -Average).Average)
$ServerInfo.MaxMem = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "mem.usage.average"} | Measure-Object -Property Value -Maximum).Maximum)
$ServerInfo.CPU = $server.numcpu
$ServerInfo.AvgCPU = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "cpu.usage.average"} | Measure-Object -Property Value -Average).Average)
$ServerInfo.MaxCPU = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "cpu.usage.average"} | Measure-Object -Property Value -Maximum).Maximum)
$ServerInfo.pDisk = [Math]::Round($server.ProvisionedSpaceGB,2)
$mycol += $ServerInfo
}
}
}
$myCol | Sort-Object vName | Export-Csv "VM_report.csv" -NoTypeInformation
In the example above we simply iterate through the cluster, and obtain statistics on our Windows VM’s via the aforementioned get-stat cmdlet. Next we store all of the information we care about in the $ServerInfo hashtable. This hashtable is ultimately what’s used for output at the end of the code snip.
I do want to take a moment to breakdown what’s happening in the calculations functions, as it can be a little off-putting if you don’t know what’s happening there. So let’s take the following line and break it down piece by piece.
$("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "mem.usage.average"} | Measure-Object -Property Value -Average).Average)
$(…) as we should know by now anything that leads with a $ sign is a variable. PowerShell allows us to cast results from commands or other objects into a variable simply by enclosing in parenthesis with a leading $.
“{0:N2}” Mathematical operator. In this case we’re formatting the value that is a result of the command that follows “-f”. In this specific instance I choose to keep two digits to the right of the decimal place. This is indicated by N2.
The command yielding our number just shows the amount of fun you can get into with pipelines. Starting just to the right of the “-f” option, we take our $stats object and pare it down using the Where-Object cmdlet. These values get further parsed out by piping into Measure-Object, which in this particular case is simply calculating out the average of the desired values in the object.
After all that is said and done, we can use the ever handy Export-Csv to come up with a pretty CSV for the powers that be, which shows just how efficient your environment is humming along!
D-day
Just like this blog post, and the presentation it supports, all good things must come to an end. And so it is with infrastructure as well. In our final example, we use the metrics from the all-powerful and omniscient vRealize Operations Manager. It should probably come as no surprise to you that the metrics which are stored in the all-knowing vROps server can be exposed to you via PowerCLI. If you’ve used or tested vROps I’m guessing that one of the first thing you checked out was the “Oversized VMs” report. We’ll use one of the statistics that make up this report in this last code snip:
$cred=Get-Credential
Connect-OMServer -Server "Demo" -Credential $cred -AuthSource "ADdomain"
$when = $(get-date).AddDays(-20)
$vkey = "Summary|Oversized"
$threshold = ".75"
foreach ($vm in $(get-vm|Select-Object -First 33)){
$vrstat=$vm|Get-OMResource
$avg = $vrstat|Get-OMStat -Key $vkey -from $when|Select-Object -ExpandProperty Value|Measure-Object -Average
write-host $vm.name, $avg.average
if($avg.Average -gt $threshold){
write-host $vm.name, $avg.average
if($vm.PowerState -eq "PoweredOn"){
stop-vm -vm $vm -Confirm:$true -WhatIf:$true
}
Start-Sleep 3
Remove-VM -vm $vm -DeletePermanently -RunAsync -Confirm:$true -WhatIf:$true
}
}
Starting to work with the vROps commandlets (contained within the VMware.VimAutomation.vROps module) has a little bit of a learning curve associated with it, so we’ll break down some of the key elements on a line by line basis again.
Line 2: vROps is a separate entity from vCenter so we need to use the Connect-OMServer cmdlet to connect. One of the things that is pretty poorly documented, which may trip you up, is the authentication model in use with this cmdlet. If you are using domain credentials you want to use your short-name and the display name that you setup in vROps as the domain authentication source.
line 9: In this case I chose to pass in a VM object into the Get-OMResource, but you can just as easily use the -Name parameter. Get-OMResource simply returns the vROPs object that we’ll use with…
Line 10: Get-OMStat. The Get-OMStat cmdlet is where you actually start returning metrics out of vROps. In this case I’m using the “Summary|Oversized” statistics key. There are literally thousands of key’s that you can leverage. I’d suggest perusing Mr. Kyle Ruddy’s post on this subject here. For the purposes of this very simple example I figured I’d use an average of the data returned over time to see if this machine is oversized and therefore a candidate for removal. Obviously in an real situation you’d want a lot more logic around an action like this.
$vrstat|Get-OMStat -Key $vkey -from $when|Select-Object -ExpandProperty Value|Measure-Object -Average
Breaking down the command line by line. We call the
$vrstat
object and pipeline it into Get-OMstat where we narrow down the results by key using the previously defined $vkey variable as well as date duration of 20 days defined in the $when variable. Finally I’m just interested in the actual values stored within the $vrstat object so we pipeline through the
Select-Object -ExpandProperty Value
cmdlet to pull only the data we want. Lastly we use the
Measure-Object
cmdlet to get an average for use in our simple example.
Line13: A simple If statement checks if we’ve crossed our pre-defined threshold. If we have, we move on to our D-day operations. Otherwise the script moves on to the next VM.
Line 16: You can’t delete a VM that’s powered on. Since we are deleting this VM anyway, there’s no need to gracerfully shut down, so we just power it off.
Line 19: So sorry to see you go VM.
Remove-VM
does exactly what it sounds like. If we omit the
-DeletePermanently
parameter we’ll simply remove the VM from inventory. In this case, we want it removed from disk as well, so the parameter is included. Lastly we don’t want to wait for the Remove operation before moving on to our next victim, so the
-RunAsync
parameter tells our script not to wait for a return before moving on.
NOTE: I don’t know if anyone will use this code or not (I surely hope it helps someone), however just in case you do, I’ve set -Confirm and -WhatIf to $true so that you don’t have any OOPS moments. Once you’re comfortable with what’s happing and how it’ll affect your environment, you can set these fit your needs.
As I said at the outset, I hope you found this talk and post useful. I plan on doing a couple of deeper dives into some of the above topics, so if you’re still reading I appreciate your follows.
Lastly, I’d like to offer up a huge thanks to the good folks at VMTN and vBrownBag for the opportunities they offer people like me and you. If you find this interesting or feel that you have a voice to contribute, please do! A vibrant community depends on engaged people like you.
Thanks for reading, see you soon.
SD