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

Powered by Squarespace

Friday
Aug222008

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!

Thursday
Jul242008

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)\$($_.name)"] = 0;
Get-Mailbox -Database $_ -ResultSize Unlimited | `
%{$mailboxcount["$($_.database)"]++}}
$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

or

"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)\$($_.name) 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

mailboxcount["$($_.database)"]++

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
$mailboxcount[$mbdatabase]++

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.

[UPDATE]

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.

Monday
Jul212008

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!

Friday
Jul042008

New-TeamBlog -post "Introduction to PowerShell"

My team has started a blog talking about a) the products that we work with, b) the services we provide, and c) anything else that we think would be of interest to people reading about a and b. I'd imagine that at a good percentage of my contributions are going to be looking at PowerShell, so I figured I'd pull together a few bits and pieces that I've written here and there and post an Introduction to Windows PowerShell. This is it...

PowerShell is a command shell and language focusing on Windows system administration. It can be used interactively to get immediate results, or you can write complex scripts and do batch processing. Although it is still not used as much as it should be, PowerShell isn't a brand new product; it's been around for a couple of years and version 2 is currently available in its 2nd Community Technical Preview.

Now that PowerShell is part of Microsoft's Common Engineering Criteria (meaning that product teams pretty-much have to incorporate PowerShell into their new releases), and being incorporated as a feature in Windows Server 2008, you'll see PowerShell usage sky-rocket! (It's worth also saying that Microsoft aren't the only ones adding PowerShell support to their products - VMWare, IBM, Citrix and others see the potential of managing their products this way.)

The thing that sets PowerShell apart from other shells is that it uses pipelines of objects, not of text. It is build on the .NET Framework, but you don't need to have a developer's appreciation of .NET to work with PowerShell. In fact one of the best ways of getting started with PowerShell is to just run it each time you were about to run cmd.exe - many of the things you'd want to do there work in PowerShell. If you've been a *nix admin in the past, you'll find that some of the commands you're familiar with work in PowerShell too.

For example, the PowerShell cmdlet (pronounced "command-let") to list the contents of a folder, Get-ChildItem, has aliases built in, so that you can use either dir or ls in its place. The interesting thing with PowerShell is that it has providers which let you access other repositories (referred to as PSDrives) in the same way as the file system, so you can do this:

cd HKLM:\software
dir

...and see the contents of a registry hive! (Note that if you want to do a recursive directory listing, the parameters are different to the dir in cmd.exe, so you'll want to check the help to see what you can do with the cmdlets)

Give it a go and I'm sure you'll soon find your way around. To get started, Get-Command gives you a list of the available cmdlets; Get-Help [cmdlet] tells you what they do; Get-Member lists the properties and methods of an object; Get-PSDrive lists the PSDrives that are available to you. Knowing those cmdlets is enough to get you quite a distance.

The TechNet Script Center has a load of great resources for IT Pros at and there's a fantastic community building around PowerShell, with a UK User Group (run by Richard Siddaway who is a PowerShell MVP - details on his blog), numerous PowerShell bloggers, and community sites. There are also a bunch of PowerShell books, most of which are pretty good, but if you're just looking for one I'd recommend Lee Holmes' PowerShell Cookbook.

If you don't find that there's support built in to products or PowerShell itself for what you want to do, there may be a 3rd party snap-in that could help. I'm making used of the free Active Directory cmdlets from Quest Software, the free Group Policy management cmdlets from SDM Software and the PowerShell Community Extensions - a large suite of additional cmdlets, providers, functions and more.

I'll leave you with a quick PowerShell example. This is a cut down version of something that we used yesterday to enumerate the members of all the AD groups in a particular OU and save the listing for each group in a separate file named after the group:

Get-QADGroup -SearchRoot "OU=Groups,OU=ISS,DC=campus,DC=ncl,DC=ac,DC=uk" | %{$name = $_.name; Get-QADGroupMember $name | Out-File "$name.txt"}

It has probably wrapped in your browser, but that's just with one line of PowerShell and frankly a good chunk of it is specifying the OU that we're looking in! If you want to run that, you'll just need to change the OU and have the Quest AD cmdlets installed. I won't explain how it works here - I just wanted to show how much you can do with so little PowerShell. How much effort/code would it take to achieve that task another way?!

Tuesday
Jul012008

Updated PowerShell resources from TechNet Script Center

If you write scripts on Windows systems, you should definitely already have the TechNet Script Center bookmarked as they have a huge amount of high quality resources for the full range of Windows admin scripting technologies (as well as the fun of the Scripting Games). Their PowerShell Script Center is a great hub of links and free tools, articles and tips and should be one of the first stops you make in learning PowerShell.

Two of the more comprehensive resources on Script Center have just been updated:

The Windows PowerShell Owner's Manual, which has previously had chapters on Getting Started, Customizing the Console, Shortcut Keys, Piping and the Pipeline and Running Scripts, has had two additional chapters published - The Windows PowerShell Profile and Windows PowerShell Aliases. This is great material, written with the trademark humour of the Scripting Guys and well worth a read.

The second updated resource is The VBScript-to-Windows PowerShell Conversion Guide. If you're already a fluent VBScript scripter and want to update your skills to PowerShell then this is a useful reference, although I think from the PowerShell purist's point of view it probably isn't the best way to learn PowerShell since there's a paradigm shift that translating VBScript directly doesn't take advantage of. That said, if you know how to do something in VBScript and you want to know how to do it in PowerShell, this is the place to come.

The Conversion Guide has been expanded from just VBScript Commands, with new sections covering Dictionary Object Methods and Properties, FileSystemObject Methods and Properties and Windows Script Host Methods.