Brokenhaze

The thoughts of a SysAdmin

Archive for the ‘Powershell’ Category

Updating VMWare Clusters without DRS

without comments

One of the pieces of the vSphere Enterprise license is DRS. Especially the ability to use DRS to one-click upgrade/update a cluster. If you don’t know what DRS is, the short version is that it is a product you get with the Enterprise license which allows you to have vSphere move VMs around intelligently. One of the added bonuses you get is the ability to evacuate a VM. When you combine that with vSphere Update Manager you get a one-click and an hour later you’re done upgrade of your cluster.

Unfortunately, when that is the one feature you would actually use in the Enterprise edition it doesn’t make financial sense to pay that premium. The question now is “What do you do to make your life easier than manually moving thing?”

The answer is you go and grab Power CLI and write a script! I’ve got one started — Put github link here when done — and I’ll go through some of the details of it here.

First, what are the things that it can do?

  • Migrate running VMs to the other hosts in the cluster
  • Enter and Exit Maintenance mode
  • Move the VMs from where they went, back to the host that was drained

Next, what still needs to be added?

  • Intelligent migrations (it just blasts the vms around blindly now)
  • automatically roll through the whole cluster
  • Everything I haven’t thought of …

The most interesting function here is the evac-host function. This is actually the meat and bones of this script.

function evac-host()
{
    Param(
        [Parameter(Mandatory=$true)]
        [string] $ClusterName,
        [Parameter(Mandatory=$true)]
        [string] $vHost
    )

    $AliveHosts = Get-VMHost -Location $ClusterName

    $toHosts = @()

    foreach ($h in $AliveHosts)
    {
        if($h.Name -ne $vHost)
        {

            $toHosts += $h.Name
        }
    }

    $svr = 0
    $vms = get-vm -Location $vHost | where {$_.PowerState -ne "PoweredOff"}
    $m_loc = @{}

    if ($vms.Count -gt 0)
    {

        foreach ($v in $vms)
        {
            Move-VM -vm $v.Name -Destination $toHosts[$svr]
            $m_loc.Add($v.Name, $toHosts[$svr])
    q
            if($svr -ne $toHosts.Length -1 )
            {
                $svr++
            }
            else
            {
                $svr = 0
            }

        }

        $m_loc.GetEnumerator() | Sort-Object Name | export-csv C:\Users\gbeech.STACKEXCHANGE\locations.csv
        $m_loc

    }
    else
    {
        write-host "No Powered on VMs on the Host"
    }
}

The 10,000 foot view is that this function takes a Cluster Name (wild cards are acceptable), and a ESX host name as arguments. Then it goes through and moves every vm off the host. As it does this, it writes the names and locations they got sent to to a Hashtable, then writes those results out to a csv just in case. It also returns the hashtable so we can work with it later, avoiding having to read the csv back in.

Written by George Beech

June 16th, 2014 at 7:41 pm

Fun With PowerShell, WS-MAN, and Dell Servers

with 8 comments

Recently I’ve been playing with using the WS-MAN protocol to gather information (and eventually run updates) on our Dell servers. It has actually been a fairly insteresting project after I got through the pretty high learning curve to get started using WS-MAN.

First, what is WS-MAN? It’s a management standard developed by the DTMF. What it really boils down to is giving us the ability to access and manipulate CIM providers via HTTP calls.

One of the interesting things Dell did with their systems in the past two generations (Gen 11 and 12) is to add something they call the Life Cycle controller. They did not really make much information known on what you can do with it, or even how to really use it.

Recently I have been exploring what you can do with the Life Cycle Controller. And, quite honestly, you can do a ton of good stuff with it. Everything from getting system information to setting boot options, all the way up to updating all of the firmware on your box. This is all done through the WS-MAN Protocol.

First I would suggest doing some reading so you can get the basic concepts of WS-MAN.

Phew, got through all that?

Lets start off with a nice code snippet that I have been working on, and then step through what it is doing.

$DELL_IDS = @{
    "20137" = "DRAC";
    "18980" = "LCC";
    "25227" = "DRAC";
    "28897" = "LCC";
    "159" = "BIOS"
    }

$pass = ConvertTo-SecureString "ThisIsMyPassword" -AsPlainText -Force
$creds = new-object System.Management.Automation.PSCredential ("root", $pass)
$wsSession = New-WSManSessionOption -SkipCACheck -SkipCNCheck

$svc_details = @{}

$base_subnet = "192.168.99."
$addrs = @(1..254)
foreach ($ip in $addrs)
{
    $base_subnet + $ip
    $s = [System.Net.Dns]::GetHostByAddress($base_subnet+$ip).HostName
<code>$fw_info = Get-WSManInstance 'cimv2/root/dcim/DCIM_SoftwareIdentity' -Enumerate -ConnectionURI https://$s/wsman -SessionOption $wsSession -Authentication basic -Credential $creds
$svr_info = Get-WSManInstance 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/root/dcim/DCIM_SystemView' -Enumerate -ConnectionURI https://$s/wsman -SessionOption $wsSession -Authentication basic -Credential $creds

$svc_details.Add($s, @{})
if($svr_info -eq $null)
{
    $svc_details[$s].Add(&quot;Generation&quot;, &quot;unknown probably 11G&quot;)
}
else
{
    $svc_details[$s].Add(&quot;Generation&quot;, $svr_info.SystemGeneration.Split(&quot; &quot;)[0])
}
foreach ($com in $fw_info)
{

    $DELL_IDS.ContainsKey($com.ComponentID)
    if($DELL_IDS.ContainsKey($com.ComponentID))
    {
        #need to see if I can update this to account for the different
        #way drac6 and 7's format this string
        $inst_state = $com.InstanceID.Split(&quot;#&quot;)[0].Split(&quot;:&quot;)[1]
        if (($inst_state -ne &quot;PREVIOUS&quot;) -AND ($inst_state -ne &quot;AVAILABLE&quot;))
        {
            $svc_details[$s].Add($DELL_IDS[$com.ComponentID], $com.VersionString)
        }
    }
}

}

The first part of this code is simply a hash table of dell component IDs and an associated easy-to-remember name matching them with the component. How did I get those? Well I queried the cimv2/root/dcim/DCIM_SoftwareIdentity namespace and parsed the output by hand to grab those IDs. They match up to BIOS, LCC v1, LCC v2, iDRAC 6 and iDRAC 7.

$pass = ConvertTo-SecureString "ThisIsMyPassword" -AsPlainText -Force
$creds = new-object System.Management.Automation.PSCredential ("root", $pass)
$wsSession = New-WSManSessionOption -SkipCACheck -SkipCNCheck

This next section of code sets up our enviroment for Get-WSManInstance. First we need to convert our plaintext password into a secure string, then create a PSCredential object to use later so we don’t have to enter our username and password over and over. Finally, we setup a new WS-MAN session options object so that it doesn’t error out on the self signed certificates we are using. If you are using fully trusted certificates on your dracs you can skip this step and not specify the -SessionOption $wsSession flag later.

$fw_info = Get-WSManInstance 'cimv2/root/dcim/DCIM_SoftwareIdentity' -Enumerate -ConnectionURI https://$s/wsman -SessionOption $wsSession -Authentication basic -Credential $creds
$svr_info = Get-WSManInstance 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/root/dcim/DCIM_SystemView' -Enumerate -ConnectionURI https://$s/wsman -SessionOption $wsSession -Authentication basic -Credential $creds

Note You can specify either the DCIM Path or the schema, I’m showing both ways here. For the $svr_info variable 'cimv2/root/dcim/DCIM_SystemView' would also work.

Now, we move on to the meat of what we are doing. These two lines grab the system information that we want to parse. The $fw_info contains an XML object that returns all of the install components as exposed by the DCIM_SoftwareIdentity endpoint, and the $svr_info variable contains an XML object that has some interesting system information – such as Server Generation, Express Service Code, Service Tag, and so on. I use these two pieces of information to parse out the Generation, DRAC, BIOS, and LCC firmware versions.

#need to see if I can update this to account for the different</h1>
#way drac6 and 7's format this string</h1>

$inst_state = $com.InstanceID.Split("#")[0].Split(":")[1]
if (($inst_state -ne "PREVIOUS") -AND ($inst_state -ne "AVAILABLE"))
    {
        svc_details[$s].Add($DELL_IDS[$com.ComponentID], $com.VersionString)
    }

One last tricky bit. When you get back the versions that are installed, you will actually have two different versions. Once that is the active version and one that is the rollback version. Unfortunately you need to parse string to figure that out. And different DRACs use different string formats.

  • Drac6: DCIM:INSTALLED:PCI:14E4:1639:0236:1028:5.0.13
  • Drac7: DCIM:INSTALLED#802__DriverPack.Embedded.1:LC.Embedded.1

Once I have this information in my two-dimensional array I can create reports and manipulate the information to tell me exactly what version each of my servers is at.

Sweet! Step one to automating the update of our firmware complete! Next up figure out how to automate the deployment and installation of new firmware.

Written by George Beech

February 26th, 2014 at 10:17 pm

Full Featured Email using powershell

with 4 comments

Note: This is copied (thank you wayback machine) from a previous incarnation of my blog. I was very sad when I realized this post was gone, then very happy when wayback machine had it

A little while ago i spent a lot of time trying to figure out how to send an email that fulfilled the following requirements:
Multiple Recipients
Attached File
Meaningful Subject
Sent without an external executable
Thanks to powershell’s ability to access .Net libraries, this is a fairly simple, however not quite so well explained – at least that i could find – process.
Let us start simply, with the basic SMTPClient Object, and setting the Server Variables, settings, etc. The most basic way to configure your server is to simple create a.Net System.Net.Mail.smtpClient object, and set the email server hostname, taking the defaults.

$SMTPClient = new-object System.Net.Mail.smtpClient
$SMTPClient.host = ""

Simple, right? Then lets get a little bit more complicated. Lets send an email to a host that requires authentication. To do this, we are going to need another .Net object: The NetworkCredential Object from there we can set the domain, user, and password, set these values on our SMTPClient.

$Credentials = new-object System.Net.networkCredential
$Credentials.domain = ""
$Credentials.UserName = ""
$Credentials.Password = ""
$SMTPClient.Credentials = $Credentials

The above code is fairly self explainitory, if you were to display $SMTPClient (Just type $SMTPClient on the console) before and after when you set the Credentials property you can see that is has been set. There are a few other options that you can set on the SMTPClient object, including Port, and SSL to see all that you can do issue

$SMTPClient | gm

Now, we have the Client setup, we want to configure message that we want to send. This will include setting up the Subject, To, From, and Body. What I do to send mail is use an overload of the SMTPClient object that lets us use System.Net.Mail.MailMessage to send the mail, it gives you ALOT more control over your message. First lets get ourselves another .Net Object, the MailMessage Object.

$MailMessage = new-object System.Net.Mail.MailMessage

The next thing I want to do is Setup my addresses The To: and From: addresses are yet another .Net object System.Net.Mail.MailAddress. Here is how you set those up, it is very simple and all you really need is the constructor which is overloaded. You can setup your address in the following two ways.

$Address = new-object System.Net.Mail.MailAddress("[email protected]")
$Address = new-object System.Net.Mail.MailAddress("[email protected]", "Display Name")

Either was you want, you need to create at least two one for your sender, one for your recipient. After we get those options out of the way, we just need to do the final setup on our message. That will include setting up the Subject, Body, and feeding it the To: and From: Addresses we already created.

$MailMessage.Subject = "Hello World!"
$MailMessage.Body = "String Body"
$MailMessage.Sender = $Sender
$MailMessage.From = $Sender
$MailMessage.To.add($Recipient)

Why, you ask, am I setting the $Senter twice, the Sender property is the Displayed From Address, while the From property is the repy-to address. You can send an email with an html body just put the code in your Body string, all you have to do is specify the boolean IsHtmlBody property.

$MailMessage.IsHtmlBody = $true

Now, how about adding an attachment. This is done very simply with another MailMessage property set to the .Net Attachements object

$Attachment = new-object System.Net.Mail.Attachment("")
$MailMessage.Attachements.Add($Attachment)

That is all there is to that. There is one last thing we have to do to get this mail off and on it’s way. Simply. Send It!

$SMTPClient.Send($MailMessage)

Written by George Beech

March 25th, 2011 at 12:22 pm

Posted in Powershell