27 July 2011

Backup/Export Full DRS Rule Info via PowerShell

Updated 22 Jan 2015
Luc Dekens and I got together on a new PowerShell module for managing/exporting/importing DRS Rules and Groups. See our posts at http://www.lucd.info/2015/01/22/drsrule-drs-rules-and-groups-module and http://www.vnugglets.com/2015/01/drsrule-new-powershell-module-for-drs.html for all of the juicy details. Going forward, that DRSRule module is the way to go. I am leaving this post intact for posterity. Enjoy!
End of update - 22 Jan 2015

We, like many, had the need for a way to backup our DRS rules. Searches turned up many pages, all of which had the same basic recipe: use the Get-DrsRule to grab the rules, get a few properties, and export to CSV.

The problem: Addressing the task getting all of the pertinent info for rules of type ClusterVmHostRuleInfo, or, "VM-to-Host" rules. Rules of this type do not have the VM IDs in a property of the rule, but rather have the VMGroupName and HostGroupNames properties. From these properties, and the .NET view object of the given cluster, one can retrieve the VMs' and hosts' info.

So, we did a little work, and wrote the following to, for all DRS rule types, get the information that would be required if one wanted to recreate the rules:
## Script function: Gather/export DRS rule info, including VMGroup and HostGroup members
## Author: vNugglets.com

## file to which to write the DRS rules' info
$strOutputFilespec = "e:\someFolder\DRSrules_export.csv"

## get cluster .NET view objects, which hold the info about the DRS rules
Get-View -ViewType ClusterComputeResource -Property Name, ConfigurationEx | %{
    ## if the cluster has any DRS rules
    if ($_.ConfigurationEx.Rule -ne $null) {
        $viewCurrClus = $_
        $viewCurrClus.ConfigurationEx.Rule | %{
            $oRuleInfo = New-Object -Type PSObject -Property @{
                ClusterName = $viewCurrClus.Name
                RuleName = $_.Name
                RuleType = $_.GetType().Name
                bRuleEnabled = $_.Enabled
                bMandatory = $_.Mandatory
            } ## end new-object

            ## add members to the output object, to be populated in a bit
            "bKeepTogether,VMNames,VMGroupName,VMGroupMembers,AffineHostGrpName,AffineHostGrpMembers,AntiAffineHostGrpName,AntiAffineHostGrpMembers".Split(",") | %{Add-Member -InputObject $oRuleInfo -MemberType NoteProperty -Name $_ -Value $null}

            ## switch statement based on the object type of the .NET view object
            switch ($_){
                ## if it is a ClusterVmHostRuleInfo rule, get the VM info from the cluster View object
                #   a ClusterVmHostRuleInfo item "identifies virtual machines and host groups that determine virtual machine placement"
                {$_ -is [VMware.Vim.ClusterVmHostRuleInfo]} {
                    $oRuleInfo.VMGroupName = $_.VmGroupName
                    ## get the VM group members' names
                    $oRuleInfo.VMGroupMembers = (Get-View -Property Name -Id ($viewCurrClus.ConfigurationEx.Group | ?{($_ -is [VMware.Vim.ClusterVmGroup]) -and ($_.Name -eq $oRuleInfo.VMGroupName)}).Vm | %{$_.Name}) -join ","
                    $oRuleInfo.AffineHostGrpName = $_.AffineHostGroupName
                    ## get affine hosts' names
                    $oRuleInfo.AffineHostGrpMembers = if ($_.AffineHostGroupName -ne $null) {(Get-View -Property Name -Id ($viewCurrClus.ConfigurationEx.Group | ?{($_ -is [VMware.Vim.ClusterHostGroup]) -and ($_.Name -eq $oRuleInfo.AffineHostGrpName)}).Host | %{$_.Name}) -join ","}
                    $oRuleInfo.AntiAffineHostGrpName = $_.AntiAffineHostGroupName
                    ## get anti-affine hosts' names
                    $oRuleInfo.AntiAffineHostGrpMembers = if ($_.AntiAffineHostGroupName -ne $null) {(Get-View -Property Name -Id ($viewCurrClus.ConfigurationEx.Group | ?{($_ -is [VMware.Vim.ClusterHostGroup]) -and ($_.Name -eq $oRuleInfo.AntiAffineHostGrpName)}).Host | %{$_.Name}) -join ","}
                    break;
                } ## end block
                ## if ClusterAffinityRuleSpec (or AntiAffinity), get the VM names (using Get-View)
                {($_ -is [VMware.Vim.ClusterAffinityRuleSpec]) -or ($_ -is [VMware.Vim.ClusterAntiAffinityRuleSpec])} {
                    $oRuleInfo.VMNames = if ($_.Vm.Count -gt 0) {(Get-View -Property Name -Id $_.Vm | %{$_.Name}) -join ","}
                } ## end block
                {$_ -is [VMware.Vim.ClusterAffinityRuleSpec]} {
                    $oRuleInfo.bKeepTogether = $true
                } ## end block
                {$_ -is [VMware.Vim.ClusterAntiAffinityRuleSpec]} {
                    $oRuleInfo.bKeepTogether = $false
                } ## end block
                default {"none of the above"}
            } ## end switch

            $oRuleInfo
        } ## end foreach-object
    } ## end if
} | Export-Csv -NoTypeInformation $strOutputFilespec

This bit of code, using the Get-View cmdlet for max speed and min memory footprint, starts off by getting a couple of select properties for all clusters. If the cluster has any DRS rules defined, then the code, for each rule:
  • creates a new PSObject to hold the pertinent info about the rule
  • includes basic rule info, the rule type, and info about the objects involved with the rule, including the VM group names/members and the host group names/members (for both Affine- and AntiAffine host groups)
  • returns the object
  • exports all of the gathered info to CSV for later consumption

Arnim van Lieshout wrote a good post with more information about DRS rules themselves at Managing VMware DRS rules using PowerCLI, and included a few functions for creating VMGroups ("Virtual Machines DRS Groups" in the vSphere client) and HostGroups ("Host DRS Groups"), and for creating new VM-to-Host rules. We will likely be using said functions for our "recreate the DRS rules/groups" work.

25 July 2011

Setting MAC Address for VM NICs using PowerShell

A bit ago, there were some questions in the VMware VMTN community forums about setting explicit MAC addresses for VMs' NICs.

While this can be done via PowerCLI using the Set-NetworkAdapter cmdlet, the MAC address range is restricted to 00:50:56:00:00:00 - 00:50:56:3F:FF:FF when using the cmdlet, which is only a subset of the range that VMware uses.

An example came up where one might need to set the MAC address to something in a valid VMware range (with VMware OUIs like "00:50:56" or "00:0C:29") outside of the subset above. This can be done using the Web Services API, as it does not have the same range limitation as the cmdlet. The well-commented code below shows how to do so using the API:

## script function:  set the MAC address of a VM's NIC
## Author: vNugglets.com

## the name of the VM whose NIC's MAC address to change
$strTargetVMName = "myVM01"
## the MAC address to use
$strNewMACAddr = "00:50:56:90:00:01"

## get the .NET view object of the VM
$viewTargetVM = Get-View -ViewType VirtualMachine -Property Name,Config.Hardware.Device -Filter @{"Name" = "^${strTargetVMName}$"}
## get the NIC device (further operations assume that this VM has only one NIC)
$deviceNIC = $viewTargetVM.Config.Hardware.Device | Where-Object {$_ -is [VMware.Vim.VirtualEthernetCard]}
## set the MAC address to the specified value
$deviceNIC.MacAddress = $strNewMACAddr
## set the MAC address type to manual
$deviceNIC.addressType = "Manual"

## create the new VMConfigSpec
$specNewVMConfig = New-Object VMware.Vim.VirtualMachineConfigSpec -Property @{
   ## setup the deviceChange object
   deviceChange = New-Object VMware.Vim.VirtualDeviceConfigSpec -Property @{
       ## the kind of operation, from the given enumeration
       operation = "edit"
       ## the device to change, with the desired settings
       device = $deviceNIC
   } ## end New-Object
} ## end New-Object

## reconfigure the "clone" VM
$viewTargetVM.ReconfigVM($specNewVMConfig)


The ReconfigVM() method does not care if the new MAC address is a valid VMware MAC address. It does not even seem to care if the new MAC address string is a valid hex MAC address. So, use with care -- one would not want to break networking on their precious VM by setting its NIC to have an invalid MAC address, or a duplicate, etc.

Other info: the valid VMware OUIs can be found many places, like at the IEEE.org Public Listing search site.


Update 25 Nov 2011:
Do note that changing the MAC address of a VM NIC to something outside of the "accepted" range will cause other PowerCLI cmdlets and the vSphere client to report that the NIC's MAC is "not in the valid range", and may prevent further operations on that VM's NIC with those tools.  Thank-you to the anonymous commenter here and to LucD (in the VMware Communities thread at http://communities.vmware.com/message/1869800#1869800) for highlighting this point.


Update 31 Dec 2011:
The -Filter parameter for the Get-View call on line 10 was using a regular expression that had the possibility of returning more than one VM View object.  I have updated the regular expression to only match VMs of exactly the given name.  Thanks go to Joep Piscaer for pointing out the issue.  The code in this post has been updated.

31 January 2011

Enable SIOC On All Datastores

After reading what Duncan at Yellow-Bricks.com had to say about enabling SIOC (Storage I/O Control) on all datastores here and here, we were inspired to write a script to avoid having to do it by hand on the 370 datastores in our environment. Of course, LucD already has a SIOC script but it was written before VMware fixed the StorageResourceManager bug in PowerCLI 4.1.1 that he had to use a workaround for. LucD's script allows you to both query and set values--we just wanted one that enabled SIOC on all datastores that supported it as quickly as possible. In addition, we wanted to schedule it (this script is not schedule-ready) to run on a regular basis so that it can enable SIOC on newly created datastores without admins having to worry about it.

This is what we ended up with:
## Script function: Enable SIOC (Storage I/O Control) on all datastores that support it and do not already have it enabled.
## Author: vNugglets.com


## Set up specification to enable SIOC
$spec = New-Object VMware.Vim.StorageIORMConfigSpec
$spec.Enabled = $True

## Create an array of datastore view objects that are SIOC capable and that do not already have it enabled
$arrSIOCableDatastores = @(Get-View -ViewType Datastore -Property Capability.StorageIORMSupported,IormConfiguration,Name -Filter @{"Capability.StorageIORMSupported" = "True"; "IormConfiguration.Enabled" = "[^(True)]"})

## Loop through each SIOC-ready datastore view (if any), then on the first run grab the "view" of the StorageResourceManager (this is needed to use the ConfigureDatastoreIORM_Task method to enable/disable SIOC) and finally, enable SIOC on the datastore
$arrSIOCableDatastores | ForEach-Object -Begin {$viewStorageRM = Get-View -Id "StorageResourceManager-StorageResourceManager"} {
$viewStorageRM.ConfigureDatastoreIORM_Task($_.MoRef, $spec)
}

Of course, if you want to disable SIOC on all of your datastores you need to only modify two pieces of code:

Line 7: Simply change $True to $False.
Line 10: Remove the caret (^) on the second part of the filter so it finds datastores that already have SIOC enabled.

Depending on what you are looking for, you may choose LucD's script over this one, or maybe you'll use both since his contains two functions and gives you more granular control on which datastores to enable/disable SIOC.

19 January 2011

Re-Registering VMs Removed from Inventory

The cache module on the array controller failed on a host, and ESXi was wasted. The host stopped responding in vCenter, but did not fail hard enough to trigger HA. Most of the VMs on it were still up and their OSes were responsive to ping requests. Eventually we had to remove the host from vCenter. At that point, all VMs registered on it also are removed from inventory.

I grabbed the info for the guests that were in "disconnected" state _BEFORE_ removing the affected host from vCenter, then removed the host, then re-registered VMs and started them as such:
## Script function:  re-register VMs after host removal from vCenter
## Author: vNugglets.com
## Jan 2011

## cluster in which troubled host resides
$strAffectedCluster = "myCluster"

## get array BEFORE removing bad host from vCenter!!! Else, will not be able to get this info in this manner, since disconnected VMs are removed from inventory at that time, too

## get an array of VM .Net View objects where their OverallStatus is "gray"; optimized for low memory consumption
$arrVMsToHandle = Get-View -ViewType VirtualMachine -SearchRoot (Get-Cluster $strAffectedCluster | Get-View).MoRef -Property Name,Parent,LayoutEx.File,Summary.OverallStatus -Filter @{"Summary.OverallStatus" = "gray"}

## get the hosts in the cluster, to be used for re-registering VMs
$arrHostsInAffectedCluster = Get-Cluster $strAffectedCluster | Get-VMHost

## for each set of VM name, parent folder ID, and .VMX file path, add VM to inventory via New-VM cmdlet; add to random host within the cluster
$arrVMsToHandle | Select Name, Parent, @{n="vmxFilePath"; e={($_.layoutex.File | ? {$_.name -like "*.vmx"}).name}} | %{
New-VM -VMFilePath $_.vmxFilePath -VMHost ($arrHostsInAffectedCluster | Get-Random) -Location (Get-VIObjectByVIView $_.Parent) -RunAsync
} ## end ForEach-Object

## start all of the VMs (may cause a boot storm if huge number of VMs -- split up by groups of x VMs as necessary)
$arrVMsToHandle | %{Start-VM $_.Name -RunAsync}

You could also have put the "Start-VM" call inside of the "ForEach-Object" loop (say, before line 19), but that makes the process more linear than quick. This would require the removal of the "-RunAsync" from the "New-VM" call on line 18.

This could come in handy for you someday. Enjoy!

07 April 2010

Working with multiple default servers

Connect-VIServer has a new behavior/feature as of the 4.0 Update 1 release: "Working with multiple default servers." So what? So this: connecting to hosts that are not managed via a vCenter instance and taking actions against inventory items.
"vSphere PowerCLI supports working with multiple default servers. If you select this option, every time when you connect to a different server using Connect-VIServer, the new server connection is stored in an array variable together with the previously connected servers, unless the -NotDefault parameter is set. This variable is named $DefaultVIServers and its initial value is an empty array. When you run a cmdlet and the target servers cannot be determined from the specified parameters, the cmdlet runs against all servers stored in the array variable. To remove a server from the $DefaultVIServers variable, you can either use Disconnect-Server to close all active connections to the server, or modify the value of $DefaultVIServers manually."
(from http://www.vmware.com/support/developer/windowstoolkit/wintk40u1/html/Connect-VIServer.html)

An example of the new behavior in action:
PS C:\> Connect-VIServer vcenter4.local
WARNING: There were one or more problems with the server certificate:
* The X509 chain could not be built up to the root certificate.

Login Information
Please supply user credentials.
User: vNuggsAdmin
Password for user vNuggsAdmin: ***********

Name Port User
---- ---- ----
vcenter4.local 443 vNuggsAdmin


PS C:\> Connect-VIServer standAloneHost0.local
WARNING: There were one or more problems with the server certificate:
* The X509 chain could not be built up to the root certificate.
* The certificate's CN name does not match the passed value.

Login Information
Please supply user credentials.
User: root
Password for user root: ***********

Working with multiple default servers?

Select [Y] if you want to work with more than one default servers. In this case, every time when you connect to a different server using Connect-VIServer, the new server connection is stored in an array variable together with the previously connected servers. When you run a cmdlet and the target servers cannot be determined from the specified parameters, the cmdlet runs against all servers stored in the array variable.
Select [N] if you want to work with a single default server. In this case, when you run a cmdlet and the target servers cannot be determined from the specified parameters, the cmdlet runs against the last connected server.
You can change your preference at any time using the DefaultServerMode parameter of Set-PowerCLIConfiguration.
[Y] Yes [N] No [S] Suspend [?] Help (default is "Y"): Y

Name Port User
---- ---- ----
standAloneHost0.local 443 root


PS C:\> $DefaultVIServers
Name Port User
---- ---- ----
standAloneHost0.local 443 root
vcenter4.local 443 vNuggsAdmin


PS C:\>
At this point, running a cmdlet that is not VIServer specific (as determined by the specified parameters) runs against all the servers to which this session is connected, namely "standAloneHost0.local" and "vcenter4.local." So, you can now act on all items in these servers' inventory.

A few notes:
  • If connecting to a vCenter instance and a host which is managed by that vCenter instance, cmdlets will return the items in the host's inventory twice: once when the cmdlet runs against the vCenter server, and once when the cmdlet runs against the host itself, since both will be in the $DefaultVIServers array.
  • The help for "Connect-VIServer" cmdlet mentions that "Working with a single default server is deprecated and will be removed in a future release.", so the time is now to familiarize one's self with multiple VIServer behavior.
  • As the message in the Powershell session above reads, one can adjust the single/multiple VIServer behavior using the "Set-PowerCLIConfiguration" cmdlet, for which the help is straightforward.

Additionally, as you will probably notice, the title bar of your PowerShell session indicate that you are connected to X number of servers, along with their names.

A simple example of the usefulness of this new feature: connect to several non-vCenter managed VIServers and act against all items associated with said hosts. We are using a Powershell Credential object in this example so as not to need to enter credentials for each host (which assumes that there is a common account across them all) and to remove the need to specify credentials on the command-line.
Connect to serveral hosts
...
PS C:\> $credRootUser = Get-Credential -Credential root

Windows PowerShell Credential Request
Enter your credentials.
Password for user root: *************


PS C:\> Connect-VIServer -Credential $credRootUser -Server hst0,host1,aHost23,hst45

Name Port User
---- ---- ----
hst0 443 root
host1 443 root
aHost23 443 root
hst45 443 root


PS C:\> Get-VM -server hst0

Name PowerState Num CPUs Memory (MB)
---- ---------- -------- -----------
win7test PoweredOff 1 1024


PS C:\> Get-VM | Format-Table Name, PowerState, NumCPU, MemoryMB, @{Label="HostName"; Expression={$_.Host.Name}}

Name PowerState Num CPUs Memory (MB) HostName
---- ---------- -------- ----------- --------
win7test PoweredOff 1 2048 hst0
dubuntu0 PoweredOn 1 768 host1
dubuntu8 PoweredOff 1 1024 host1
vMA4 PoweredOff 1 512 aHost23
2k8test PoweredOff 1 1024 hst45


PS C:\> Get-VM | Where-Object {$_.PowerState -eq "PoweredOff"} | Start-VM
...
Now connected to each host, one could perform whatever task(s) they desired. In this example, we got the VMs for one host, got the VMs for all hosts to which we were connected, and then started all powered-off VMs. Another example would be to gather VMNIC info as described in the "Find Busted VMNICs" post here at vNugglets.com. Yet another example would be, as Glenn at get-admin.com points out in his "Find vCenter without vCenter" post, finding the VMHost on which the vCenter VM resides in the case that vCenter services (or the VM) are not responding properly (and, so, one cannot connect to vCenter to determine this info).
This feature is most useful for managing hosts that are not managed via a vCenter instance. Once the given hosts are connected to a vCenter, admins can run their commands against said vCenter as per usual.

23 December 2009

Find Busted VMNICs

So, while we mentioned this would be a "low-volume blog", we didn't think we'd get our domain expiration notice before the first vNugglet hatched. Anyway, a problem we've run into in the past are VMNICs that suddenly have no link for whatever reason--switch upgrades, random user error, etc.

The following script will search the connected vCenter Server(s) to find any VMNICs that are actively being used in a standard vSwitch and also have no link (0Mbps).

## Script function: Lists all VMNICs that have no link and that are connected to a vSwitch (sorted by corresponding host).
## Author: vNugglets.com
## Usage: Connect to vCenter, run script interactively.


## Get all hosts and sort by name
Get-VMHost | Sort-Object -Property Name | ForEach-Object {
## (Re)Initialize array of failed VMNICs
$arrFailedVMNICsInUse = @()

## Get current host's Name, NICs, and vSwitches objects
$objCurrentHostNICs = Get-VMHostNetworkAdapter -VMHost $_
$objCurrentHostVSwitches = Get-VirtualSwitch -VMHost $_

## For each failed NIC, if it is attached to a vSwitch, add it to the failed VMNICs array
$objCurrentHostNICs | Where-Object {$_.BitRatePerSec -eq 0} | ForEach-Object {
$objCurrentFailedNIC = $_
$arrValidVMNICs = $objCurrentHostVSwitches | ForEach-Object {$_.Nic}
if ($arrValidVMNICs -match $objCurrentFailedNIC.DeviceName) {
$arrFailedVMNICsInUse += $objCurrentFailedNIC
} ## end if
} ## end inner ForEach-Object

## If there is at least one failed VMNIC, display the hostname it is associated with followed by the list of failed VMNICs
if ($arrFailedVMNICsInUse.Count -gt 0) {
$_.Name + ":"
$arrFailedVMNICsInUse | ForEach-Object {
"`t" + $_.DeviceName
} ## end ForEach-Object
Write-Host ""
} ## end if
} ## end outer ForEach-Object

That's it! Hopefully this is helpful to someone else.


*UPDATE - 19 January 2011: Updated script due to the following properties that are now deprecated.

PS C:\> .\findBustedVMnics.ps1
WARNING: 'PhysicalNic' property is obsolete. Use 'Get-VMHostNetworkAdapter' cmdlet instead.
WARNING: 'VirtualSwitch' property is obsolete. Use 'Get-VirtualSwitch' cmdlet instead.

...

08 February 2009

vNugglets Objectives

Welcome to vNugglets. Right now vNuggs is comprised of two IT masters that needed a place to share some scripts/howto's/whatever. AC started off washing dishes at Chubby's Fish & Steak. Then during and after attending college he worked as a Systems Administrator at a small shop, followed by a Fortune 500 financial company. AC currently works at a consulting company where he specializes in VMware administration/infrastructure. D-Day started his illustrious career by slinging pizza pizzas! at Little Caesar's. Then during college he too worked at a small shop as a Systems Administrator (doing everything). D-Day currently works at the same consulting company as AC, specializing in VMware administration/automation.

This blog will contain useful (hopefully) scripts, howto's, and miscellany. While there are plenty of scripts/howto's out there, vNuggs will focus on providing well-written/documented material, not just "it works, that's good enough" efforts. This includes:
  • providing example usage/output
  • good, readable, commented code
  • trying to stay away from most aliases for the sake of the readers
pertaining to:
  • VMware (generally PowerShell)
  • Sys Admin tasks
  • general automation
In focusing on these items, vNugglets.com will likely be a low-volume blog, not one that will rigorously cover current virtualization/technology news. There are excellent sources for that sort of info (and more) on our blog list.