Saturday, December 20, 2008

FreeBusy In/Out Board for Exchange 2007 using EWS and Powershell

Update version see here

Firstly this isn't a new idea there's been a similar script on CDO live http://www.cdolive.com/inoutboard.htm for years that uses CDO 1.2. I've had a couple of people ask about doing this in Powershell and EWS using the new availability service on 2007 so here it is.

How does it work

To get the Free/Busy status of another mailbox in Exchange you need to make use of the getuseravailability operation in EWS. To be able to query another users availability you must have been granted rights to the free/busy information for that users Mailbox by default the default ACL gives users the right to see Free/Busy availability but not the newer Subject and Location information made available in 2007. To change the default Free/Busy Permission you can use something like this post A more granular approach would be to assign these rights to a specific user or group eg something like this would add a group to calendar if it didn't exist


[void][Reflection.Assembly]::LoadFile("c:\temp\EWSUtil.dll")
$mbMailboxEmail = "krudd@domain.com"
$GrouptoAdd = "DefaultPermsGroup@domain.com"
$exists = $false
$calutil = new-object EWSUtil.CalendarUtil($mbMailboxEmail,$false, "username","password","domain",$null)
for ($cpint=0;$cpint -lt $calutil.CalendarDACL.Count; $cpint++){
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUserSpecified -ne $true){
$sidbind = "LDAP://&ltsid=">"
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
if ($aceuser.objectClass[1] -eq "group"){
if ($aceuser.mail -eq $GrouptoAdd){$exists = $true}
}
}
}
if($exists -eq $false){
"Group Doesn't Exist Adding"
$calutil.CalendarDACL.Add($calutil.Reviewer($GrouptoAdd))
$calutil.FreeBusyDACL.Add($calutil.FolderReviewer($GrouptoAdd))
$calutil.Update()
}
else{
"Group Exists Do Nothing"
}



Okay once you have the permissions sorted you can look at actually querying the free/busy information. To do this I've wrapped a few methods that do this using some EWS code in my EWSUtil powershell library so once you've made a connection to the mailbox all you need is to fire the method with firstly an array of mailbox email addresses you want to include in you FB board the duration which consists of the start and end time and the last parameter is the granularity for the Free/Busy time slot eg 60,30 or 15 minutes. So a example of this if you wanted to produce a freeBusy board that create a board for 8:30 to 5:00 with a 30 minute slot period you would need something like this

$drDuration = new-object EWSUtil.EWS.Duration
$drDuration.StartTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 08:30"))
$drDuration.EndTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 17:30"))
$fbents = $ewc.GetAvailiblity($mbs, $drDuration, 30)

This would produce something that look like this

Actually thats jumping ahead a little we also need some code to generate a String array of Mailbox email addressses to use with the GetAvailiblity method. There are several methods to do this using a custom ADSI query, Group Membership or using Get-Mailbox with a specific filter. The limitaiton here is that you need to limit that array you feed into the method to no more then 100 mailboxes. An example of using Get-Mailbox to build an array would look like

$mbHash = @{ }

get-mailbox | foreach-object{
if ($mbHash.ContainsKey($_.WindowsEmailAddress.ToString()) -eq $false){
$mbHash.Add($_.WindowsEmailAddress.ToString(),$_.DisplayName)
}
}
$mbs = @()
foreach($key in $mbHash.keys){
$mbs += $key
}

The second hashtable I use later to map email address's back to display names.

What the method returns is a nested hashtable for the slots you requested with the Subject and Location information indexed to each of the time slot. I've used the Time and Location information as Mouse over titles in the above board so for example when you mouse over a cetain table cell you will get information about that segment eg


Okay thats it some relatively simple powershell code does the color mapping and exporting to a Html file. I've put a download of the script here. To use this script you need to use the latest EWSUtil Powershell library which you can download from http://msgdev.mvps.org/exdevblog/ewsutil.zip. For more information on using the library and connection and authentication options this is documented in another post. The code itself looks like

[void][Reflection.Assembly]::LoadFile("C:\temp\EWSUtil.dll")

$casUrl = "https://servername/ews/exchange.asmx"
$mbHash = @{ }

get-mailbox | foreach-object{
if ($mbHash.ContainsKey($_.WindowsEmailAddress.ToString()) -eq $false){
$mbHash.Add($_.WindowsEmailAddress.ToString(),$_.DisplayName)
}
}
$mbs = @()
foreach($key in $mbHash.keys){
$mbs += $key
}

$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$false, "usernames", "password", "domain",$casUrl)
$drDuration = new-object EWSUtil.EWS.Duration
$drDuration.StartTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 08:30"))
$drDuration.EndTime = [DateTime]::Parse([DateTime]::Now.ToString("yyyy-MM-dd 17:30"))
$fbents = $ewc.GetAvailiblity($mbs, $drDuration, 30)
$frow = $true
foreach($key in $fbents.keys){
if ($frow -eq $true){
$fbBoard = $fbBoard + "<table><tr bgcolor=`"#95aedc`">" +"`r`n"
$fbBoard = $fbBoard + "<td align=`"center`" style=`"width=200;`" ><b>User</b></td>" +"`r`n"
for($stime = $drDuration.StartTime;$stime -lt $drDuration.EndTime;$stime = $stime.AddMinutes(30)){
$fbBoard = $fbBoard + "<td align=`"center`" style=`"width=50;`" ><b>" + $stime.ToString("HH:mm") + "</b></td>" +"`r`n"
}
$fbBoard = $fbBoard + "</tr>" + "`r`n"
$frow = $false
}
for($stime = $drDuration.StartTime;$stime -lt $drDuration.EndTime;$stime = $stime.AddMinutes(30)){
$valuehash = $fbents[$key]
if ($stime -eq $drDuration.StartTime){
$fbBoard = $fbBoard + "<td bgcolor=`"#CFECEC`"><b>" + $mbHash[$valuehash[$stime.ToString("HH:mm")].MailboxEmailAddress.ToString()] + "</b></td>" + "`r`n"
}
switch($valuehash[$stime.ToString("HH:mm")].FBStatus.ToString()){
"0" {$bgColour = "bgcolor=`"#41A317`""}
"1" {$bgColour = "bgcolor=`"#52F3FF`""}
"2" {$bgColour = "bgcolor=`"#153E7E`""}
"3" {$bgColour = "bgcolor=`"#4E387E`""}
"4" {$bgColour = "bgcolor=`"#98AFC7`""}
"N/A" {$bgColour = "bgcolor=`"#98AFC7`""}
}
$title = "title="
if ($valuehash[$stime.ToString("HH:mm")].FBSubject -ne $null){
if ($valuehash[$stime.ToString("HH:mm")].FBLocation -ne $null){
$title = $title + "`"" + $valuehash[$stime.ToString("HH:mm")].FBSubject.ToString() + " " + $valuehash[$stime.ToString("HH:mm")].FBLocation.ToString() + "`" "
}
else {
$title = $title + "`"" + $valuehash[$stime.ToString("HH:mm")].FBSubject.ToString() + "`" "
}
}
else {
if ($valuehash[$stime.ToString("HH:mm")].FBLocation -ne $null){
$title = $title + "`"" + $valuehash[$stime.ToString("HH:mm")].FBLocation.ToString() + "`" "
}
}
if($title -ne "title="){
$fbBoard = $fbBoard + "<td " + $bgColour + " " + $title + "></td>" + "`r`n"
}
else{
$fbBoard = $fbBoard + "<td " + $bgColour + "></td>" + "`r`n"
}

}
$fbBoard = $fbBoard + "</tr>" + "`r`n"

}
$fbBoard = $fbBoard + "</table>" + " "
$fbBoard | out-file "c:\fbboard.htm"

14 comments:

Anonymous said...

Wow! Been looking for this for a while! Great work! I have a newbie question: how do i change, for example, the font used in the produced htm-file?

Anonymous said...

Glen,

I can't seem to make this script to run. I change the following variables, but I get an error (see below)
...
$casUrl = "https://casw03dc01.TAA.net/ews/exchange.asmx"
...
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$false, "administrator",

"Pa$$w0d!", "TAA",$casUrl)

Error that I get is:

Exception calling "GetAvailiblity" with "3" argument(s): "System.Web.Services.P
rotocols.SoapException: Microsoft.Exchange.InfoWorker.Common.Availability.Ident
ityArrayTooBigException: There are too many target users in the EmailAddress ar
ray. The allowed size = 100; the actual size = 180. ---> Microsoft.Exchange.Inf
oWorker.Common.Availability.IdentityArrayTooBigException: There are too many ta
rget users in the EmailAddress array. The allowed size = 100; the actual size =
180.

This is not the full error message tht I get. The atual error message is very long, but I figured I'd tart here.

Thank you.
Happy Hollydays!

Glen said...

It looks like your trying to report on more then 100 mailboxes you need to limit the query to under 100 or add some extra code to call the availibility in batches of 100. Let me know if you dont understand what i mean by batching.

Cheers
Glen

Anonymous said...

Glen,

I have no idea what batchng is. I do want to be able to see schedule for all users or have an ability o chose which users I can see the schedule for.

Thank you.

Glen said...

Okay I've updated the download with a new version that handles batching I tested it on server with over 1000 users and it worked fine although its was slow.

If you wanted to limit which users it runs on I'd suggest using a group put all the users you want to include in the board in a group and then you can replace the Get-Mailbox part with Get-DistributionGroupMember eg

Get-DistributionGroupMember -Identity "Primary Sales Group" | foreach-object{
if ($mbHash.ContainsKey($_.PrimarySmtpAddress.ToString()) -eq $false){
$mbHash.Add($_.PrimarySmtpAddress.ToString(),$_.DisplayName)
}
}

Cheers
Glen

Anonymous said...

Glen,

Great. It works perfectly now. A coule of suggestions to improve functionality. It would be great that upon script execution it would dsplay some sort of list of all user accounts in the company and ability to choose for which users you want to display free/busy information. Then after execution of the selectin is complete, the web page with results opens up automatically.

Thank you for all your GREAT work.
Sharapov

John McLear said...

Glen, as you know I have been wanting to do this for quite some time. I will implement/tweek/publish and feed back with my changes.

Thanks for your efforts on this!

Sean said...

We have employees who work across several offices and currently they have to manually update a shared calendar with their in/out information including which office they are going to be in.

Would it be possible to modify this (trivially) so that it can pull information from say an All-Day event (with Free or Out of Office set) that indicates which office the user will be in for a particular day?

I'm not familiar with the EWS functionality but it looks like you could work with the FBSubject or FBLocation to do this maybe?

Glen said...

This queries the users freebusy information if they put the office they are going to be in their calendar appointments then maybe. It wont query a central calendar you would use just a normal calendar query to do this.

Cheers
Glen

John McLear said...

Hey Glen, I got this working closer to how I want it, been playing around a lot today. The hover over only shows one of the current things, if there is +1 event happening at one point it does not show both events. Is it possible to change this please? :)

John McLear said...

This now works with multiple events, seperated with |, I recommend doing a string replace with something like a




Cheers Glen

John McLear said...

I made a weekly/per user version of this:

Example here:
http://rss.primaryemail.co.uk/wil.htm

Code here:
http://johnmclear.blogspot.com/2009/04/exchange-web-based-calendar-without.html

Anonymous said...

Hi Glen

I try to run your script, but I always get the following error:
Exception calling "GetAvailiblity" with "3" argument(s): "The request failed with HTTP status 401: Unauthorized."

I use as query: get-mailbox -identity testuser | foreach-object{....

and ...$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$false, "testuser", "testuserpassword", "organsiation",$casUrl)...

Any Idea?

Unknown said...

Hi Glen,

I am working on the script. Thank you for it. It is realy helping me. However, when I run the script I don't see all the users in the list. I have checked the availibility settings in Exchange, but that looks good. Can you help me?