North East Bytes - a Microsoft technology usergroup in North East England.

Powered by Squarespace


Who needs vowels anyway?!

I am, very openly, a gadget fan - always have been - and for a long time, my preferred online source of gadget news has been Engadget. I'm also very much into listening to podcasts, so the Engadget podcast was definitely on my list of must-listen-to shows until it dried up about a year ago, having been not exactly weekly for a while before that. Seems that hosts Peter Rojas and Ryan Block were just too busy to find a matching slot in both their schedules to record it, which was a shame.

Ever since Pandora was blocked for users outside North America, one of my main sources of music in the office has been Peter Rojas' ad-supported online record label - RCRD LBL. I'm a big fan of the site (and one of my favourite tracks of the last couple of years has come from there - George Pringle's Carte Postale). If you like music, you've got to check out RCRD LBL - it's all free to download, so there's no reason not to!

Following on with the theme of scrapping the vowels, the two guys are back to working together with a new venture called gdgt.

In their own words: "gdgt is the new consumer electronics site by Peter Rojas and Ryan Block -- the guys behind Engadget and Gizmodo. We're still prepping things (no, this isn't the final site!), but we've got a weekly podcast you can listen to in the meantime." Peter says gdgt isn't a gadget blog, so it'll be interesting to see what the site turns into!

I listened to the first episode of the gdgt podcast this morning on the way to work and it was instantly like the last year without the Engadget podcast never happened. :-) Peter and Ryan said that episode 2 will be netbook-heavy, which is great because I'm toying with the idea of getting one - I'm putting off my decision until I've had a chance to listen to what they say.

Glad I didn't miss @Veronica's tweet about this!


Setting AD logon hours in PowerShell

Or "How to disable an AD user account and still allow delivery to their Exchange mailbox"...

I'd meant to blog about this some time ago, but was eventually reminded when Richard beat me to the punch last week with his post about setting AD logon hours in PowerShell - read Richard's post first, then come back here... ;-)

We make extensive use of logon hours on our accounts, but rather than using Richard's method of setting the value, we have a collection of what I call template accounts, and we copy the values from them. We've got one template that's setup for "always allowed", one for "never allowed" and another template for "custom" logon hours.

To use those, I do something like this: (NB. THIS CODE NO LONGER WORKS. SEE THE UPDATE BELOW)

#requires -pssnapin Quest.ActiveRoles.ADManagement
Get template user logon hours
$userLogonHoursEnabled = Get-QADUser enabledtemplate | Select LogonHours
$logonHours = $userLogonHoursEnabled.logonHours
# Set logon hours on user in variable $username
Get-QADUser $username | Set-QADUser -ObjectAttributes @{logonHours = $logonHours}


So, why would we do that? Well, two reasons really...

We never actually disable a user account; instead we'll set the logon hours to never allow logon when we want to block someone's access. The reason for that is that disabling a user in the traditional way stops delivery to their mailbox. That may be desirable in some organisations, but we only disable for disciplinary reasons and they usually get access back pretty quickly, and we don't want them to miss some vital message that was sent in the meantime.

Secondly, by using that third, "custom" template, we can give someone permission to alter the logon hours on that template account using the GUI, so that they don't have to work out the numbers for the bytes and, more importantly, don't need access to change the PowerShell script; they can just run the script to apply the custom template to a collection of accounts.


The PowerShell code above was written with an old version of Quest's AD cmdlets. While I'd noticed this, I hadn't got round to changing my code before Jonathan Medd from the Get-Scripting Podcast contacted me and said that he'd discussed the above example with some of the excellent folk from the PowerShell community on the forums. Together they've come up with solutions that work with the current version of the Quest AD cmdlets ( in both PowerShell version 1 and V2 (as it currently stands at CTP2).


Rather than me re-posting here, check out the thread on the forums so you can see where the credit should lie (with Shay, Aleksandar and Andrey).


New PowerShell Book

My good friend Richard Siddaway, PowerShell MVP and founder of the UK PowerShell User Group, is writing a new book called PowerShell in Practice, aimed at systems administrators to show them how PowerShell can make their life easier.

The publisher, Manning, has started making chapters available through their Early Access Program, so you get the sections as they become ready. The first chapter, PowerShell Fundamentals, is available to read for free, so definitely go and check it out!


Spreading Exchange 2007 mailboxes between databases with PowerShell

At this time every year, I have to arrange the provisioning of Active Directory user accounts and Exchange mailboxes for thousands of students. This year, I'm completing the whole of that process using Windows PowerShell. This means spreading them evenly over a set of Exchange mailbox databases.

The first step in this is finding out how many mailboxes are already in each database, which I'm doing like this:

#requires -pssnapin Microsoft.Exchange.Management.PowerShell.Admin
$mailboxcount = @{}
Get-MailboxDatabase -Server EXSRV02 | ?{$_.recovery -eq $FALSE} | `
%{$mailboxcount["$($_.storagegroup)\$($"] = 0;
Get-Mailbox -Database $_ -ResultSize Unlimited | `
$mailboxcount.GetEnumerator() | sort name

Breaking that down...

I'm not actually running this in the Exchange Management Shell. My profile imports the necessary Exchange snapin to the standard shell (along with a few other things), but I'm just checking that it's registered first by using the #requires statement because otherwise we wouldn't get very far (see my friend Aleksandar's post on #requires for more explanation).

Then we declare an empty hashtable, $mailboxcount, which we're going to use to hold the mailbox databases and the number of mailboxes in each.

In my example script, I'm specifying a server name to the Get-MailboxDatabase cmdlet, but you could equally leave off the -server parameter, which will get all of the mailbox stores (databases) in your Exchange 2007 environment. You could also specify a set of servers by reading them from a file, or passing an array of strings containing server names, to the get-mailboxserver cmdlet and then into get-mailboxdatabase like this:

get-content mailboxserver.txt | get-mailboxserver | get-mailboxdatabase


"EXSRV01","EXSRV02" | getmailboxserver | get-mailboxdatabase

That gets all of the databases on the servers that we want to look at, but it also includes Recovery Storage Groups, so we're filtering those out by piping our mailbox databases through...

?{$_.recovery -eq $FALSE}

Next, we're piping each database into a script block by using the foreach-object cmdlet (aliased to %). This script block contains two lines of PowerShell, separated with a semi-colon. The first populates the $mailboxcount hashtable with each of the database names. It does it in a slightly strange manner: $($_.storagegroup)\$($ because this the same format as we'll get from the mailbox's database property and we need them to match up, and also because this produces the most useful output - SERVERNAME\STORAGEGROUP\DATABASE. We're assigning each database an initial value of 0.

That line may be optional to you, depending on how you want this to work. If you want to see any empty databases, you need it in there, but if you've added a new database you're not quite ready to use just yet, you can leave it out and the empty database will be left out of your results (and therefore the allocations in the next phase).

The next line uses the Get-Mailbox cmdlet, specifying the -database parameter to return all the mailboxes for the current database and these are passed down the pipeline to increment the value for that database in the hashtable with


The mailbox's database property is enclosed in $() so that it is evaluated before it's treat as the key string, otherwise you'd end up with a hashtable full of MAILBOXNAME.database, with a value of 1. That part of the script takes longer the more mailboxes you have.

That done, we're displaying the contents of the hashtable, which is better viewed sorted, so we're using the GetEnumerator method and sorting by name to get something like:

Name                           Value
----                           -----
EXSRV02\SG01\MS01              199
EXSRV02\SG02\MS02              201
EXSRV02\SG03\MS03              199
EXSRV02\SG04\MS04              200
EXSRV02\SG05\MS05              189
EXSRV02\SG06\MS06              188
EXSRV02\SG07\MS07              200
EXSRV02\SG08\MS08              172
EXSRV02\SG09\MS09              195
EXSRV02\SG10\MS10              183
EXSRV02\SG11\MS11              77

Alternatively, you can sort by value to see the most used databases by using

$mailboxcount.GetEnumerator() | sort value -desc

which will show largest down to smallest, like this:

Name                           Value
----                           -----
EXSRV02\SG02\MS02              201
EXSRV02\SG07\MS07              200
EXSRV02\SG04\MS04              200
EXSRV02\SG01\MS01              199
EXSRV02\SG03\MS03              199
EXSRV02\SG09\MS09              195
EXSRV02\SG05\MS05              189
EXSRV02\SG06\MS06              188
EXSRV02\SG10\MS10              183
EXSRV02\SG08\MS08              172
EXSRV02\SG11\MS11              77

That's a useful script for seeing how many mailboxes are in each database, although you may want to discount the disconnected mailboxes, i.e. those that are waiting to be purged because their associated user object has been deleted (see my earlier post about orphaned mailboxes).

Now on to adding new mailboxes...

Now that we've got that hashtable, we don't need to query the system again for this round of provisioning. All we're going to do is, for each new mailbox we create, grab the database with the fewest mailboxes, increment the value in the hashtable, like so...

$mbdatabase = ($mailboxcount.GetEnumerator() | sort value | select -first 1).key

and this gives us the database to use for the new mailbox in our variable
$mbdatabase, which we can give to the Database parameter of the New-Mailbox cmdlet, which will create the mailbox AND the user object for us, or the Enable-Mailbox cmdlet, which will give a mailbox to an existing user.


Scott Bueffel contacted me to say that he's written a new version of this code to work in his large Exchange environment which is far quicker to run as a result of using bypassing the Exchange cmdlets to get the minimum amount of data count the mailboxes and just update the hash table. I'd recommend checking out his post.

I'm not so worried about the speed because these days I'm just rebuilding that hash table once a week and saving it in a file which the mailbox creation script reads and updates as it adds more mailboxes. I still need to re-do the counts in full because our current method of user/mailbox removal doesn't update the counts. If I remember correctly, it was /\/\o\/\/'s suggestion to store the hash table in a file, but if I'm wrong and someone else deserves the credit, get in touch.


Get-ActiveSyncDeviceStatistics (32-bit) cmdlet forces device re-sync

Update (solution):

A very helpful Microsoft support person told me that there was a problem in this area
which was fixed by Exchange 2007 SP1, so the problem is more
specifically that I was apparently using a pre-SP1 version of the 32-bit cmdlet, even though the rest of the Exchange 2007 infrastructure was patched to SP1. I've now applied the 32-bit version of Exchange 2007 SP1 to the 32-bit server and everything now works fine.

Note to self - if you've got client tools installed somewhere for remote managment, make sure you patch them as well as the servers you're managing!

Original Post:
Here's a bit of a gotcha that I've not seen documented elsewhere, so I wanted to get it out there for anyone else stumbling across it...

Last week I was prompted by Adam Smith's post to check how many iPhones running the new 2.0 software had begun to use Exchange ActiveSync against our systems. I looked to tweak a script that I'd pulled together previously by editing in PowerShell Plus (my PowerShell tool of choice), but I was working on 64-bit Server 2008 and PowerShell Plus won't use 64-bit plug-ins like the Exchange cmdlets. I decided that it would be easier to edit the script in my old trusty 32-bit Server 2003 machine which has the 32-bit Exchange 2007 Management Tools (from the Microsoft Download Center) installed.

I've worked with those tools in PowerShell Plus frequently in the past - it's a handy environment to develop scripts; although I now run everything in production on the 64-bit server, so I rarely actually use the 32-bit versions of the Exchange cmdlets to do anything. I'd figured that using a "getter" to grab some statistics couldn't do any harm. Guess again!

Every time I ran Get-ActiveSyncDeviceStatistics from from the 32-bit tools, the next time a device would try to use EAS, it'd receive the message "There has been a change made on your server that requires you to re-synchronise all items on your device. Please perform a manual sync." with support code 0x80883001.

When the support queries started coming in shortly after, I initially thought it must be a coincidence since I wasn't making any changes to the server, but I had a nagging feeling that my script must be triggering something in the Exchange infrastructure that was having this effect.

I started testing running the cmdlet against mine and a couple of other mailboxes and true enough it was recreating the problem totally consistently. It was almost by accident that part way through my testing I found myself using a shell on my 64-bit server and the problem stopped occurring. Going back and forth between testing the two: the 64-bit version of the cmdlet is fine; the 32-bit version always causes a problem.

I'm not sure if you'd call this a bug or a perfectly reasonably explainable feature, but it had me stumped for a little while!