September 2008 - Posts

In yesterday's EMO (Exchange Messaging Outlook eZine, subscribe at http://www.slipstick.com/emo) I had an article named It's All About The IOPS, Silly! which discussed the impact of RAID-1 and RAID-5 arrays on total IOPS calculations.

However, space didn't allow me to talk about SANs (Storage Area Networks) in regards to IOPS.

Where Exchange is concerned (and SQL Server as well, although that isn't our focus here), you do not want to be sharing your "disk group" or "volume group" or "virtual array" (or whatever your SAN software calls it) with any other application - unless you've performance tested it in the worst case.

SANs allocate storage in terms of LUNs - Logical Unit Numbers. For a given server, a LUN is allocated to that server (note that in the case of clusters, quorum disk may be visible to multiple servers at one time) and only one server may have write access to that LUN at a time. A LUN may, or may not, be tied to a particular disk array or disk set contained within the physical hardware of the SAN.

Many types of SAN software support a concept known as "LUN Stacking". In this case, an array (be it RAID-1, RAID-5, RAID-10, whatever) has multiple logical stripes of the disk where multiple LUNs are allocated on the same array. Arguably, this is what SANs are all about - allowing you to control storage logically instead of drive-by-drive. And, for many (perhaps most) applications this makes sense.

However, while the BEST CASE SCENARIO says that the IOPS available for a LUN is the maximum IOPS available for that disk set, the WORST CASE scenario says that the IOPS available for a LUN is equal to the number of accessors (servers) that are using that physical array.

So, for example, if you have five servers hitting a RAID-1 array via different LUNS, the best case performance scenario says that you can have the maxiumum IOPS available to the hardware available to each server. The worst case is IOPS / 5.

For example, if your RAID-1 group has a calculated IOPS of 250, one server could potentially benefit from having a total IOPS of 250 available to it. However, if all the servers are hitting the disk, the performance would be more like 50 IOPS. While for many small and medium businesses, an IOPS of 250 is probably sufficient to meet their Exchange needs - only the smallest of companies can get adequate performance on 50 IOPS total.

Another reason you want to avoid sharing LUNs is due to the usage profiles of the various Exchange needs.

Database access (which includes queues, mailbox stores, and public folder stores) is completely random. The creation of log files (transaction log files, message tracking logfiles, and protocol log files) is completely sequential. If you share LUNs between different usage profiles, then your performance will suffer (especially for those needs which are optimized for sequential access).

Conclusion: Just Say No! Don't let your SAN guys tell you what kind of storage performance requirements Exchange has. You need to be prepared to tell them!

Until next time...

As always, if there are items you would like me to talk about, please drop me a line and let me know!

Posted by michael | 3 comment(s)
Filed under: ,

I'm working with a new client and this client is a "wholly owned subsidiary" of another company and is getting ready to be spun off as their own company.

One of the things we are doing is configuring their network to be standalone and not part of the corporate Active Directory. That involves creating users, group, moving computers, installing a new Exchange organization, etc. etc.

But sometimes it's the simple things that bite you...

We'd gotten an export of the OU for the subsidiary out of corporate in a CSV file - every object, every attribute. That was a mess in-and-of itself, but at least all the data was there.

So we started importing the data. And it broke.

Huh? We'd done this before and no problem. What was going on here?

Like I said - the simple things...

This set of users and groups were using embedded groups. Sometimes up to five levels deep. (That is, a group within a group within a group within a group within a group.)

Well, you can't assign a group membership to a group that doesn't exist! Seems obvious, in retrospect.

The solution? Go through and harvest all groups FIRST - just create the groups in Active Directory. Then, go back through and assign membership to the groups.

Here is a somewhat sanitized version of the code we used. On the first pass, it creates groups. On the second pass, it will assign membership to the groups. This shows a number of PowerShell techniques in group management - including creating groups, checking for membership in a group, and adding members to a group.

Also note: there are several places where this script could be optimized. But I was more interested in clarity (since, as a consultant, I won't be there forever!).

I hope you find this useful for your scripts!

set-variable ADS_GROUP_TYPE_DOMAIN_LOCALGROUP 1          
set-variable ADS_GROUP_TYPE_GLOBAL_GROUP      2          
set-variable ADS_GROUP_TYPE_LOCAL_GROUP       4          
set-variable ADS_GROUP_TYPE_UNIVERSAL_GROUP   8          
set-variable ADS_GROUP_TYPE_SECURITY_ENABLED -2147483648 

$groupType = $ADS_GROUP_TYPE_GLOBAL_GROUP –bor $ADS_GROUP_TYPE_SECURITY_ENABLED

$csv = import-csv GroupsOnly.csv
#
# Each line contains the dn, the name, the description if one exists, and the member list
# with the dn of each group member. The member list has each member separated by a semi-colon.
#
foreach ($line in $csv)
{
	$dn = $line.dn
	$adsPath = "LDAP://" + $dn
	$objGroup = [ADSI]$adsPath
	if ($objGroup.Name)
	{
		# the above verifies that the group exists. Note that 
		# ADSI is "smart". It doesn't actually access AD and 
		# fill the property cache until you read an attribute
		# from the object.
		"Exists: " + $line.Name
		if ($line.member.Length -gt 0)
		{
			$i = 0
			$members = $line.member.Split(";")
			$lower   = $dn.ToLower()
			foreach ($member in $members)
			{
				$objMember = [ADSI]("LDAP://" + $member)
				if ($objMember.Name)
				{
					# check to see if member is already in group
					[bool]$already = $false
					$userGroups = $objMember.memberOf.Value
					if ($userGroups)
					{
						foreach ($usergroup in $userGroups)
						{
							if ($userGroup.ToLower() -eq $lower)
							{
								$already = $true
								break
							}
						}
					}
					$objMember = $null
					if ($already)
					{
						"`tAlready in group: $member"
					}
					else
					{
						$objGroup.Add(("LDAP://" + $member))
						if ($?)
						{
							"`tAdded to group: $member"
							$i++
						}
						else
						{
							# should be an error displayed on the PowerShell window
							"`tCouldn't add to group: $member"
						}
					}
				}
				else
				{
					# member specified in CSV doesn't exist in Active Directory
					"`tObject doesn't exist: $member"
				}
			}
			"`tadded $i members"
		}
		else
		{
			# members element of the CSV was empty
			"`tno members specified for group"
		}
		$objGroup = $null
	}
	else 
	{
		"Creating " + $line.Name

		$parent = "LDAP://" + $dn.SubString(1 + $dn.IndexOf(","))
		$object = [ADSI]$parent
		$child  = $object.Create("group", "CN=" + $line.Name)

		$child.Put("groupType",      $groupType)
		$child.Put("sAMAccountName", $line.name)
		$child.Put("name",           $line.name)

		if ($line.description.length -gt 0)
		{
			$child.Put("description",    $line.description)
		}

		$child.SetInfo()
		$child = $null

	}

	sleep -milli 500
}

$csv = $null

Until next time...

As always, if there are items you would like me to talk about, please drop me a line and let me know!

Posted by michael | with no comments

In case anyone besides me is counting, I've been at this (consulting, writing, speaking, etc.) for 10 months now. I'm having a blast. I wish I'd done this years ago.

Two months ago I took on a partner and we formed Smith Consulting d/b/a The Essential Exchange as a legal entity (as opposed to the sole proprietership I was previously).

After several weeks of back-and-forth, we finally came up with a logo today, and I'm very pleased with it. It will replace the generic icons you see on this blog fairly soon (image manipulation is not my strong suit). Here it is:

Posted by michael | 1 comment(s)
Filed under:

In larger organizations, the overhead associated with backing up every server in an organization (where every server is already redundant in some way) can become onerous and expensive. In those types of organizations, with dedicated virtual servers for a specific feature or multiple pieces of physical hardware set up clustered or otherwise redundant (round robin, network load balancing, etc.); you are primarily interested in only backing up what makes a particular server unique.

You are well aware that in the case of a catastrophic failure, you can "spin up" a replacement in a couple of hours, be it virtual or physical hardware. It would take longer than that to execute a full restore following all the proper steps.

Therefore, many applications, Exchange included, provide you a mechanism for backing up and restoring only those "things you need".

Let's take the example of a Client Access Server (CAS) crashing catastrophically. For example, a piece of hair falls on the motherboard and it fizzles, taking out the motherboard AND the directly attached storage (DAS). [[OK: not a particularly likely example, but still illustrative.]]

What do you need to have saved in order to CYA (Cover Your A$$)??

Your first option is, of course, the old standard: full system backup, which you can restore after doing a bare metal install of Windows Server to compatible hardware.

The second option, which is a bit more detailed but MUCH quicker, if you've prepared for it, goes as follows:

1] Image a new machine, and name it the same as the old machine

2] Install Exchange, using the /mode:RecoverServer switch, with all other options set to the SAME as you used to install the original server (this is when using DEFAULTS can really save your bacon -- or having excellent server installation documentation)

3] Restore the HKLM\SOFTWARE\Microsoft\Exchange registry key (which you smartly exported and backed up)

4] Restore the ClientAccess Exchange directory (which you also had smartly exported and backed up), which is located by default at C:\Program Files\Microsoft\Exchange\ClientAccess

5] Restore the IIS metabase settings by executing restorevdir.ps1 (since you had executed savevdir.ps1 to back those settings up!)

And wow. You are done.

If you have practiced this process, and your imaging process is fast; you can probably be done in less than 30 minutes. Much less than the two hours or so that a full recovery is probably is going to take you.

Granted, the above is simply an overview. You need to work out the specific details for your environment, but it really isn't that hard. Here are some resources to help you:

Actually, writing a script to do this might be a good project, if I ever get a few free minutes. :-)

You can also extend this concept for generating clones and redundant servers within an Exchange organization. If you examine the various text files, you will note that modifying them for other server names is not that difficult.

Until next time...

As always, if there are items you would like me to talk about, please drop me a line and let me know!

Posted by michael | 3 comment(s)
Filed under: ,