Friday, May 1, 2015

Change iDRAC DNS Name Dynamically

Today I'd like to share a quick script I threw together to dynamically update the iDRAC DNS Name using the Hostname reported to the iDRAC from OMSA or iDRAC Service Module installed on the host operating system.

Here's the scenario:  We have a customer running running Ubuntu server, but OpenManage Server Administrator (OMSA) and OpenManage Essentials (OME) aren't validated with Ubuntu.  While there is a Debian port of OMSA, a critical service for system updates (dsm_sa_shrsvc part of srvadmin-cm package) is not available.  We have also found that if we discover both the host and the iDRAC that they correlate, but system updates are not displayed due to the missing inventory.

The decision was made to go completely out-of-band, but the iDRAC DNS names were all configured with the default idrac-SVCTAG convention.  This makes it very difficult to tell which iDRAC belongs to which server.  The solution?  Let's change it to <hostname>-iDRAC instead!

Since I've been experimenting with manipulating the iDRAC and Lifecycle Controller via PowerShell using WS-Man for a while now, I decided I would put the necessary pieces together to create a fast and streamlined method to: 

1. Query the iDRAC for the OS hostname and make sure it's not null (updated by OMSA services)
2. Query the iDRAC for current iDRAC hostname
3. Make sure the current iDRAC hostname is default convention
4. Change the hostname to <hostname>-iDRAC
5. Commit the change
6. Pull the new iDRAC hostname after the configuration change

I already have a couple helper functions I've written up (and actually threw in a .psm1 and installed them as cmdlets on my system) that will help out with this.

The first one is "New-iDRACSession":

Function New-iDRACSession {
param (
[Parameter (Mandatory = $true)][string] $racUser,
[Parameter (Mandatory = $true)][string] $racPass,
[Parameter (Mandatory = $true)][string] $racIP
)

# WSMAN Cmdlet Session
$secPass = ConvertTo-SecureString "$racPass" -AsPlainText -Force
$credentials = New-Object -typename System.Management.Automation.PSCredential -argumentlist ("$racUser", $secPass)
$wsmanOptions = New-WSManSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$wsmanArgs = @{Authentication="Basic";ConnectionURI="https://$racIP/wsman";SessionOption=$wsmanOptions;Credential=$credentials}

# Test Session
$SystemView = Get-WSManInstance -Enumerate @wsmanArgs -ResourceURI "http://schemas.dell.com/wbem/wscim/1/cim-schema/2/DCIM_SystemView"
if ($SystemView.Manufacturer -ne $null) {
$wsmanArgs
}
else {
throw "Unable to connect to the iDRAC, check iDRAC IP and/or credentials and try again."
}
}

This function allows you to create a session that you can use with the other helper function that we'll use to query the iDRAC.  You'll notice that this function is basically a wrapper to create a credential for the PowerShell wsman cmdlets, though this does validate that you can connect to the iDRAC before it returns the credential object.

The next helper function is "Get-iDRACClass":

Function Get-iDRACClass {
param (
[Parameter (Mandatory = $true)][hashtable] $Session,
[Parameter (Mandatory = $true)][string] $Class,
[Parameter (Mandatory = $false)][string] $Instance
)
if ($Instance -eq [String]::Empty) {
Get-WSManInstance @Session -Enumerate -ResourceURI "http://schemas.dell.com/wbem/wscim/1/cim-schema/2/$Class"
}
if ($Instance -ne [String]::Empty) {
$InstanceID = @{InstanceID="$Instance"}
Get-WSManInstance @Session -ResourceURI "http://schemas.dell.com/wbem/wscim/1/cim-schema/2/$ClassName" -SelectorSet $InstanceID
}
}

This function uses the session you created (and saved to a variable) to query iDRAC Classes with just their name, and not the entire resource URI/URL.

Now that we have our helper functions, we can write the function that will actually orchestrate all of the work:

Function Set-DNSRacNameFromHostname {
param (
[Parameter (Mandatory = $true)]$racIP,
[Parameter (Mandatory = $true)]$racUser,
[Parameter (Mandatory = $true)]$racPass
)
# Create session and enum classes
$idrac = New-iDRACSession -racUser $racUser -racPass $racPass -racIP $racIP
$idraccardview = Get-iDRACClass -Session $idrac -Class DCIM_iDRACCardView
$systemview = Get-iDRACClass -Session $idrac -Class DCIM_SystemView
if ($idraccardview.DNSRacName -like "idrac-*" -and $systemview.Hostname -ne "") {
$iDRACCardService = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/DCIM_iDRACCardService"
$selectFilter = @{SystemCreationClassName="DCIM_ComputerSystem";CreationClassName="DCIM_iDRACCardService";SystemName="DCIM:ComputerSystem";Name="DCIM:iDRACCardService"}
$hostname = @{Target="iDRAC.Embedded.1";AttributeName="NIC.1#DNSRacName";AttributeValue="$($systemview.hostname)-iDRAC"}
$commitSet = @{Target="iDRAC.Embedded.1";ScheduledStartTime="TIME_NOW"}
Invoke-WSManAction @idrac -ResourceURI $iDRACCardService -SelectorSet $selectFilter -Action SetAttribute -ValueSet $hostname
Invoke-WSManAction @idrac -ResourceURI $iDRACCardService -SelectorSet $selectFilter -Action CreateTargetedConfigJob -ValueSet $commitSet
}
elseif ($systemview.Hostname -eq "") {
Write-Host "There is no reported hostname... aborting!"
#throw "There is no reported hostname... aborting!"
}
elseif ($idraccardview.DNSRacName -notlike "idrac-*") {
Write-Host "The iDRAC has been changed from `"idrac-SVCTAG`" naming convention... aborting!"
#throw "The iDRAC has been changed from `"idrac-SVCTAG`" naming convention... aborting!"
}
else {
throw "Unhandled exception"
}
sleep 1
Write-Host "New DNSRacName set to $((Get-iDRACClass -Session $idrac -Class DCIM_iDRACCardView).DNSRacName)"
}

Stepping through the function, we go ahead and establish our iDRAC credential, then we go ahead and enumerate the two classes where we can quickly grab the two properties we want.  After that, we make sure that we're using the default iDRAC naming convention, and that the hostname isn't empty before doing any work.

We'll be performing configuration changes with Invoke-WSManAction, and I like to lay everything out in variables so those commands are a bit easier to read.  We first define our resourceURI where the methods we'll be invoking live ($iDRACCardService).  Then we provide the values for -SelectorSet ($selectFilter).  Now we need the value sets.  In one, we're changing the hostname, the other we're committing.  $hostname's attribute value handles the work of changing the name to <hostname>-iDRAC with substitution (AttributeValue="$($systemview.hostname)-iDRAC"), while $commitSet is just handling targeting and time requirements.  Finally we have our two Invoke-WSManAction commands for SetAttribute and CreateTargetedConfigJob that are run, then we wait a second and return the newly configured iDRAC Hostname that is on the iDRAC after configuration.

This script can easily be run through a foreach loop to target multiple systems, or you can use OME and the Generic Command line Remote Task type to apply to multiple discovered iDRACs.  This also gives you job scheduling/management without having to write up a PowerShell function to manage it for you.

To do this in OME, you'd need to: 
1. Set-ExecutionPolicy Unrestricted (if you haven't already)
2. Create your command line task
3. Set to Generic Command
4. Enter "powershell.exe" for Command
5. Enter the path to script and arguments + tokens for Arguments (c:\test\Set-DNSRacNameFromHostname.ps1 -racIP $RAC_IP -racUser $USERNAME -racPass $PASSWORD)
6. Check Output to file and specify a location for script output to be logged (you'll have multiple instances of the script running, so be sure to check Append)
7. Enter the iDRAC username in $USERNAME field
8. Enter the iDRAC password in $PASSWORD field


9. Select your iDRACs in Task Target



10. Set to Run now or schedule a time for execution
11. Enter account credentials (account needs to have local administrator rights on the OME server)


I'd say that about wraps up this episode of tinkering.  For easy reference I am adding the script below: