More on Active Directory Privileged Groups and Exchange Server

In October of 2008, I wrote the article adminCount, adminSDHolder, SDProp and you! This article discussed why membership in privileged groups could cause permissions challenges. You may want to refer to it before proceeding.

With Exchange Server 2010, the overall situation has continued to get more restrictive. Exchange Server 2010 needs for users who have mailboxes to inherit permissions in order for Role-Based Access Control (RBAC) and overall Exchange access to work properly. Membership in a privileged group will stop a mailbox from inheriting permissions. Usually, normal mailbox access will work fine (i.e., using Outlook with MAPI) due to historical permissions present on a mailbox, but any other IIS based or MAPI service will fail. This includes, but is not limited to, Blackberry Enterprise Server, Exchange ActiveSync, Outlook Web App, etc.

You can see a complex way of working around this issue described in KB Article 907434: The “Send As” right is removed from a user object after you configure the “Send As” right in the Active Directory Users and Computers snap-in in Exchange Server.But that method is an administrative nightmare.

Realistically speaking – what can you do?

The answer is simple: Get your users out of privileged groups.

No user who is Administrator, a Domain Admin, an Enterprise Admin, or a Schema Admin should be signing into Exchange with an account that is a member of those groups. This is also true for any member of the Built-in\Administrators group that exists on Domain Controllers. Every user who has access to a high-privilege account should also have a normal user account that they use for day-to-day usage – just like everyone else.

For the other privileged groups (Account Operators, Server Operators, Print Operators, Backup Operators, Cert Publishers) – these are legacy groups. They are a carry-over from Windows NT. Don’t use them. Instead, use a computer’s Local Security Policy – or a Domain Security Policy when appropriate – to assign users who need those capabilities the specific rights they require.

Usually, you will find that the built-in groups provide more power than you think they do (e.g., a member of Account Operators, Server Operators, Print Operators, or Backup Operators can log in locally to a domain controller and shut it down). Mapping between the groups is fairly simple by examing User Rights Assignments in any Local Security Policy. An online version of this is available at: http://technet.microsoft.com/en-us/library/bb726980.aspx in tables 7-7, 7-8, and 7-9.

You may not normally consider <insert-any-group-name-here> to be a privileged group. But Active Directory does. Get users out of privileged groups where possible, and where not possible – assign (and require use of!) users a normal account and a privileged account.

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!


Follow me on twitter! : @EssentialExch

Changing Security Groups to Distribution Groups

In Exchange Server 2010, you cannot create a new Distribution Group that is not a Universal group. If your Exchange 2010 installation was upgraded from a prior release of Exchange Server, you may find Distribution Groups created in those earlier versions of Exchange that are not Universal. As I’ve discussed in Exchange Server 2007 and Universal Groups, Exchange simply Works Better ™ with Universal Groups.

Just kidding on the ™ – but it does work better.

So, I’ve got customers who have a large investment in vbscript automation. Of course, I feel that PowerShell is a better solution, but ripping-and-replacing thousands of lines of vbscript takes quite a while. One of the customer’s vbscript applications created distribution lists in Exchange 2003 (as you may or may not be aware, as long as a few key attributes were set on an object, in Exchange 2003 the Recipient Update Service would process that object automatically – such is not true in Exchange 2007). But that application does not work properly in Exchange 2007 or Exchange 2010.

Instead of re-writing a thousand-line vbscript, with all the testing and debugging that that would’ve entailed, it seemed prudent to simply “fix the problem”. In this case, there were two problems:

[1] Sometimes, a global group is created instead of a universal group, and

[2] The group needs to be mail-enabled.

This can be done, in true PowerShell fashion, in a “one-liner”. But it’s a LONG one-liner, and much easier to read when split up. This is a technique that may come in handy for you.

##
## Fix-Security-groups.ps1
## Michael B. Smith
## September 30, 2010
##
## Examine each group present in the Groups OU. If the group is a security
## group, then ensure that it is a Universal group and mail-enable it to become
## a distribution group.
##
get-group -organizationalUnit sub.example.com/Accounts/Groups |?  `
        {$_.RecipientType -eq 'Group'} |% { `
        if( $_.GroupType -match 'Global' ) `
        { `
                set-group $_.Identity -Universal; `
        } `
        Enable-DistributionGroup $_.Identity; `
}

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter! : @EssentialExch

A Brief History of Time…(ok ok, let’s go with “An Introduction to the Windows Time Service”)

Note: This article is written for “modern” versions of the Windows operating system – that is, Windows Server 2008, Windows Server 2008 R2, Windows Vista, and Windows 7. For older versions of the Windows operating system, the concepts still apply, but some of the command line parameters for w32tm have changed.

Windows, especially in an Active Directory environment, requires “good” time. For this discussion, having “good” time means that all members of a domain are capable of synchronizing their clock to a domain controller. Domain controllers synchronize their clocks with the domain controller which holds the PDCe (Primary Domain Controller emulator) role in their Active Directory domain. PDCe’s in child domains synchronize their clocks with the PDCe of the root domain of the Active Directory forest.

When Windows does not have good time, log file entries have incorrect timestamps, event logs have incorrect timestamps, database transaction logs have incorrect timestamps, etc. etc. When the time on a computer becomes too far off from that of a domain controller (more than five minutes above or below), the computer is no longer capable of acquiring Kerberos tickets – this means that a computer and/or a user will not be able to log in to the Active Directory domain, nor will they be able to access any resources on the Active Directory network.

This can happen to user workstations and to servers. Obviously, a server may effect more users than a single workstation; but that doesn’t mean you should pay any less attention to your user workstations.

The “Windows Time” service is responsible for keeping a computer’s clock synchronized. This service can be controlled and configured on each computer by a command line tool named w32tm. Modifying any parameters for the Windows Time service requires local administrator permissions (and if UAC is enabled on the computer, it also requires an elevated command prompt or PowerShell session).

Determining whether a computer can synchronize its clock is easy to test (this is irrespective of whether the correct time source is configured – just that the computer can synchronize to the configured time source). Open an elevated command prompt or PowerShell session, and then enter:

w32tm /resync

Does it work? If so, then this computer can synchronize its clock with its configured time source. If the clock on the computer is off (“skewed” is the typical term used for this situation), then further analysis is required. If the time on the clock is off by an even number of hours, you should probably be looking at the timezone configured for the user or computer, not at the time synchronization sources.

If there are other computers whose time is skewed, then enter the same command on the other computers. The command should work there too.

If the resync commands work, but the computers are getting the wrong time, you need to begin analyzing the configuration for the Windows Time service. From your shell or PowerShell session, enter:

w32tm /query /source

This allows you to determine where the particular computer is getting its time. There are a number of possible responses. These include:

Local CMOS Clock

In this case, the computer is using the hardware clock on the computer as its time source. If you are using VMware, this means that the virtual machine is synchronizing to the VMware host.

Free Running System Clock

In this case, the computer is not using any external source, but depending on the time tick generated by the System Idle Process running on the computer. This value will generate a skewed time more quickly than any other.

a hostname of a domain controller in the Active Directory forest

In this case, the computer is using a domain controller as either an NTP server or as the time source via Active Directory. To determine which, see “/query /configuration”, discussed later.

a hostname of a computer running a NTP server

In this case, the computer is using a non-Active Directory server running an NTP server as its time source.

VM IC Time Synchronization Provider

In this case, the computer is using Hyper-V virtualization services as its time source.

Best practices from Microsoft recommend that you never use virtualization services (regardless of your hypervisor provider) as a time source for domain-joined computer; instead, you should depend on typical Active Directory synchronization methods.

VMware recommends that, for domain-joined computers, you install an NTP server on the VMware host and you have the computers synchronize to that NTP server.

*** Edit on June 25, 2010 – A VMware employee contacted me at the end of May suggesting that the above line was not accurate. Indeed, VMware updated their documentation (the linked VMware KB 1318 article below) in March of 2010. Now, for most intents and purposes, the VMware recommendations match the Microsoft recommendations.

In my mind, you are better off starting with the Microsoft recommendations and then go from there.

Here are references to the above comments and best practices:

Virtual Domain Controllers and Time Synchronisation
Considerations when hosting Active Directory domain controller in virtual hosting environments
Deployment Considerations for Virtualized Domain Controllers
VMware KB: Timekeeping best practices for Windows

Now, if the initial resync command doesn’t work – that particular failure reason is what you need to figure out. The first thing I always check is the firewall configuration. By default, time synchronization requires that that a computer be capable of sending a UDP request to port 123 on the NTP server (and receiving the response). NTP servers also listen on port 123 for TCP requests. The Windows Advanced Firewall in the modern Windows operating systems will automatically have an entry opened for time synchronization on UDP port 123 to your domain controllers. However, if you are configuring your PDC emulator server, you also need to ensure that the external firewall also allows that request. If you have non-domain-joined computers, then you may need to globally allow port 123 requests in your firewall.

The command below will tell you the time source for a particular computer:

w32tm /query /configuration

You are initially most interested in the value of the Type variable which is displayed. There are a number of possible responses. These include:

NTP – the external time source is the NTP server(s) specified by the NtpServer variable
NT5DS – the external time source is the domain hierarchy (that is, time synchronization originates from a domain controller)
NoSync – there is no external time source
AllSync – the computer should use both the domain hierarchy and the manually specified NTP server(s) as external time sources

There may be multiple external NTP servers listed in the NtpServer variable.

To properly set up a time source synchronization hierarchy for your domain, you need to begin by locating the domain controller which holds the PDC emulator FSMO role (obviously, if you have a single domain controller, such as is normally the case in SBS 2008, this process can be shortcut). To determine the holders of the FSMO roles, at that earlier-opened command prompt or PowerShell session, enter:

netdom query fsmo

Next, on the domain controller which is revealed to hold the PDC emulator role, you should do something like this:

w32tm /config /manualpeerlist:pool.ntp.org /syncfromflags:manual
w32tm /config /update
net stop w32time
net start w32time
w32tm /resync /rediscover

This ensures that this particular domain controller will attempt to synchronize with an external source providing known good time. pool.ntp.org is a common source. Windows computers come configured by default to use time.windows.com, which sometimes works and sometimes doesn’t.

For all other domain-joined computers, the appropriate configuration is:

w32tm /config /syncfromflags:domhier
w32tm /config /update
net stop w32time
net start w32time
W32tm /resync /rediscover

That really should take care of it. /syncfromflags:domhier is the default for domain-joined workstations and should be for all DCs except for the one in the root domain holding the PDCe role.

When a computer is properly synchronizing from an external source (after the Windows Time service restarts or becomes capable of synchronizing after some interval where it can’t synchronize), the following entry is made to the System Event Log:

Log Name: System
Source: Microsoft-Windows-Time-Service
Date: 1/24/2010 1:01:27 AM
Event ID: 35
Task Category: None
Level: Information
Keywords:
User: LOCAL SERVICE
Computer: W2008R2-DC
Description:
The time service is now synchronizing the system time with the time source pool.ntp.org (ntp.m|0x0|0.0.0.0:123->69.26.112.120:123).

If the time source is a DC, the DC will be named and its IP address listed, just as if it were an external source.

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter: @EssentialExch

Where oh where, did my AD site go…[Alternate title: It’s the DNS, stupid.]

I recently had a very confusing issue arise at one of my Exchange 2007 clients and I decided to share it with you. At this particular company, an Active Directory site is reserved for Exchange, and there are two domain controllers (both global catalog servers) in that AD site. The front-end is two CAS (CAS1 and CAS2) load-balanced by an ISA enterprise array with a CCR backend.

The week before, we had replaced all of the domain controllers in the forest with Windows Server 2008 R2 domain controllers, and bumped both the domain functional level and the forest functional level to Server 2008 R2 (we are going to enable the AD Recycle Bin). The new DCs replaced the old DCs and kept the original IP addresses.

That’s the setup.

An onsite technician was applying patches late one night (good for him!). Unfortunately, he patched and rebooted both of the Exchange AD site DCs at the same time (bad for him!). As you may already know – that makes Exchange very unhappy. System Center Operations Manager is also running in the environment and it immediately started to generate alerts about the missing domain controllers.

Sidebar: In Exchange 2003 and above, Exchange executes an Active Directory Topology discovery every 15 minutes. The specifics vary between versions of Exchange, but suffice it to say that, within 15 minutes, Exchange will find another DC/GC set (if they exist). In that case, your best bet is just to wait out that 15 minutes.

The technician reacted to the alerts from OpsMgr by rebooting the Client Access Servers. They both found out-of-site DCs and began working.

Then, the fun began. When the in-site DCs came back online (just a few minutes later), CAS1 reassociated with the in-site DCs and reset its secure channel to one of the in-site DCs. CAS2 did not.

The symptom of this is that all users connected to CAS1 through ISA were fine. However, the users that ISA connected to CAS2 were redirected through the same URL that they had already used – and CAS-to-CAS proxying did not work, so they couldn’t access any Exchange services – OWA, Exchange, anything. Quick workaround: remove CAS2 from the webfarm and RPC publishing in ISA so everything was routed through CAS1. However, redundancy is now lost.

Why this problem happened – I don’t know. The NetLogon service is responsible for maintaining the AD site a computer identifies itself with and maintaining the secure channel to a proper DC. However, for CAS2, NetLogon refused to reassociate to an in-site DC.

NetLogon bases site affinity on DNS. Both servers, CAS1 and CAS2, were configured identically for DNS. NetLogon uses a Windows API call named DsGetSiteName. In Windows Server 2008 and Windows Server 2008 R2 (and in Windows 7), you can use the nltest.exe utility to check this value. To wit:

PS C:\> nltest.exe /dsgetsite
Default-First-Site-Name
The command completed successfully
PS C:\>

Sidebar: nltest is available for Windows Server 2003 and Windows Server 2003 R2 as well, you just have to download and install the Windows Support Tools.

NetLogon does its check-and-reset once an hour, and upon startup. Once you know that, it should be easy to just restart the NetLogon service, right? Well, that didn’t make any difference.

So, we have the capability of forcing a particular secure channel for a server, and this also will set its AD site. To wit:

PS C:\> netdom.exe reset cas2 /server:DsEx2
The secure channel from CAS2 to DOMAIN was reset.
The command completed successfully.
PS C:\>

Note: nltest.exe has this functionality too, but netdom.exe has been around longer and I was more familiar with its parameters. See the SC_RESET parameter to nltest.

The AD site is updated, the secure channel is updated, and everything looks great. I declare success, put the server back into ISA, and move on.

An hour later, the client calls and says it is broken again.

Well, he’s right. The AD site has flipped back again and CAS2 is thus not operating properly. Obviously this has happened because NetLogon did its cycle.

C-r-a-p.

OK. Now its time to buckle down. AD sites are based on DNS. We know that. So, I ran dcdiag on all the servers. replmon on all the servers. Everything is clean.

But then visually examine the DC locator records in DNS – and… I find an extra one.

During the process of standing up all the new DCs, and configuring the new DCs with old permanent IP addresses, the OLD DCs ended up with the temporary IP addresses of the new DCs. Then, the old DCs were demoted.

All of the DCs but one cleaned up after themselves. The extra locator record was one of those DCs, and shockingly, now has stale DNS.

The fix? Remove the stale DC locator record. Reset the secure channel again, just to ensure it gets to the right place.

And Voila! It’s fixed.

If you’ve ever been to one of my installation seminars or read many of my articles, I talk about the importance of DNS in both Active Directory and Exchange Server. Here, yet again, is another example of that. Sometimes, you just have to take a look in the right place to find the problem.

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter: @EssentialExch

Speeding Reboot When Exchange is on a DC/GC

As I’ve noted in several previous blog entries (such as here and here), installing Exchange on a domain controller and/or a global catalog server is not a best practice. However, if you are running SBS (Small Business Server) or EBS (Essential Business Server) or if you only have a single server in your environment – you may not/don’t have much choice.

Given that you or your company may have no choice in the decision, it still may come as a disappointment (disgust?) that it takes so long to reboot your Exchange server.

This typically happens because of two primary reasons:

  • When Exchange is installed on a DC/GC, that Exchange server will refer to no other DC/GCs in the Active Directory, and
  • When a shutdown or reboot request is received, it isn’t possible for Exchange to terminate prior to Active Directory shutting down.

Now, you may think “poor poor Exchange, do what _I_ want anyway!” Well, in Exchange’s defense, that may be harder than you think. Consider a common scenario that may occur:

  • A VSS backup is running against your server and it’s just entered the Freeze stage against all writers
  • Exchange is running
  • RPC/HTTP is up
  • OWA is up
  • SQL is running\
  • …a shutdown request comes in

What is the right order to shut things down in that ensure everything gets shut down before AD starts shutting down?

The answer is – can’t be done!

Exchange and Active Directory have no mechanism for terminating the right things in the right order. So, it is up to a human brain to help them out.

I suggest you create a directory on your combination Exchange / Active Directory server named c:\scripts. Within that directory, create a file named shutdown.cmd. In that file, place the following commands:

echo %DATE% %TIME% Shutting Down Services >>c:\scripts\shutdown.txt
net stop msexchangeadtopology /y
echo %DATE% %TIME% Shut Down MSExchangeADTopology >>c:\scripts\shutdown.txt
net stop msftesql-exchange /y
echo %DATE% %TIME% Shut Down MSFteSQL-Exchange >>c:\scripts\shutdown.txt
net stop msexchangeis /y
echo %DATE% %TIME% Shut Down MSExchangeIS >>c:\scripts\shutdown.txt
net stop msexchangesa /y
echo %DATE% %TIME% Shut down MSExchangeSA >>c:\scripts\shutdown.txt
net stop iisadmin /y
echo %DATE% %TIME% Shut down IISAdmin >>c:\scripts\shutdown.txt
echo %DATE% %TIME% Shut down services script complete >>c:\scripts\shutdown.txt

Note that the echo statements are completely optional. They are simply present to allow you to record the sequence of events that does occur during a shutdown.

Once you have created this file, open Administrative Tools -> Group Policy Management.

Expand the domains node, then expand the node for your domain, and then expand the Group Policy Objects node.

Under the GPO node, right click on the Default Domain Controllers Policy and select Edit…

Expand Computer Configuration -> Policies -> Windows Settings and then click on Scripts.

In the right pane, double click on Shutdown, then click on Add in the dialog that opens. Browse to the shutdown.cmd that you created earlier and click OK.

Now, click OK until you are back to the group policy main window and close it and then close the Group Policy Management window.

If you have a single DC, you are done. Otherwise, wait for 15-20 minutes to allow your modified group policy to replicate to other DCs in your Active Directory.

Now, each time your DC that has Exchange Server installed on it reboots (or shuts down), it will execute the above script. This will reduce the required reboot time 50% – 75%.

Enjoy!

Until next time…

If there are things you would like to see written about, please let me know.


Follow me on twitter: @EssentialExch

The Experts Conference – TEC’2010

For the third time, I’ll be speaking at the upcoming The Experts Conference, sponsored by Quest.

I’ll be discussing using Exchange Web Services (EWS) from PowerShell — and for some reason, my abstract isn’t yet posted on the conference website! I need to get that corrected.

TEC’2010 is being held in Los Angeles, CA this year, from April 25 – 28. The Exchange track will have it’s strongest expert content ever, with MVPs and Microsoft personnel from all over the United States and Europe. Of course, as always, TEC’2010 will have a huge Directory Services and Identity Management track and is introducing a SharePoint track.

See the official TEC’2010 website for more information.

I hope you’ll come join me and many other people at TEC’2010. It’s a great time with lots of talk-time with some of the best folks in the business.

Until next time…

If there are things you would like to see written about, please let me know


Follow me on twitter: @EssentialExch

Getting the Contents of an Active Directory Integrated DNS Zone, Version 2

In June of 2009, I published the first version of Getting the Contents of an Active Directory Integrated DNS Zone. Shortly after that, Chris Dent (chris at highorbit dot co dot uk) published a blog post clarifying the format of the dnsRecord attribute. Most of the time, the difference between the “correct” format and what I had deduced had no effect. But, I recently had to go back to this project and I needed to decode more DNS resource records. Now, using the proper format became important.

Thus, version 2 of the utility was born. I now process A, AAAA, MX, PTR, CNAME, SOA, NS, TXT, SRV, and HINFO resource records.

I hope you’ll enjoy this update. Now, if I can figure out how to determine lame delegations in PowerShell, I can write a PowerShell version of dnswalk!

##
## dns-dump.ps1
##
## Michael B. Smith
## michael at smithcons dot com
## http://TheEssentialExchange.com/blogs/michael
## May/June, 2009
## Updated December, 2009 adding many add'l record types.
##
## Use as you wish, no warranties expressed, implied or explicit.
## Works for me, may not for you.
## If you use it, I would appreciate an attribution.
##
## Thanks to Chris Dent, chris at highorbit dot co dot uk
## for some clarification on the precise format of the
## dnsRecord attribute. See his blog post on the topic at
## http://www.highorbit.co.uk/?p=1097
##

Param(
	[string]$zone,
	[string]$dc,
	[switch]$csv,
	[switch]$help
)

function dumpByteArray([System.Byte[]]$array, [int]$width = 9)
{
	## this is only used if we run into a record type
	## we don't understand.

	$hex = ""
	$chr = ""
	$int = ""

	$i = $array.Count
	"Array contains {0} elements" -f $i
	$index = 0
	$count = 0
	while ($i-- -gt 0)
	{
		$val = $array[$index++]

		$hex += ("{0} " -f $val.ToString("x2"))

		if ([char]::IsLetterOrDigit($val) -or 
		    [char]::IsPunctuation($val)   -or 
		   ([char]$val -eq " "))
		{
			$chr += [char]$val
		}
		else
		{
			$chr += "."
		}

		$int += "{0,4:N0}" -f $val

		$count++
		if ($count -ge $width)
		{
			"$hex $chr $int"
			$hex = ""
			$chr = ""
			$int = ""
			$count = 0
		}		
	}

	if ($count -gt 0)
	{
		if ($count -lt $width)
		{
			$hex += (" " * (3 * ($width - $count)))
			$chr += (" " * (1 * ($width - $count)))
			$int += (" " * (4 * ($width - $count)))
		}

		"$hex $chr $int"
	}
}

function dwordLE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert four consecutive bytes in $arr into a
	## 32-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for little-endian values.

	$res = $arr[$startIndex + 3]
	$res = ($res * 256) + $arr[$startIndex + 2]
	$res = ($res * 256) + $arr[$startIndex + 1]
	$res = ($res * 256) + $arr[$startIndex + 0]

	return $res
}

function dwordBE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert four consecutive bytes in $arr into a
	## 32-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for big-endian values.

	$res = $arr[$startIndex]
	$res = ($res * 256) + $arr[$startIndex + 1]
	$res = ($res * 256) + $arr[$startIndex + 2]
	$res = ($res * 256) + $arr[$startIndex + 3]

	return $res
}

function wordLE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert two consecutive bytes in $arr into a
	## 16-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for little-endian values.

	$res = $arr[$startIndex + 1]
	$res = ($res * 256) + $arr[$startIndex]

	return $res
}

function wordBE([System.Byte[]]$arr, [int]$startIndex)
{
	## convert two consecutive bytes in $arr into a
	## 16-bit integer value... if I had bit-manipulation
	## primitives in PowerShell, I'd use them instead
	## of the multiply operator.
	##
	## this routine is for big-endian values.

	$res = $arr[$startIndex]
	$res = ($res * 256) + $arr[$startIndex + 1]

	return $res
}

function decodeName([System.Byte[]]$arr, [int]$startIndex)
{
	## names in DNS are stored in two formats. one
	## format contains a single name and is what we
	## called "simple string" in the old days. the
	## first byte of a byte array contains the length
	## of the string, and the rest of the bytes in 
	## the array are the data in the string.
	##
	## a "complex string" is built from simple strings.
	## the complex string is prefixed by the total
	## length of the complex string in byte 0, and the
	## total number of segments in the complex string
	## in byte 1, and the first simple string starts 
	## (with its length byte) in byte 2 of the complex
	## string.

	[int]$totlen   = $arr[$startIndex]
	[int]$segments = $arr[$startIndex + 1]
	[int]$index    = $startIndex + 2

	[string]$name  = ""

	while ($segments-- -gt 0)
	{
		[int]$segmentLength = $arr[$index++]
		while ($segmentLength-- -gt 0)
		{
			$name += [char]$arr[$index++]
		}
		$name += "."
	}

	return $name
}

function analyzeArray([System.Byte[]]$arr, [System.Object]$var)
{
	$nameArray = $var.distinguishedname.ToString().Split(",")
	$name = $nameArray[0].SubString(3)

	## RData Length is the length of the payload in bytes (that is, the variable part of the record)
	## Truth be told, we don't use it. The payload starts at $arr[24]. If you are ever concerned
	## about corrupt data and running off the end of $arr, then you need to verify against the RData
	## Length value.
	$rdataLen = wordLE $arr 0

	## RData Type is the type of the record
	$rdatatype = wordLE $arr 2

	## the serial in the SOA where this item was last updated
	$updatedAtSerial = dwordLE $arr 8

	## TimeToLive
	$ttl = dwordBE $arr 12

	## $unknown = dword $arr 16

	## timestamp of when the record expires, or 0 means "static"
	$age = dwordLE $arr 20
	if ($age -ne 0)
	{
		## hours since January 1, 1601 (start of Windows epoch)
		## there is a long-and-dreary way to do this manually,
		## but get-date makes it trivial to do the conversion.
		$timestamp = ((get-date -year 1601 -month 1 -day 1 -hour 0 -minute 0 -second 0).AddHours($age)).ToString()
	}
	else
	{
		$timestamp = "[static]"
	}

	if ($rdatatype -eq 1)
	{
		# "A" record
		$ip = "{0}.{1}.{2}.{3}" -f $arr[24], $arr[25], $arr[26], $arr[27]

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "A", $ip
	}
	elseif ($rdatatype -eq 2)
	{
		# "NS" record
		$nsname = decodeName $arr 24

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "NS", $nsname
	}
	elseif ($rdatatype -eq 5)
	{
		# CNAME record
		# canonical name or alias

		$alias = decodeName $arr 24

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "CNAME", $alias
	}
	elseif ($rdatatype -eq 6)
	{
		# "SOA" record
		# "Start-Of-Authority"

		$nslen = $arr[44]
		$priserver = decodeName $arr 44
		$index = 46 + $nslen

		# "Primary server: $priserver"

		##$index += 1
		$resparty = decodeName $arr $index

		# "Responsible party: $resparty"

		# "TTL: $ttl"
		# "Age: $age"

		$serial = dwordBE $arr 24
		# "Serial: $serial"

		$refresh = dwordBE $arr 28
		# "Refresh: $refresh"

		$retry = dwordBE $arr 32
		# "Retry: $retry"

		$expires = dwordBE $arr 36
		# "Expires: $expires"

		$minttl = dwordBE $arr 40
		# "Minimum TTL: $minttl"

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}"

			$formatstring -f $name, $timestamp, $ttl, `
				"SOA", $priserver, $resparty, `
				$serial, $refresh, $retry, `
				$expires, $minttl
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}"

			$formatstring -f $name, $timestamp, $ttl, "SOA"
			(" " * 32) + "Primary server: $priserver"
			(" " * 32) + "Responsible party: $resparty"
			(" " * 32) + "Serial: $serial"
			(" " * 32) + "TTL: $ttl"
			(" " * 32) + "Refresh: $refresh"
			(" " * 32) + "Retry: $retry"
			(" " * 32) + "Expires: $expires"
			(" " * 32) + "Minimum TTL (default): $minttl"
		}
	}
	elseif ($rdatatype -eq 12)
	{
		# "PTR" record

		$ptr = decodeName $arr 24

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "PTR", $ptr
	}
	elseif ($rdatatype -eq 13)
	{
		# "HINFO" record

		[string]$cputype = ""
		[string]$ostype  = ""

		[int]$segmentLength = $arr[24]
		$index = 25

		while ($segmentLength-- -gt 0)
		{
			$cputype += [char]$arr[$index++]
		}

		$index = 24 + $arr[24] + 1
		[int]$segmentLength = $index++

		while ($segmentLength-- -gt 0)
		{
			$ostype += [char]$arr[$index++]
		}

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4},{5}"
		}

		$formatstring -f $name, $timestamp, $ttl, "HINFO", $cputype, $ostype
	}
	elseif ($rdatatype -eq 15)
	{
		# "MX" record

		$priority = wordBE $arr 24
		$mxhost   = decodeName $arr 26

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}  {5}"
		}

		$formatstring -f $name, $timestamp, $ttl, "MX", $priority, $mxhost
	}
	elseif ($rdatatype -eq 16)
	{
		# "TXT" record

		[string]$txt  = ""

		[int]$segmentLength = $arr[24]
		$index = 25

		while ($segmentLength-- -gt 0)
		{
			$txt += [char]$arr[$index++]
		}

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "TXT", $txt

	}
	elseif ($rdatatype -eq 28)
	{
		# "AAAA" record

		### yeah, this doesn't do all the fancy formatting that can be done for IPv6

		$str = ""
		for ($i = 24; $i -lt 40; $i+=2)
		{
			$seg = wordBE $arr $i
			$str += ($seg).ToString('x4')
			if ($i -ne 38) { $str += ':' }
		}

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3}`t{4}"
		}

		$formatstring -f $name, $timestamp, $ttl, "AAAA", $str
	}
	elseif ($rdatatype -eq 33)
	{
		# "SRV" record

		$port   = wordBE $arr 28
		$weight = wordBE $arr 26
		$pri    = wordBE $arr 24

		$nsname = decodeName $arr 30

		if ($csv)
		{
			$formatstring = "{0},{1},{2},{3},{4},{5}"
		}
		else
		{
			$formatstring = "{0,-30}`t{1,-24}`t{2}`t{3} {4} {5}"
		}

		$formatstring -f `
			$name, $timestamp, `
			$ttl, "SRV", `
			("[" + $pri.ToString() + "][" + $weight.ToString() + "][" + $port.ToString() + "]"), `
			$nsname
	}
	else
	{
		$name
		"RDataType $rdatatype"
		$var.distinguishedname.ToString()
		dumpByteArray $arr
	}

}

function processAttribute([string]$attrName, [System.Object]$var)
{
	$array = $var.$attrName.Value
####	"{0} contains {1} rows of type {2} from {3}" -f $attrName, $array.Count, $array.GetType(), $var.distinguishedName.ToString()

	if ($array -is [System.Byte[]])
	{
####		dumpByteArray $array
		" "
		analyzeArray $array $var
		" "
	}
	else
	{
		for ($i = 0; $i -lt $array.Count; $i++)
		{
####			dumpByteArray $array[$i]
			" "
			analyzeArray $array[$i] $var
			" "
		}
	}
}

function usage
{
"
.\dns-dump -zone  [-dc ] [-csv] |
	   -help

dns-dump will dump, from Active Directory, a particular named zone. 
The zone named must be Active Directory integrated.

Zone contents can vary depending on domain controller (in regards
to replication and the serial number of the SOA record). By using
the -dc parameter, you can specify the desired DC to use. Otherwise,
dns-dump uses the default DC.

Usually, output is formatted for display on a workstation. If you
want CSV (comma-separated-value) output, specify the -csv parameter.
Use out-file in the pipeline to save the output to a file.

Finally, to produce this helpful output, you can specify the -help
parameter.

This command is basically equivalent to (but better than) the:

	dnscmd /zoneprint 
or
	dnscmd /enumrecords  '@'

commands.

Example 1:

	.\dns-dump -zone essential.local -dc win2008-dc-3

Example 2:

	.\dns-dump -help

Example 3:

	.\dns-dump -zone essential.local -csv |
            out-file essential.txt -encoding ascii

	Note: the '-encoding ascii' is important if you want to
	work with the file within the old cmd.exe shell. Otherwise,
	you can usually leave that off.
"
}

	##
	## Main
	##

	if ($help)
	{
		usage
		return
	}

	if ($args.Length -gt 0)
	{
		write-error "Invalid parameter specified"
		usage
		return
	}

	if (!$zone)
	{
		throw "must specify zone name"
		return
	}

	$root = [ADSI]"LDAP://RootDSE"
	$defaultNC = $root.defaultNamingContext

	$dn = "LDAP://"
	if ($dc) { $dn += $dc + "/" }
	$dn += "DC=" + $zone + ",CN=MicrosoftDNS,CN=System," + $defaultNC

	$obj = [ADSI]$dn
	if ($obj.name)
	{
		if ($csv)
		{
			"Name,Timestamp,TTL,RecordType,Param1,Param2"
		}

		#### dNSProperty has a different format than dNSRecord
		#### processAttribute "dNSProperty" $obj

		foreach ($record in $obj.psbase.Children)
		{
			####	if ($record.dNSProperty) { processAttribute "dNSProperty" $record }
			if ($record.dnsRecord)   { processAttribute "dNSRecord"   $record }
		}
	}
	else
	{
		write-error "Can't open $dn"
	}

	$obj = $null

Until next time…

If there are things you would like to see written about, please let me know


Follow me on twitter: @EssentialExch

Exchange Server 2010 – Administrative Access to All Mailboxes

In Exchange 2010, the storage group has disappeared. Instead, the properties of a database and of a storage group have merged – the result being referred to as a database. Effectively, a database has been promoted to be as important as a storage group used to be.

You may could have predicted this coming from changes which happened in Exchange 2007, as a number of features required that a storage group only have a single database contained within those storage groups.

Regardless of which, a mailboxdatabase is unique within an Exchange 2010 organization. That means you can no longer have a mailboxdatabase named “Mailbox Database” or “Mailbox (servername)” on each and every server within your Exchange organization. Instead, each and every mailboxdatabase name is unique. This is guaranteed by a many-digit number suffixed to the end of a mailbox database’s name.

This does simplify some aspects of administration – instead of having to specify server\storage-group\database in order to name a specific database, you can now specify simply the database name. However, the name of that database may be something like “Mailbox Database 1015374940” (which is the name of the mailbox database hosting my production domain). That is somewhat more challenging to remember. Just somewhat. HAH.

One of the changes involved in moving databases to be organizational objects instead of server objects makes it practical to (again – after skipping Exchange 2007) allow a single user or group administrative access to all Exchange 2007 mailboxes.

Of course, this can be done from the GUI – however, the GUI you must use is LDP.exe or ADSIEdit.msc – not the Exchange Management Console (EMC).

However, this is probably easier to do from the Exchange Management Shell (EMS), given that you know a couple of key facts: the distinguishedname of your Active Directory domain and any of three formats for a user/group you want to allow this access.

Note that allowing Administrative Access to all mailboxes can be tracked by logging – but only if that logging is enabled – and that logging is not enabled by default. Also note that there may be legal issues associated with allowing specific users or groups access to all mailboxes in your organization – I recommend that every organization have a information access and security policy that includes corporate access and use of electronic mail. Finally, this information is provided for instructional purposes and I accept no liability for providing this information or to any use to which it may be put.

Now that I’ve covered my rear….

If, for example, your forest is named named example.com, then the distinguished name of that forest is DC=example,DC=com. If your forest is named SBS.Example.Local, then the distinguishedname of the forest is DC=SBS,DC=Example,DC=Local. Now, remember that. 🙂

In terms of specifying a user or group name that you are going to provide access, you have three possible formats:

NetBIOS-domain-name\principal-name

Active-Directory-forest-name/container-or-organizational-unit/principal-name

CN=principal-name,OU=organizational-unit,DC=example,DC=local

For example, if your Active Directory forest name is example.local and the NetBIOS domain name is EXAMPLE, and the security principal is named TEST and that principal is located in the Users container, you would have these examples:

EXAMPLE\TEST

example.local/Users/TEST

CN=TEST,CN=Users,DC=example,DC=local

Finally, using the above example, you would have this PowerShell command:

Add-AdPermission –Identity “CN=Databases,CN=Exchange Administrative Group (FYDIBOHF23SPDLT),CN=Administrative Groups,CN=First Organization,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=example,DC=local” –User EXAMPLE\TEST –InheritedObjectType msExchPrivateMDB –extendedRights Receive-As –inheritanceType Descendents

Or, if we were to expand this out a little bit:

$principal = “EXAMPLE\Test”
$domain = “DC=example,DC=local”
$identity = “CN=Databases,” +
“CN=Exchange Administrative Group (FYDIBOHF23SPDLT),” +
“CN=Administrative Groups,CN=First Organization,” +
“CN=Microsoft Exchange,CN=Services,CN=Configuration,” +
$domain
Add-AdPermission –Identity $identity –User $principal `
–InheritedObjectType msExchPrivateMDB `
–extendedRights Receive-As `
–inheritanceType Descendents

Until next time…

If there are things you would like to see written about, please let me know!


Follow me on twitter: @EssentialExch

Mailbox Permissions: Pulling Back the Curtain…

Let’s talk about mailbox permissions. People often get a little confused between store-level mailbox permissions and Active Directory-level mailbox permissions. They are similar but not the same. For clarity it may help for us to look at them all.

Note: this post is written specifically against Exchange Server 2007. For Exchange Server 2010, storage groups disappear – mailbox stores acquire storage group attributes, they are promoted to equal status (you could see this coming, as a number of features in Exchange Server 2007 only worked when you had a single mailbox store per storage group). So, the contents of this post apply equally to Exchange Server 2010 – just where-ever you see “storage group”, replace that with “mailbox store”.

Mailbox permissions include: FullAccess, SendAs, ExternalAccount, DeleteItem, ReadPermission, ChangePermission, and ChangeOwner. This list does not include “Send on Behalf”. That’s because a user can set “Send on Behalf” for another user by defining the other user as a delegate and that’s handled separately from mailbox permissions.

Relevant Active Directory permissions include: FullControl, SendAs, ReceiveAs, Delete, and ViewInfoStoreStatus. Three of these (SendAs, ReceiveAs, and ViewInfoStoreStatus) are so-called “extended rights”, which means they are handled somewhat differently than standard access rights.

The AD permission ViewInfoStoreStatus allows a specific user or group to do just that – view the status of an information store. It doesn’t map to anything at the mailbox level. I don’t believe that it is used in Exchange 2007 and above. It had applicability in the Exchange 2000 and Exchange 2003 timeframe when administration of Exchange was handled at the “Administrative Group” level, and ViewInfoStoreStatus was assigned to an administrative group for the administrators of that administrative group (and set to inherit down through all the servers and stores in that group).

The AD permission FullControl includes Delete, SendAs, and ReceiveAs at the AD level. At the mailbox level this maps to FullAccess and SendAs.

The AD permission ReceiveAs maps to FullAccess at the mailbox level. Note that this does not include SendAs or ExternalAccount permissions. There is no way (even though some Microsoft documentation states otherwise) to provide read-only access to a mailbox via permissions.

The AD permission SendAs maps to SendAs at the mailbox level. Note that while it is possible to set Send-As on the mailbox itself, without having it set in AD, you will not be able actually Send-As using Outlook – it depends on Send-As being set within AD.

The AD permission Delete maps to DeleteItem at the mailbox level.

The store permission FullAccess includes all mailbox permissions except SendAs and ExternalAccount.

Setting the AD permission does not cause the mailbox permission(s) to be set (AD has no direct knowledge of Exchange). One must presume that the information store service is smart enough to check both. I have no idea of the official precedence map of AD permissions vs. store permissions. However, behavior indicates that AD permissions are evaluated first, and if they produce a “pass” then the store permissions are evaluated to get the final result (similar to the “share” vs. “NTFS” precedence rules).

The AD permissions can be set on a storage group or a mailbox store, and apply to all mailboxes in that storage group or mailbox store (if set for inheritance). There is no mechanism to do that within the mailbox store itself (that is, there is no cmdlet for Add-MailboxDatabasePermission or Add-StorageGroupPermission, nor do I believe that a store has a concept of a security hierarchy above the mailbox level). Also, while you can set SendAs at the storage group or mailbox store level, this just means that you can impersonate the storage group or mailbox store – it does not mean that you can Send-As for all accounts in that storage group or mailbox store. That permission must be set on a per-mailbox basis.

There is a good whitepaper for understanding store and AD permissions written against Exchange 2000 and Exchange 2003. It is still at microsoft.com/downloads : Working with Store Permissions in Microsoft Exchange 2000 Server and Exchange Server 2003. However, it is a little dated and there are a couple of errors in the document. The basics are still good, but Exchange 2007 reintroduced the idea of setting actual permissions on the mailbox (in 2000 and 2003, you could set mailbox permissions only before the mailbox was created, everything else was set against the AD user object). Exclusive of the impact of Role Based Access Control (commonly referred to as RBAC), I believe that Exchange Server 2010 continues to follow the Exchange Server 2007 rules.

While we have not discussed them here, note that the store-level permissions available to mailboxes are the same permissions available to public folders (excepting only ExternalAccount). And, with the exception of SendAs and ExternalAccount, these are the same permissions available to subfolders within a mailbox and a public folder.

Implementation note:

From a technical perspective, the AD attributes actually represent Access Control Entries set within the nTSecurityDescriptor object assigned to a user object within Active Directory.

For more information on Access Control Lists, Access Control Entries, and the nTSecurityDescriptor object, see Displaying Security on Active Directory, Exchange, and Registry Objects and Windows Permissions – Access Control Lists.

Until next time…

If there are things you would like to see written about, please let me know!

P.S. Thanks to Ross Smith, IV of Microsoft for clarifying a couple of points contained in this article.


Follow me on twitter: @EssentialExch

Mailbox Permissions: Pulling Back the Curtain…

<p>Let’s talk about mailbox permissions. People often get a little confused between store-level mailbox permissions and Active Directory-level mailbox permissions. They are similar but not the same. For clarity it may help for us to look at them all.</p>
<blockquote>
<p>Note: this post is written specifically against Exchange Server 2007. For Exchange Server 2010, storage groups disappear – mailbox stores acquire storage group attributes, they are promoted to equal status (you could see this coming, as a number of features in Exchange Server 2007 only worked when you had a single mailbox store per storage group). So, the contents of this post apply equally to Exchange Server 2010 – just where-ever you see “storage group”, replace that with “mailbox store”.</p></blockquote>
<p>Mailbox permissions include: FullAccess, SendAs, ExternalAccount, DeleteItem, ReadPermission, ChangePermission, and ChangeOwner. This list does not include “Send on Behalf”. That’s because a user can set “Send on Behalf” for another user by defining the other user as a delegate and that’s handled separately from mailbox permissions.</p>
<p>Relevant Active Directory permissions include: FullControl, SendAs, ReceiveAs, Delete, and ViewInfoStoreStatus. Three of these (SendAs, ReceiveAs, and ViewInfoStoreStatus) are so-called “extended rights”, which means they are handled somewhat differently than standard access rights.</p>
<p>The AD permission ViewInfoStoreStatus allows a specific user or group to do just that – view the status of an information store. It doesn’t map to anything at the mailbox level. I don’t believe that it is used in Exchange 2007 and above. It had applicability in the Exchange 2000 and Exchange 2003 timeframe when administration of Exchange was handled at the “Administrative Group” level, and ViewInfoStoreStatus was assigned to an administrative group for the administrators of that administrative group (and set to inherit down through all the servers and stores in that group).</p>
<p>The AD permission FullControl includes Delete, SendAs, and ReceiveAs at the AD level. At the mailbox level this maps to FullAccess and SendAs.</p>
<p>The AD permission ReceiveAs maps to FullAccess at the mailbox level. Note that this does <strong>not</strong> include SendAs or ExternalAccount permissions. There is no way (even though some Microsoft documentation states otherwise) to provide read-only access to a mailbox via permissions.</p>
<p>The AD permission SendAs maps to SendAs at the mailbox level. Note that while it is possible to set Send-As on the mailbox itself, without having it set in AD, you will not be able actually Send-As using Outlook – it depends on Send-As being set within AD.</p>
<p>The AD permission Delete maps to DeleteItem at the mailbox level.</p>
<p>The store permission FullAccess includes all mailbox permissions except SendAs and ExternalAccount.</p>
<p>Setting the AD permission does not cause the mailbox permission(s) to be set (AD has no direct knowledge of Exchange). One must presume that the information store service is smart enough to check both. I have no idea of the official precedence map of AD permissions vs. store permissions. However, behavior indicates that AD permissions are evaluated first, and if they produce a “pass” then the store permissions are evaluated to get the final result (similar to the “share” vs. “NTFS” precedence rules).</p>
<p>The AD permissions can be set on a storage group or a mailbox store, and apply to all mailboxes in that storage group or mailbox store (if set for inheritance). There is no mechanism to do that within the mailbox store itself (that is, there is no cmdlet for Add-MailboxDatabasePermission or Add-StorageGroupPermission, nor do I believe that a store has a concept of a security hierarchy above the mailbox level). Also, while you can set SendAs at the storage group or mailbox store level, this just means that you can impersonate the storage group or mailbox store – it does not mean that you can Send-As for all accounts in that storage group or mailbox store. That permission must be set on a per-mailbox basis.</p>
<p>There is a good whitepaper for understanding store and AD permissions written against Exchange 2000 and Exchange 2003. It is still at microsoft.com/downloads : <a href=”http://www.microsoft.com/downloads/details.aspx?familyid=2ae266f0-16b7-40d7-94d9-c8be0e968a57&amp;displaylang=en” target=”_blank”>Working with Store Permissions in Microsoft Exchange 2000 Server and Exchange Server 2003</a>. However, it is a little dated and there are a couple of errors in the document. The basics are still good, but Exchange 2007  reintroduced the idea of setting actual permissions on the mailbox (in 2000 and 2003, you could set mailbox permissions only before the mailbox was created, everything else was set against the AD user object). Exclusive of the impact of Role Based Access Control (commonly referred to as RBAC), I believe that Exchange Server 2010 continues to follow the Exchange Server 2007 rules.</p>
<p>While we have not discussed them here, note that the store-level permissions available to mailboxes are the same permissions available to public folders (excepting only ExternalAccount). And, with the exception of SendAs and ExternalAccount, these are the same permissions available to subfolders within a mailbox and a public folder.</p>
<p><strong>Implementation note:</strong></p>
<p>From a technical perspective, the AD attributes actually represent Access Control Entries set within the nTSecurityDescriptor object assigned to a user object within Active Directory.</p>
<p>For more information on Access Control Lists, Access Control Entries, and the nTSecurityDescriptor object, see <a href=”http://theessentialexchange.com/blogs/michael/archive/2007/11/13/displaying-security-on-active-directory-exchange-and-registry-objects.aspx” target=”_blank”>Displaying Security on Active Directory, Exchange, and Registry Objects</a> and <a href=”http://theessentialexchange.com/blogs/michael/archive/2007/11/13/windows-permissions-access-control-lists.aspx” target=”_blank”>Windows Permissions – Access Control Lists</a>.</p>
<p>Until next time…</p>
<p>If there are things you would like to see written about, please let me know!</p>
<p>P.S. Thanks to Ross Smith, IV of Microsoft for clarifying a couple of points contained in this article.</p>
<hr><p>Follow me on twitter: @EssentialExch</p>