VMware

September 25, 2009

How to run PowerCLI scripts from vCenter Alarms.

Background
vCenter provides an alarm feature which causes vCenter to automatically look for certain events or conditions within your virtual environment, and automatically take some action when conditions are met. There are quite a lot of criteria supported, you can generate alarms based on vCenter's event stream, so for example you could create an alarm any time a virtual machine is powered off. Alarms could also be created based on vCenter statistics, for example an alarm can be generated if an ESX host's memory utilization is above 90% for too long.

There are a number of actions you can take in response to an alarm. For example you can send email or generate an SNMP trap. You can also run scripts. A popular topic on the PowerCLI forum lately is how to run PowerCLI scripts in response to vCenter actions. Unfortunately the process is pretty tricky, so I've created some sample scripts and laid out a process that will hopefully give you everything you need to get going.

Note:
These instructions were prepared based on vSphere 4.0. If you're using VirtualCenter, things may vary a bit (or maybe a lot), I haven't actually tried it. If you try this with VirtualCenter, please leave a note with your experiences.

Step 1: Within vCenter, decide on a well-defined location for your scripts.
Your life will be a lot easier if you put everything in one place, so you should decide where you want all your scripts and data to go. I recommend using a directory called %ALLUSERSPROFILE%\application data\VMware\VMware VirtualCenter\scripts. You'll have to create this directory but using this location has the advantage that your scripts will exist alongside a lot of other vCenter data.

One thing to note is that vCenter doesn't actually make use of environment variables, so henceforth I'm always going to use the location of C:\Documents and Settings\All Users\\application data\VMware\VMware VirtualCenter\scripts. Your drive letter and location may vary, so make the adjustment accordingly.

Step 2: Copy all the sample scripts into this location.
Getting the scripts just right is a tedious and painful process, so I've created some starter content that should make it a lot easier. After you've decided on your script location, copy all the files from the Alarms repository below into your directory.



After you've done that, your target directory will look a little something like this:

Vcenterfolder

Step 3: Decide on an authentication strategy.

If you don't want to log in to vCenter in response to an alarm, skip this section. However there's a good chance that when an alarm is fired, you want to do something to vCenter in response to it. Of course, you'll need to authenticate to vCenter to do that. PowerCLI supports SSPI and single-sign-on, but chances are that won't help you here. The reason is that vCenter runs alarm scripts using the same account that is used for the vCenter service. By default this is the local system account, and a script running as local system won't be able to use SSPI to log in the vCenter.

There are 3 ways to deal with this:

  1. Code usernames and passwords into your scripts. This is the easiest and least secure approach.
  2. Change the vCenter service to use an actual domain login. To be honest I'm not sure if this is even possible, but if it is it will be difficult to use this approach if you're not already using it. On the other hand this approach would allow use of SSPI and would not require any ongoing maintenance.
  3. Store encrypted credentials for the local system account to use when running the script. This approach is much more secure than the first approach yet is not nearly as difficult as the third approach. It's also the approach I took, so I'll spend a bit of time discussing how to do it.

Approach 3 relies on the fact that PowerShell makes it pretty easy to deal with the Windows Data Protection API. This allows us to store a credential in a file in such a way as it can only be decrypted by someone who has logged in using the same identity that was used to export the credential. If you look at the screenshot above, there's a file called systemCredentials.enc.xml. This is my exported credential.

The trick is that the alarm script is going to be running as local system, so to make it possible to read this credential, I have to export this credential as local system. If I export it as Administrator, the alarm script won't be able to use it. This means that somehow I have to run PowerShell as local system. There are tricks for starting command prompt as local system, if you're running pre-Vista it's pretty simple to launch one using the "at" command. If you're on Windows 2008 you can use the psexec utility (also described in the same link).

Once you manage to get a local system command prompt, launch PowerShell and navigat to your script directory. Then "dot source" the credentialManagement.ps1 file and run Export-PSCredential. This brings up a prompt for credentials, so type in the user name and password you want to use against vCenter. This will cache your credential and you're ready to start running alarm scripts securely. Check your directory against my screenshot above to ensure that things appear to be in order.

. .\credentialManagement.ps1

Export-PSCredential -path systemCredentials.enc.xml

The downside to this approach is that if your vCenter password ever changes, you need to go in and re-cache the credential.

The other thing to note is that my scripts assumes you use this approach. If you don't, you'll have to edit each of them accordingly.

Step 4: Associate an alarm with a sample script.
You may think that from here it's just as simple as associating an alarm with your favorite .ps1 file. Unfortunately no, vCenter will only run batch files. So we need to create an intermediate batch file that will call our PowerShell file. This is the purpose of redirect.bat, we can use this batch file to call any PowerShell file we want. Since redirect.bat takes a script as an argument, we avoid the situation of having one batch file per PowerShell file.

Let's walk through the whole process of associating an alarm with a PowerCLI script. We'll create an alarm that fires any time a host's CPU utilizatoin is less than 75% and launches a script that will create a new VM on this not-busy-enough host. Of course this is a pretty stupid thing to do, but it's a good way to prove that everything is working as it should be.

First, let's create an alarm. If you've never created an alarm you may have a hard time figuring out how to actually do it. To do it, go to the very top of your inventory tree and right click on the root node, then select Alarm > Add Alarm

Createanalarm

There are 4 tabs to fill out with the new alarm. Until you master all of this stuff I recommend just copying what I've got here.

In the first tab, within that Alarm Type group, select Hosts under the Monitor pulldown.

Screen1

In the Triggers tab, add a trigger based on Host CPU Usage (%). Set the condition to "Is below". Set the condition length for both warning and alert to 30 seconds.

Screen2

In the Reporting tab change "Repeat triggered alarm every" to 1 minute.

Screen3

In the Actions tab, within the Frequency group change "Repeat actions every" to 1 minute. Then create an action. For the action type select "Run a command". On the right-hand side there are 4 pulldowns based on the state transitions. Set the first 3 of these to Repeat. Now comes the important part: within Configuration you need to enter the path to the redirect batch script, giving it an argument of the PowerShell script to run. For our purposes enter:

"C:\Documents and Settings\All Users\Application Data\VMware\VMware VirtualCenter\scripts\redirect.bat" "C:\Documents and Settings\All Users\Application Data\VMware\VMware VirtualCenter\scripts\sampleHostAlarm.ps1"

This extremely long line needs to go in without spaces or linefeeds. If you're using a different directory from the one I suggested, you'll also need to adjust that here.

Screen4

Now we're ready to go. If you've done everything right, all of your not-too-busy hosts will start getting new VMs in less than a minute. That will continue until you disable or delete the alarm, so don't let it run too long. You can modify or delete an alarm by clicking the Alarms tab.

Step 5: Decide if you want to play it safe.
Your scripts may never exit. There are thousands of reasons why, it may enter an infinite loop, it may sit waiting for input, who knows? When vCenter runs a script, it times the operation and kills it after 5 minutes. Unfortunately it only kills the command prompt that it launches and not any child processes the command prompt may have created, so in reality vCenter is unable to reign in any runaway scripts.

One way we can cope with that is: before running any alarm script, check for old leftover scripts and kill them. If you want to play it safe and do that for yourself, I've provided a script called alarmCleanup that will do it. To use it, all you have to do is uncomment the relevant line in redirect.bat and you'll have extra protection against runaway scripts.

Step 6: Write your own scripts, using the samples as a guide.

I've provided 3 sample scripts to get you going, but there are two things worth pointing out that are not really obvious as you start writing your own scripts.

First, when your script is run, several environment variables will be defined, as this table shows.

Environment Variable Name

Sample Value

VMWARE_ALARM_TRIGGERINGSUMMARY

Metric Usage = 0%                              

VMWARE_ALARM_DECLARINGSUMMARY

([Yellow metric Is below 75%; Red metric Is b...

VMWARE_ALARM_OLDSTATUS

Green                                          

VMWARE_ALARM_TARGET_NAME

192.168.1.11                                   

VMWARE_ALARM_ALARMVALUE

Current values for metric/state                

VMWARE_ALARM_TARGET_ID

host-1560                                      

VMWARE_ALARM_NAME

New Alarm                                      

VMWARE_ALARM_NEWSTATUS

Red                                             

VMWARE_ALARM_EVENTDESCRIPTION

Alarm 'New Alarm' on 192.168.1.11 changed fro...

VMWARE_ALARM_ID

alarm-46                                       


If you want to get the object that actually triggered the alarm, the best thing to use is the ID. Most PowerCLI Get cmdlets support a -ID argument that lets you load objects by ID. However, the ID that PowerCLI cmdlets use are a bit different from the IDs that will be set in the environment variable. For example, in the table above we have an id of host-1560. However, we can't call Get-VMHost -ID host-1560 and expect it to work, instead we have to prepend the term HostSystem- to it. In other words we would run:

    Get-VMHost -Id HostSystem-host-1560

to get the host that triggered the alarm. The same is true of other objects, and you can see a table of prefixes below.

When dealing with this:

Prepend this to the ID

Virtual Machines

VirtualMachine

Hosts

HostSystem

Clusters

ClusterComputeResource

Datacenters

Datacenter

Datastores

Datastore


Since alarms can only be associated with one type of object, you can hard-code this string inside the script that will respond to the alarm. Again there are samples to get you started in the right direction.

Troubleshooting.
Unfortunately this process is pretty tricky and you're probably not going to get it right the first time. If you need to diagnose what's going on, I recommend downloading Process Explorer.Some of the key things to note, when the alarm fires you should see a new process spawn as a child of vpxd.exe, then quickly disassociate from it (this is due to the use of "start" within redirect.bat).

Procexp

Another important thing to note, if your PowerShell process isn't behaving properly, right-click on it and take a look at its environment and command line.

Procexp2

Expect the process of getting alarm scripts to take a bit of experimentation, but once you've got the first one working the rest is very easy. Good luck!

June 22, 2009

Listing RDMs and adding NFS datastores all over the place.

This post is going to cover some useful and popular recent content seen on our community. If you haven’t seen our community, it really is the best way to learn about PowerCLI and start getting automated. Today I’m going to touch on two topics. First, I’ll cover a script to list all RDM information, then give a script that will let you add NFS datastores to all your hosts in one shot.

Listing RDM Information

If you’re wondering what an RDM is or why you might use it, check out Rich Brambley’s blog for a really good explanation. Long story short is that RDMs are often used because you need to use Microsoft Cluster Services, or if you want to take advantages of features of your storage hardware.

In PowerCLI, RDMs will show up when you run the Get-HardDisk cmdlet. Specifically they will have a type of either “rawPhysical” or “rawVirtual”, depending on the type of RDM. However, they won’t have things like the SCSI LUN, which you may find very useful to report on. If you need this information, Luc Dekens has the script you’re looking for. It will list all RDMs on all VMs, along with their size, SCSI device, etc.

Adding NFS Datastores to Multiple vCenter or ESX Hosts

If you have more than a few hosts you probably have shared storage, this is what enables a lot of vSphere’s great features like vMotion. For this to work smoothly, however, you need that storage to be available on all your hosts in a consistent way, and this can turn in to quite a challenge as your environment grows.

Not long ago a question was asked about how to automate adding datastores across multiple vCenters. Not surprisingly, Luc also suggested an answer, and it’s a pretty good one. However I want to offer a slightly different answer. The first thing we should do is define all our NFS mapping information in a CSV file, like this:

VCName

DatastoreName

NfsHost

NfsExport

192.168.1.1

NFS1

10.24.1.1

/share

192.168.1.1

NFS2

10.24.1.2

/share

192.168.1.1

NFS3

10.24.1.3

/export

192.168.1.2

NFS1

10.24.1.1

/share

192.168.1.2

NFS2

10.24.1.2

/share

192.168.1.2

NFS3

10.24.1.3

/export

192.168.1.3

NFS1

10.24.1.1

/share

192.168.1.3

NFS2

10.24.1.2

/share

192.168.1.3

NFS3

10.24.1.3

/export


One great feature of PowerShell is its Import-Csv cmdlet, which will turn lines from a CSV file into objects that you can access just like any other object. My variation on Luc’s solution is to use the Group-Object cmdlet and group these entries by the VCName property. This way, if you’re adding multiple datastores to a single VC, you only have to log in once. Here’s my full solution to the problem, which assumes that you’ve saved your data into a file called nfs.csv.

Import-Csv nfs.csv | Group VCName | Foreach {
 Connect-VIServer -Server $_.Name
 Foreach ($entry in $_.Group) {
  Get-VMHost | New-Datastore -Nfs -Name $entry.DatastoreName `
   -Path $entry.NfsExport -NfsHost $entry.NfsHost
 }
}

Closing Thought

Be sure to check out our community, it really is the most valuable source of information for automating with PowerCLI.

May 27, 2009

Moving custom fields from one vCenter to another.

If you’re looking at upgrading to vSphere, one thing that may come in handy is an easy and flexible way to move custom fields from your old vCenter to your new one. With PowerCLI this is quite easy… except for one thing: there is no Get-CustomField cmdlet! The why is a long story but it turns out it’s pretty easy to write just such a thing. In fact here it is:

function Get-CustomField {
 param($item)
 
 foreach ($cf in $item.CustomFields) {
  if ($cf.Value) {
   $field = "" | Select Key, Value, EntityName
   $field.Key  = $cf.Key
   $field.Value  = $cf.Value
   $field.EntityName = $item.Name
   Write-Output $field
  }
 }
}

Not too hard at all, really. So how do we use this? One nice feature of PowerCLI that often gets overlooked is its ability to connect to multiple vCenters at once and manage them all. We can take advantage of this to connect to both our old vCenter and our new one, and move the values directly between them. The way this works is to store the output of Connect-VIServer in a variable, then pass this variable to later cmdlets to specify the server you want to manage. For example:

$server1 = Connect-VIServer vcserver1
$server2 = Connect-VIServer vcserver2

This next bit of code will take all custom fields on all VMs in vcserver1 and transfer them to vcserver2. This assumes the VMs already exist in both locations.

$fields = Get-VM -server $server1 | Foreach { Get-CustomField $_ }
$vms = Get-VM -server $server2
foreach ($field in $fields) {
 $vm = $vms | Where { $_.Name -eq $field.EntityName }
 Set-CustomField -Entity $vm -Name $field.Key -Value $field.Value
}

That’s really all there is to it. If you need to transfer custom fields on more than just VMs, say maybe you also have fields on ESX hosts, you can use a more general bit of code that will do this for all types of objects. I’ve added that example to my script repository and you can download it here.