Saturday, March 29, 2008

Default Calendar permission Powershell Gui for Exchange 2007

****Note if your using Exchange 2010 SP1 see which is a version that use the new Exchange Management Shell cmdlets *****

This is a sample script that uses the calendar permission helper class I posted here. This script basically presents a Winform that can be used to firstly enumerate all the default calendar permissions of all mailboxes on an Exchange Server and then you can do a multiple select and set a new default calendar permission level for one or more mailboxes. The script gives the option of specifying the authentication setting to use and the Cas Server you want to use. By default it tries to locate a cas server using the get- WebServicesVirtualDirectory cmdlet eg

[array]$calurls = Get-WebServicesVirtualDirectory
$strRootURI = $calurls[0].InternalUrl.AbsoluteUri

Using EWS impersonation allows you to set the calendar setting on a mailbox that the account running the script doesn’t have permissions to access to setup impersonation in EWS have a look at this from the Exchange SDK. The Specify Credentials check box allows you to specify the user credential to use when running the EWS operations. This script does rely on the Exchange-cmdlet's get-mailbox and get-user so it needs to be run from within the Exchange Management Shell.

If you select to use Exchange Impersonation the script will check to see if the account its going to impersonate first is disabled if it is then impersonation will not be use because you can’t impersonate a disable account.

To use the script you need to download the library from the other post as I marked in that post this code itself is very untested and I don’t consider it stable and or safe for use in a production environment I can only recommend that you use it in a test/dev environment and as a guide to building your own applications. The library needs to be located in the directory referenced in the following line


I’ve put a download of this script here the code itself looks like

Warning this code may cause global warming if you use the Air con when you don't need to

[array]$calurls = Get-WebServicesVirtualDirectory
$strRootURI = $calurls[0].InternalUrl.AbsoluteUri

function GetPerms(){

get-mailbox -server $snServerNameDrop.SelectedItem.ToString() -ResultSize Unlimited | foreach-object{$colnum
$defperm = ""
$unUsername = ""
$pnPpassword = ""
$dnDomainName = ""
$inInpersonate = $false
if ($seAuthCheck.Checked -eq $true){
$unUsername = $unUserNameTextBox.Text
$pnPpassword = $unPasswordTextBox.Text
$dnDomainName = $unDomainTextBox.Text
if ($seImpersonationCheck.Checked -eq $true){
$uoUser = [ADSI]("LDAP://" + $_.DistinguishedName.ToString())
if ($uoUser.PSBase.InvokeGet("AccountDisabled") -ne $true){$inInpersonate = $true }

$calutil = new-object EWSUtil.CalendarUtil($_.WindowsEmailAddress,$inInpersonate,$unUsername,$pnPpassword,$dnDomainName,$unCASUrlTextBox.text)
for ($cpint=0;$cpint -lt $calutil.CalendarDACL.Count; $cpint++){
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUserSpecified -eq $true){
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Default){
write-host "Processing : " + $_.WindowsEmailAddress
$defperm = $calutil.enumOutlookRole($calutil.CalendarDACL[$cpint])

$dgDataGrid.DataSource = $logTable


Function UpdatePerms{
$unUsername = ""
$pnPpassword = ""
$dnDomainName = ""
$inInpersonate = $false
if ($seAuthCheck.Checked -eq $true){
$unUsername = $unUserNameTextBox.Text
$pnPpassword = $unPasswordTextBox.Text
$dnDomainName = $unDomainTextBox.Text
if ($seImpersonationCheck.Checked -eq $true){
$mbchk = get-mailbox $dgDataGrid.Rows[$dgDataGrid.CurrentCell.RowIndex].Cells[1].Value
$uoUser = [ADSI]("LDAP://" + $mbchk.DistinguishedName.ToString())
if ($uoUser.PSBase.InvokeGet("AccountDisabled") -ne $true){$inInpersonate = $true }

if ($dgDataGrid.SelectedRows.Count -eq 0){
$mbtoSet = $dgDataGrid.Rows[$dgDataGrid.CurrentCell.RowIndex].Cells[1].Value
$calutil = new-object EWSUtil.CalendarUtil($mbtoSet,$inInpersonate,$unUsername,$pnPpassword,$dnDomainName,$unCASUrlTextBox.text)
switch ($npNewpermDrop.Text){
"None" {$calutil.CalendarDACL.Add($calutil.NonePermissions("default"))}
"FreeBusyTimeOnly" {$calutil.CalendarDACL.Add($calutil.FreeBusyTimeOnly("default"))}
"FreeBusyTimeAndSubjectAndLocation" {$calutil.CalendarDACL.Add($calutil.FreeBusyTimeAndSubjectAndLocation("default"))}
"Reviewer" {$calutil.CalendarDACL.Add($calutil.Reviewer("default"))}
"Contributer" {$calutil.CalendarDACL.Add($calutil.Contributer("default"))}
"Author" {$calutil.CalendarDACL.Add($calutil.AuthorPermissions("default"))}
"NonEditingAuthor" {$calutil.CalendarDACL.Add($calutil.NonEditingAuthorPermissions("default"))}
"PublishingAuthor" {$calutil.CalendarDACL.Add($calutil.PublishingAuthorPermissions("default"))}
"Author" {$calutil.CalendarDACL.Add($calutil.AuthorPermissions("default"))}
"Editor" {$calutil.CalendarDACL.Add($calutil.EditorPermissions("default"))}
write-host "Permission updated" + $npNewpermDrop.Text
$lcLoopCount = 0
while ($lcLoopCount -le ($dgDataGrid.SelectedRows.Count-1)) {
$mbtoSet = $dgDataGrid.SelectedRows[$lcLoopCount].Cells[1].Value
$calutil = new-object EWSUtil.CalendarUtil($mbtoSet,$inInpersonate,$unUsername,$pnPpassword,$dnDomainName,$unCASUrlTextBox.text)
switch ($npNewpermDrop.Text){
"None" {$calutil.CalendarDACL.Add($calutil.NonePermissions("default"))}
"FreeBusyTimeOnly" {$calutil.CalendarDACL.Add($calutil.FreeBusyTimeOnly("default"))}
"FreeBusyTimeAndSubjectAndLocation" {$calutil.CalendarDACL.Add($calutil.FreeBusyTimeAndSubjectAndLocation("default"))}
"Reviewer" {$calutil.CalendarDACL.Add($calutil.Reviewer("default"))}
"Contributer" {$calutil.CalendarDACL.Add($calutil.Contributer("default"))}
"Author" {$calutil.CalendarDACL.Add($calutil.AuthorPermissions("default"))}
"NonEditingAuthor" {$calutil.CalendarDACL.Add($calutil.NonEditingAuthorPermissions("default"))}
"PublishingAuthor" {$calutil.CalendarDACL.Add($calutil.PublishingAuthorPermissions("default"))}
"Author" {$calutil.CalendarDACL.Add($calutil.AuthorPermissions("default"))}
"Editor" {$calutil.CalendarDACL.Add($calutil.EditorPermissions("default"))}
write-host "Permission updated" + $npNewpermDrop.Text
$lcLoopCount += 1
write-host "end PermUpdate"
write-host "Refresh Perms"

$form = new-object System.Windows.Forms.form
$form.Text = "Calender Permission Enum Tool"
$Dataset = New-Object System.Data.DataSet
$logTable = New-Object System.Data.DataTable
$logTable.TableName = "ActiveSyncLogs"

# Add Server DropLable
$snServerNamelableBox = new-object System.Windows.Forms.Label
$snServerNamelableBox.Location = new-object System.Drawing.Size(10,20)
$snServerNamelableBox.size = new-object System.Drawing.Size(70,20)
$snServerNamelableBox.Text = "ServerName"

# Add Server Drop Down
$snServerNameDrop = new-object System.Windows.Forms.ComboBox
$snServerNameDrop.Location = new-object System.Drawing.Size(80,20)
$snServerNameDrop.Size = new-object System.Drawing.Size(130,30)
get-exchangeserver | ForEach-Object{$snServerNameDrop.Items.Add($_.Name)}

# Add Get Perms Button

$gpgetperms = new-object System.Windows.Forms.Button
$gpgetperms.Location = new-object System.Drawing.Size(220,20)
$gpgetperms.Size = new-object System.Drawing.Size(85,23)
$gpgetperms.Text = "Enum Perms"

# Add New Permission Drop Down
$npNewpermDrop = new-object System.Windows.Forms.ComboBox
$npNewpermDrop.Location = new-object System.Drawing.Size(350,20)
$npNewpermDrop.Size = new-object System.Drawing.Size(190,30)

# Add Apply Button

$exButton = new-object System.Windows.Forms.Button
$exButton.Location = new-object System.Drawing.Size(550,20)
$exButton.Size = new-object System.Drawing.Size(60,20)
$exButton.Text = "Apply"

# New setting Group Box

$OfGbox = new-object System.Windows.Forms.GroupBox
$OfGbox.Location = new-object System.Drawing.Size(320,0)
$OfGbox.Size = new-object System.Drawing.Size(300,50)
$OfGbox.Text = "New Permission Settings"

# Add Impersonation Clause

$esImpersonationlableBox = new-object System.Windows.Forms.Label
$esImpersonationlableBox.Location = new-object System.Drawing.Size(10,50)
$esImpersonationlableBox.Size = new-object System.Drawing.Size(130,20)
$esImpersonationlableBox.Text = "Use EWS Impersonation"

$seImpersonationCheck = new-object System.Windows.Forms.CheckBox
$seImpersonationCheck.Location = new-object System.Drawing.Size(140,45)
$seImpersonationCheck.Size = new-object System.Drawing.Size(30,25)

# Add Auth Clause

$esAuthlableBox = new-object System.Windows.Forms.Label
$esAuthlableBox.Location = new-object System.Drawing.Size(10,70)
$esAuthlableBox.Size = new-object System.Drawing.Size(130,20)
$esAuthlableBox.Text = "Specify Credentials"

$seAuthCheck = new-object System.Windows.Forms.CheckBox
$seAuthCheck.Location = new-object System.Drawing.Size(140,65)
$seAuthCheck.Size = new-object System.Drawing.Size(30,25)
$seAuthCheck.Add_Click({if ($seAuthCheck.Checked -eq $true){
$unUserNameTextBox.Enabled = $true
$unPasswordTextBox.Enabled = $true
$unDomainTextBox.Enabled = $true
$unUserNameTextBox.Enabled = $false
$unPasswordTextBox.Enabled = $false
$unDomainTextBox.Enabled = $false}})

# Add UserName Box
$unUserNameTextBox = new-object System.Windows.Forms.TextBox
$unUserNameTextBox.Location = new-object System.Drawing.Size(230,70)
$unUserNameTextBox.size = new-object System.Drawing.Size(100,20)

# Add UserName Lable
$unUserNamelableBox = new-object System.Windows.Forms.Label
$unUserNamelableBox.Location = new-object System.Drawing.Size(170,70)
$unUserNamelableBox.size = new-object System.Drawing.Size(60,20)
$unUserNamelableBox.Text = "UserName"
$unUserNameTextBox.Enabled = $false

# Add Password Box
$unPasswordTextBox = new-object System.Windows.Forms.TextBox
$unPasswordTextBox.PasswordChar = "*"
$unPasswordTextBox.Location = new-object System.Drawing.Size(400,70)
$unPasswordTextBox.size = new-object System.Drawing.Size(100,20)

# Add Password Lable
$unPasswordlableBox = new-object System.Windows.Forms.Label
$unPasswordlableBox.Location = new-object System.Drawing.Size(340,70)
$unPasswordlableBox.size = new-object System.Drawing.Size(60,20)
$unPasswordlableBox.Text = "Password"
$unPasswordTextBox.Enabled = $false

# Add Domain Box
$unDomainTextBox = new-object System.Windows.Forms.TextBox
$unDomainTextBox.Location = new-object System.Drawing.Size(550,70)
$unDomainTextBox.size = new-object System.Drawing.Size(100,20)

# Add Domain Lable
$unDomainlableBox = new-object System.Windows.Forms.Label
$unDomainlableBox.Location = new-object System.Drawing.Size(510,70)
$unDomainlableBox.size = new-object System.Drawing.Size(50,20)
$unDomainlableBox.Text = "Domain"
$unDomainTextBox.Enabled = $false

# Add CASUrl Box
$unCASUrlTextBox = new-object System.Windows.Forms.TextBox
$unCASUrlTextBox.Location = new-object System.Drawing.Size(70,100)
$unCASUrlTextBox.size = new-object System.Drawing.Size(500,20)
$unCASUrlTextBox.text = $strRootURI

# Add CASUrl Lable
$unCASUrllableBox = new-object System.Windows.Forms.Label
$unCASUrllableBox.Location = new-object System.Drawing.Size(10,100)
$unCASUrllableBox.size = new-object System.Drawing.Size(60,20)
$unCASUrllableBox.Text = "CASUrl"

# Add DataGrid View

$dgDataGrid = new-object
$dgDataGrid.Location = new-object System.Drawing.Size(10,130)
$dgDataGrid.size = new-object System.Drawing.Size(650,550)
$dgDataGrid.AutoSizeColumnsMode = "AllCells"
$dgDataGrid.SelectionMode = "FullRowSelect"

$form.Text = "Exchange 2007 Default Calendar Permissions Form"
$form.size = new-object System.Drawing.Size(700,730)

$form.autoscroll = $true
$form.topmost = $true

Class library helper for setting Calendar Permission via Powershell and Exchange 2007

I posted something a while ago that tried to explain how you can go about setting calendar rights using the new features in EWS included in Exchange 2007 SP1. Unfortunately from the feedback i got it seemed to confuse people more than help it seemed most people where interested in resetting the default calendar permissions especially in regards to the new Freebusy Acl’s that where included in Exchange 2007/Outlook 2007. The freebusy rights pose a special challenge because the normal scripts like CDO 1.2 or Pfdavadmin can’t be used to set these particular rights you need to use either the Outlook 2007 OOM or EWS. So I decided to come up with a helper class similar to the OOF helper class I wrote before that would allow people to change the rights on a calendar just using a couple of lines in Powershell.

Because I already has a namespace for the OOF helper I decided to fork that code into a new namespace and library and I thought that I’d be able to reuse a lot of what I’d done before.... But the library turned out to be a lot more complex to get working for a number of reasons and required a lot of extra logic to get the library to behave the way I wanted to (but hey go hard or go home). How does it work well lets take a walk though and I’ll try to explain. Firstly if you want to use the library in Powershell you need to load it with a cmd such as


Then you can actually create an object using the class library you just loaded when you create the object you need to specify some parameters that will be used to query EWS. The only mandatory parameter is the email address the optional parameters control how the object will work with EWS and how it handles authentication and which CAS it will use. The first parameter is the impersonation parameter which is a Boolean that controls whether the object will use EWS impersonation to access mailboxes or just use Delegate access (when I talk about delegate access I don’t mean delegated Exchange Admin access I mean either Outlook delegates or rights assigned via add-mailboxpermission). EWS impersonation comes in handy and is a more secure way of giving rights to an account to do a job such as changing calendar rights for every use on a server see for giving an account EWS impersonation rights.

The next three parameters are for the username,password and domain if you want to specify the user context you want this object to run under. This allows you to use this library on a machine that isn’t a member of the domain or run the library as a user that’s different from the currently logged on user. The last parameter is for the URL of the CAS Server to use this is important if you want to run the script from a machine that isn’t a member of the domain the Exchange Server is a member of. By default if you don’t specify this value the Library will try to look up active directory for the autodiscovery address and then try to do a Autodiscover request to find the EWS url based on the email address entered. Once you have everything worked out you need to run a line like this to create a calendar utility object.

$calutil = new-object EWSUtil.CalendarUtil("", $false, "admin", "password#", "domain", "https://servername /EWS/Exchange.asmx")

Issuing this command will cause the GetFolder request to be issued to the server and it will retrieve the current calendar folder permissions and populate a generic list of Calendarpermssion objects.

This is where we encounter our first caveat as I said in a previous post the permission roles you set in Outlook don’t reflect the permissions level returned by EWS. So for most of the Outlook Roles you set you would see that as a custom permission in EWS (apart from FreeBusy rights which are reflected correctly). To work around this I created a few helper routines to convert the custom permissions that EWS returns back to the appropriate Outlook Roles which is handy if you’re trying to document or check the permissions that have been set via Outlook. And also some helpers so when you want to set permissions instead of using the EWS calendar permission that will be reflected as Custom permissions when viewed in Outlook you will see the correct Outlook role set. This is a hard one to explain its just one of the current nuances of EWS. An example of using one of the helper routines to set an ACE to an outlook role for a user your adding to the calendar DACL you would need to use one of the following lines depending on the Outlook Role you wanted to set.


If you want to set permissions for the default user or anonymous user on the calendar instead of specifying the email address of the user you need to just put default or anonymous

eg $calutil.CalendarDACL.Add($calutil.FreeBusyTimeAndSubjectAndLocation(“default”))

Once you have added or removed the ACE you wanted to you then just call the update method eg


Another quick sample if you want to output the default permissions and anonymous permission for a users calendar you can do the following. This shows an example of using the enumOutlook helper to get the Outlook Role that has been set.

$calutil = new-object EWSUtil.CalendarUtil("", $false, "user", "password", "domain", "https://servername/EWS/Exchange.asmx");
for ($cpint=0;$cpint -lt $calutil.CalendarDACL.Count; $cpint++){
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUserSpecified -eq $true){
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Default){
"Default " + $calutil.enumOutlookRole($calutil.CalendarDACL[$cpint])
if ($calutil.CalendarDACL[$cpint].UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Anonymous){
"Anonymous " + $calutil.enumOutlookRole($calutil.CalendarDACL[$cpint])


A few things about the update method when update is called it will first loop through the generic ACE list and build a Calendarpermission set to be used in a EWS update folder operation. Because there are no verification routines in the List add methods it will go through and make sure you don’t have any duplicate ACE's in the list say if you wanted to set the default permission on a calendar and instead of changing the current ACE you just added a new ACE for the default user. The update routine will find the duplicate ACE's and will use the last one added to the list which will overwrite the old ACE.

To demonstrate the use of this library I’ve put together a little powershell GUI script the will allow you to enumerate all the Default calendar permissions for every user on a server and set them to something else if you so desire. I’ve posted this separately here.

I wish I had more time to document this properly and I will try to update this post when I can. The code itself is very untested and I don’t consider it stable and or safe for use in a production environment I can only recommend that you use it in a test/dev environment and as a guide to building your own applications. I’ve include a complied DLL as well as the full source so you can look/debug as you please. If you do spot any major bugs and things I’ve done wrong or that could be done better please let me know so I can update the source.

I’ve put a download of the source and dll here the update function looks like

Additional -- Added after problems dicovered

One important and potentially frustrating point for anybody who wants to set permissions on the calendar folder in a mailbox with any Exchange API that allows you to modify the folder DACL’s is that you also need add the same ACE your adding (or modifying) to the FreeBusy Data folder which is one the NON_IPM_Subtree Root folders in a mailbox explained in this KB. Exchange Web Services is no exception to this rule and accessing the NON_IPM_Subtree folders isn’t as straight forward as normal mailbox folders but here’s a method for accessing the freebusy folder and modifying the DACL.

So what happens now when you create the object the code will try to retrieve the free/busy folder rights and a separate generic list object is created that contains the current Free/busy folder ACE's called FreeBusyDACL. Because as i said with this DACL you need to use a different type of ACE their are separate ACE objects defined eg


What you will need to do is duplicate any operations that Add or Remove ACE's to the CalendarDACL to also now do the FreeBusyDACL eg


When you run the update method it will update both DACL's

Saturday, March 15, 2008

Exchange 2007 Mail enable User Powershell Quick Form

A couple of weeks okay I had to mail enable (as opposed to create mailboxes) for a bunch of Active Directory accounts in a variety of OU’s in Active Directory. With previous versions of Exchange being able to do this directly from ADUC made this task relative easy and depending on the number of accounts you had to do probably not something that was worth writing a script for. With the loss of this functionality out of ADUC you have to use either a wizard in the Exchange Management Console or do it directly from Powershell. I’m not a fan of the EMC wizards they are there to do a job which they do adequately but are just too time consuming to use for repetitive tasks that aren’t worth writing a larger script to do. So I decided to port the form I had for doing quick mailbox creates to do quick mail enabled's. I wouldn’t say the result was brilliant but in the end once I had the form up I could bang though the accounts I wanted to mail enable resonably quickly (what helped is the external mail address I wanted to mail enable the user for was already in the mail attribute) vs going through the wizard for each user and clicking, cut and paste etc .

The script is similar to a lot of the others I’ve posted before it uses a .net winform to present a user interface to the users. Once you select an OU it runs the Get-User cmdlet to filter and retrieve only the users from that OU selected that aren’t currently mail-enabled or have a mailbox associated with it. This populates the UserName Drop down box. Once a user is selected from the dropdown the other textboxes are populated in the form from the information in the User account (retrieved using the Get-User cmdlet). This then builds a Enable-MailUser cmd line if you edit any of the information in the text boxes that affects the cmdline that will run which you click the mail enable button the cmd is updated as you type. This is achived by hooking into the TextChange event of each of the texboxes. Maybe the form isn’t that impressive but this function is pretty funky and worth giving a check out if you want to build your own little GUI's for your helpdesk users to use (you know all those people who’s eyes glaze over when you try to explain what powershell is).

Because this script requires the Exchange Management Shell Cmdlet's it needs to run from the Exchange Management Shell. If you download the script file from this site you need to right click the file and select unblock from the properties menu to allow the script to be executed.

I’ve put a download copy of this script here the script itself look like.

· Warning this script may cause Global Warming if keep buying incandescent light globes.


Function Enableuser{
$result = Enable-MailUser -Identity $emIdentityTextBox.Text -Alias $AliasNameText.Text -ExternalEmailAddress ("SMTP:" + $exExternalMail.Text)
if ($result -ne $null){write-host "User Mail enabled"}
else{write-host "Error Mail enabling user check command Line for Details"}
get-user -sortby "LastName" -OrganizationalUnit $OUhash1[$ouOuNameDrop.SelectedItem.ToString()] where { $_.RecipientType -eq "User" } foreach-object{
# $uname = $_.LastName + " " + $_.FirstName
$uname = $_.DisplayName

$OUhash1 = @{ }
$MBhash1 = @{ }

$form = new-object System.Windows.Forms.form
$form.Text = "Exchange 2007 Quick User Mail User Form"
$form.size = new-object System.Drawing.Size(600,400)

# Add OU DropLable
$ouOuNamelableBox = new-object System.Windows.Forms.Label
$ouOuNamelableBox.Location = new-object System.Drawing.Size(10,10)
$ouOuNamelableBox.size = new-object System.Drawing.Size(70,20)
$ouOuNamelableBox.Text = "OU Name"

# Add OU Drop Down
$ouOuNameDrop = new-object System.Windows.Forms.ComboBox
$ouOuNameDrop.Location = new-object System.Drawing.Size(100,10)
$ouOuNameDrop.Size = new-object System.Drawing.Size(230,30)
$root = [ADSI]''
$searcher = new-object System.DirectoryServices.DirectorySearcher($root)
$searcher.Filter = '(objectClass=organizationalUnit)'
$searcher1 = $searcher.FindAll()
foreach ($person in $searcher1){
[string]$ent = $person.Properties.canonicalname
get-user -sortby "LastName" -OrganizationalUnit $OUhash1[$ouOuNameDrop.SelectedItem.ToString()] where { $_.RecipientType -eq "User" } foreach-object{
$uname = $_.DisplayName


# Add UserName Box
$unUserNameDropBox = new-object System.Windows.Forms.ComboBox
$unUserNameDropBox.Location = new-object System.Drawing.Size(100,40)
$unUserNameDropBox.size = new-object System.Drawing.Size(330,20)
$user = get-user $unUserNameDropBox.SelectedItem.ToString()
$emIdentityTextBox.text = $user.Identity
$unFirstNameTextBox.text = $User.FirstName
$lnLastNameTextBox.text = $user.LastName
$dsDisplayNameTextBox.text = $user.DisplayName
$AliasNameText.text = $user.SamAccountName
$exExternalMail.text = $user.WindowsEmailAddress
$pscmd = "Enable-MailUser -Identity '" + $user.Identity + "' -Alias '" + $user.SamAccountName + "' -ExternalEmailAddress 'SMTP:" + $user.WindowsEmailAddress + "'"
$msCmd.text = $pscmd

# Add Username Lable
$unUserNamelableBox = new-object System.Windows.Forms.Label
$unUserNamelableBox.Location = new-object System.Drawing.Size(10,40)
$unUserNamelableBox.size = new-object System.Drawing.Size(100,20)
$unUserNamelableBox.Text = "Username UPN"

# Add Identity Box
$emIdentityTextBox = new-object System.Windows.Forms.TextBox
$emIdentityTextBox.Location = new-object System.Drawing.Size(100,65)
$emIdentityTextBox.size = new-object System.Drawing.Size(330,20)
$pscmd = "Enable-MailUser -Identity '" + $emIdentityTextBox.Text + "' -Alias '" + $AliasNameText.Text + "' -ExternalEmailAddress 'SMTP:" + $exExternalMail.Text + "'"
$msCmd.text = $pscmd

# Add Identity Lable
$emIdentitylableBox = new-object System.Windows.Forms.Label
$emIdentitylableBox.Location = new-object System.Drawing.Size(10,65)
$emIdentitylableBox.size = new-object System.Drawing.Size(100,20)
$emIdentitylableBox.Text = "Identity"

# Add FirstName Box
$unFirstNameTextBox = new-object System.Windows.Forms.TextBox
$unFirstNameTextBox.Location = new-object System.Drawing.Size(100,90)
$unFirstNameTextBox.size = new-object System.Drawing.Size(130,20)

# Add FirstName Lable
$unFirstNamelableBox = new-object System.Windows.Forms.Label
$unFirstNamelableBox.Location = new-object System.Drawing.Size(10,90)
$unFirstNamelableBox.size = new-object System.Drawing.Size(60,20)
$unFirstNamelableBox.Text = "First Name"

# Add LastName Box
$lnLastNameTextBox = new-object System.Windows.Forms.TextBox
$lnLastNameTextBox.Location = new-object System.Drawing.Size(100,120)
$lnLastNameTextBox.size = new-object System.Drawing.Size(130,20)

# Add LastName Lable
$lnLastNamelableBox = new-object System.Windows.Forms.Label
$lnLastNamelableBox.Location = new-object System.Drawing.Size(10,120)
$lnLastNamelableBox.size = new-object System.Drawing.Size(60,20)
$lnLastNamelableBox.Text = "Last Name"

# Add DisplayName Box
$dsDisplayNameTextBox = new-object System.Windows.Forms.TextBox
$dsDisplayNameTextBox.Location = new-object System.Drawing.Size(100,150)
$dsDisplayNameTextBox.size = new-object System.Drawing.Size(130,20)

# Add DisplayName Lable
$dsDisplayNamelableBox = new-object System.Windows.Forms.Label
$dsDisplayNamelableBox.Location = new-object System.Drawing.Size(10,150)
$dsDisplayNamelableBox.size = new-object System.Drawing.Size(100,20)
$dsDisplayNamelableBox.Text = "Display Name"

# Add Alias Text
$AliasNameText = new-object System.Windows.Forms.TextBox
$AliasNameText.Location = new-object System.Drawing.Size(100,180)
$AliasNameText.Size = new-object System.Drawing.Size(230,30)
$pscmd = "Enable-MailUser -Identity '" + $emIdentityTextBox.Text + "' -Alias '" + $AliasNameText.Text + "' -ExternalEmailAddress 'SMTP:" + $exExternalMail.Text + "'"
$msCmd.text = $pscmd

# Add Alias TextBox Lable
$AliasNameTextlableBox = new-object System.Windows.Forms.Label
$AliasNameTextlableBox.Location = new-object System.Drawing.Size(10,180)
$AliasNameTextlableBox.size = new-object System.Drawing.Size(100,20)
$AliasNameTextlableBox.Text = "Alias"

# Add External Email Address
$exExternalMail = new-object System.Windows.Forms.TextBox
$exExternalMail.Location = new-object System.Drawing.Size(100,210)
$exExternalMail.Size = new-object System.Drawing.Size(300,30)
$pscmd = "Enable-MailUser -Identity '" + $emIdentityTextBox.Text + "' -Alias '" + $AliasNameText.Text + "' -ExternalEmailAddress 'SMTP:" + $exExternalMail.Text + "'"
$msCmd.text = $pscmd

# Add External DropLable
$exExternalMaillableBox = new-object System.Windows.Forms.Label
$exExternalMaillableBox.Location = new-object System.Drawing.Size(10,210)
$exExternalMaillableBox.size = new-object System.Drawing.Size(100,20)
$exExternalMaillableBox.Text = "External Email"

# Add cmdbox
$msCmd = new-object System.Windows.Forms.RichTextBox
$msCmd.Location = new-object System.Drawing.Size(100,240)
$msCmd.Size = new-object System.Drawing.Size(400,75)
$msCmd.readonly = $true

# Add CMd DropLable
$msCmdlableBox = new-object System.Windows.Forms.Label
$msCmdlableBox.Location = new-object System.Drawing.Size(10,240)
$msCmdlableBox.size = new-object System.Drawing.Size(100,20)
$msCmdlableBox.Text = "PowerShell CMD"

# Add Mail Enabled Button

$crButton = new-object System.Windows.Forms.Button
$crButton.Location = new-object System.Drawing.Size(110,330)
$crButton.Size = new-object System.Drawing.Size(100,23)
$crButton.Text = "Enabled User"$crButton.Add_Click({Enableuser})$form.Controls.Add($crButton)

$form.topmost = $true

Saturday, March 01, 2008

ActiveSync IIS Log Tool / Powershell GUI Version 1

Anyone trying to administer and manage Windows Mobile Devices when active-syncing against an Exchange 2007 server will know the unique set of challenges keeping an eye on these devices presents. Because all the communication a mobile device makes is via https (well lets hope it is) all this activity is recorded in the IIS Log files. On the Exchange Team blog there are some great scripts that have been published for doing reporting on Exchange Active Sync via the logparser which works really well and is a really fast and functional method if you have large log files. .

Because some of the servers I work on tend to be smaller and greater in number I wanted a better method to do some quick reporting on ActiveSync that was quick, easy to setup and practical to do on a day to day basis. So I thought I put together a little powershell GUI to be able to pull information from an IIS log file about Active-Sync devices and then summarize and present the result in a DataGrid.

To read the log file in Powershell I’ve used Get-Content, now anyone who has tried to use Get-Content to read a large log file is probably right now going “oh”. To put it simply reading a large log file with Get-content is like watching the grass grow. If you have large log files to read unless you have a lot of time to maybe crochet a nice hat than this script isn’t for you. If your log files are under 50 mb then you should be able to get some decent performance (around a minute for a 50MB file).

The script works by presenting a nice shiny winform for you to select a log file to look at. It then uses Get-Content to read in the content from the IIS log file you select. To filter only ActiveSync data it uses the following filter Where-Object -FilterScript { $_ -ilike “*Microsoft-Server-ActiveSync*” }

It then does a summary of the information it gets back, to help you get a handle on what mobile versions and devices you have out in the field this script has a function to parse and then work out from the information in the log what version of window mobile the device is running. This is version 1 so it may not do the device conversion properly but from the rather small amount of testing I did it seemed to work okay.

The other thing it does is summarizes the sent and received Bytes from the Log file to let you see how much data your mobile device has sent and received. And also does summaries for each device to see how many ping,syncs,replys etc each device is doing. The script populates 2 ADO.NET tables there is a raw log view to let you see the raw data and a summary view which reports on all the nice summary stuff I just talked about. To do all the summaries it uses a number of nested hash tables.

Because by default Log files are in UTC (yes you can change this) the script will convert the times it reads in the log file into local time where the script is running. This behavior is configurable with a checkbox you can also view a certain number of lines in the log file and refresh it to read the latest information. Although using Get-Content is slow the advantage of using it is that it doesn’t lock the log file when it reading it (yeh!)

I’ve put a downloadable version of the script here the script itself looks like.

· Warning this script may cause Global Warming if you leave the light on in the bathroom


function openLog{
$exFileName = new-object System.Windows.Forms.openFileDialog
$fnFileNamelableBox.Text = $exFileName.FileName

function Populatetable{

$tcountline = -1
$lnum = 0
$rnma = 1000
if ($rbVeiwAllOnlyRadioButton.Checked -eq $true){$tcountline = $lnLogfileLineNum.value}
get-content $fnFileNamelableBox.Text | Where-Object -FilterScript { $_ -ilike “*Microsoft-Server-ActiveSync*” } | %{
$lnum ++
if ($lnum -eq $rnma){ Write-Progress -Activity "Read Lines" -Status $lnum
$rnma = $rnma + 1000
$linarr = $_.split(" ")
$lfDate = ""
$async = 0
$lfTime = ""
$lfSourceIP = ""
$lfMethod = ""
$lfAction = ""
$lfUserName = ""
$devid = ""
$lfPPCversion = ""
if ($linarr[0].substring(0, 1) -ne "#"){
if ($linarr.Length -gt 0){$lfDate = $linarr[0]}
if ($linarr.Length -gt 1){$lfTime = $linarr[1]}
if ($linarr.Length -gt 8){$lfSourceIP= $linarr[9]}
if ($linarr.Length -gt 3){$lfMethod = $linarr[4]}
if ($linarr.Length -gt 5){$lfAction = $linarr[6]}
if ($linarr.Length -gt 7){$lfUserName = $linarr[8]}
if ($linarr.Length -gt 10){$lfPPCversion = $linarr[11]}
if ($linarr.Length -gt 15){$bcSent = $linarr[16]}
if ($linarr.Length -gt 16){$bcRecieved = $linarr[17]}
if ($lfMethod -ne "Options"){
$cmdint = $lfAction.IndexOf("&Cmd=")+5
$aysnccmd = $lfAction.Substring($cmdint,($lfAction.IndexOfAny("&",$cmdint)-$cmdint))
else {$aysnccmd = "Options"}
$ldLogDatecombine = $lfDate + " " + $lfTime
if ($ltimeconv.Checked -eq $true){
$LogDate = Get-Date($ldLogDatecombine)
$LocalLogDate = $LogDate.tolocaltime()
$ldLogDatecombine = $LocalLogDate.ToString("yyyy-MM-dd HH:mm:ss")
$devidint = $lfAction.indexof("&DeviceId")+10
$devid = $lfAction.Substring($devidint,($lfAction.IndexOfAny("&",$devidint)-$devidint))
if ($lfLogFieldHash.ContainsKey -eq $false){$lfLogFieldHash.Add($aysnccmd,$lfLogFieldHashnum)
$lfLogFieldHashnum = $lfLogFieldHashnum + 1 }
if ($unHash.ContainsKey($devid)){
$usrHash = $unHash[$devid]
$usrHash["BytesSent"] = [Int]$usrHash["BytesSent"] + [Int]$bcSent
$usrHash["BytesRecieved"] = [Int]$usrHash["BytesRecieved"] + [Int]$bcRecieved
$usrHash["LastSeen"] = $ldLogDatecombine
$lfPPCversion = $usrHash["MobileVersion"]
if ($usrHash.ContainsKey($aysnccmd)){
$usrHash[$aysnccmd] = $usrHash[$aysnccmd] +1
$lfPPCversion = getppcversion($lfPPCversion)
$usrHash = @{ }


foreach($devkey in $unHash.keys){
$devid = $devkey
$devhash = $unHash[$devkey]
$userName = $devhash["UserName"]
$MobileVersion = $devhash["MobileVersion"]
$LastSeen = $devhash["LastSeen"]
$pings = 0
if ($devhash.Contains("Ping")){
$pings = $devhash["Ping"]
$Sync = 0
if ($devhash.Contains("Sync")){
$Sync = $devhash["Sync"]
$FolderSync = 0
if ($devhash.Contains("FolderSync")){
$FolderSync = $devhash["FolderSync"]
$SendMail = 0
if ($devhash.Contains("SendMail")){
$SendMail = $devhash["SendMail"]
$SmartReply = 0
if ($devhash.Contains("SmartReply")){
$SmartReply = $devhash["SmartReply"]
$SmartForward = 0
if ($devhash.Contains("SmartForward")){
$SmartForward = $devhash["SmartForward"]
$gattach = 0
if ($devhash.Contains("GetAttachment")){
$gattach= $devhash["GetAttachment"]
$bsent = 0
if ($devhash.Contains("BytesSent")){
$bsent = [math]::round($devhash["BytesSent"]/1024,2)
$brecv = 0
if ($devhash.Contains("BytesSent")){
$brecv = [math]::round($devhash["BytesRecieved"]/1024,2)


if ($SsumBox.Checked -eq $true){
$dgDataGrid.DataSource = $asyncsumTable
else {
$dgDataGrid.DataSource = $logTable
write-progress "complete" "100" -completed

function getppcversion($ppcVersion){
$ppcret = $ppcVersion
$verarry = $ppcVersion.split("/")
$versegarry = $verarry[1].split(".")
if ($verarry[0] -eq "Microsoft-PocketPC"){
if ($versegarry[0] -eq "3"){
$ppcret = "PocketPC 2003"
if ($versegarry[0] -eq "2"){
$ppcret = "PocketPC 2002"
else {
if ($versegarry[0] -eq "5"){
if ($versegarry[1] -eq "1"){
if ([Int]$versegarry[2] -ge 2300) {$ppcret = "SmartPhone 2003"}
else {$ppcret = "Mobile 5"}

if ($versegarry[1] -eq "2"){
$ppcret = "Mobile 6"

if ($ppcVersion -eq "Microsoft-PocketPC/3.0") {}
return $ppcret

$unHash = @{ }
$lfLogFieldHash = @{ }
$lfLogFieldHashnum = 2
$form = new-object System.Windows.Forms.form
$form.Text = "ActiveSync Log Tool"
$Dataset = New-Object System.Data.DataSet
$logTable = New-Object System.Data.DataTable
$logTable.TableName = "ActiveSyncLogs"

$asyncsumTable = New-Object System.Data.DataTable
$asyncsumTable.TableName = "ActiveSyncSummary"

# Content
$cmClickMenu = new-object System.Windows.Forms.ContextMenuStrip

# Add Open Log file Button

$olButton = new-object System.Windows.Forms.Button
$olButton.Location = new-object System.Drawing.Size(20,19)
$olButton.Size = new-object System.Drawing.Size(75,23)
$olButton.Text = "Select file"

# Add FileName Lable
$fnFileNamelableBox = new-object System.Windows.Forms.Label
$fnFileNamelableBox.Location = new-object System.Drawing.Size(110,25)
$fnFileNamelableBox.forecolor = "MenuHighlight"
$fnFileNamelableBox.size = new-object System.Drawing.Size(200,20)

# Add Refresh Log file Button

$refreshButton = new-object System.Windows.Forms.Button
$refreshButton.Location = new-object System.Drawing.Size(390,29)
$refreshButton.Size = new-object System.Drawing.Size(75,23)
$refreshButton.Text = "Refresh"

# Add Veiw RadioButtons
$rbVeiwAllRadioButton = new-object System.Windows.Forms.RadioButton
$rbVeiwAllRadioButton.Location = new-object System.Drawing.Size(20,52)
$rbVeiwAllRadioButton.size = new-object System.Drawing.Size(69,17)
$rbVeiwAllRadioButton.Checked = $true
$rbVeiwAllRadioButton.Text = "View All"
$rbVeiwAllRadioButton.Add_Click({if ($rbVeiwAllRadioButton.Checked -eq $true){$lnLogfileLineNum.Enabled = $false}})

$rbVeiwAllOnlyRadioButton = new-object System.Windows.Forms.RadioButton
$rbVeiwAllOnlyRadioButton.Location = new-object System.Drawing.Size(110,52)
$rbVeiwAllOnlyRadioButton.size = new-object System.Drawing.Size(89,17)
$rbVeiwAllOnlyRadioButton.Text = "View Only #"
$rbVeiwAllOnlyRadioButton.Add_Click({if ($rbVeiwAllOnlyRadioButton.Checked -eq $true){$lnLogfileLineNum.Enabled = $true}})

# Local Time Converstion CheckBox

$ltimeconv = new-object System.Windows.Forms.CheckBox
$ltimeconv.Location = new-object System.Drawing.Size(310,7)
$ltimeconv.Size = new-object System.Drawing.Size(150,20)
$ltimeconv.Checked = $true
$ltimeconv.Text = "Convert to Local Time"

# Show Summary CheckBox

$SsumBox = new-object System.Windows.Forms.CheckBox
$SsumBox.Location = new-object System.Drawing.Size(510,7)
$SsumBox.Size = new-object System.Drawing.Size(200,20)
$SsumBox.Checked = $true
$SsumBox.Add_Click({if ($SsumBox.Checked -eq $true){$dgDataGrid.DataSource = $asyncsumTable}
else {$dgDataGrid.DataSource = $logTable}})

$SsumBox.Text = "Show Summary"

# Add Numeric log line number
$lnLogfileLineNum = new-object System.Windows.Forms.numericUpDown
$lnLogfileLineNum.Location = new-object System.Drawing.Size(201,52)
$lnLogfileLineNum.Size = new-object System.Drawing.Size(69,20)
$lnLogfileLineNum.Enabled = $false
$lnLogfileLineNum.Maximum = 10000000000

# File setting Group Box

$OfGbox = new-object System.Windows.Forms.GroupBox
$OfGbox.Location = new-object System.Drawing.Size(12,0)
$OfGbox.Size = new-object System.Drawing.Size(464,75)
$OfGbox.Text = "Log File Settings"

# Add DataGrid View

$dgDataGrid = new-object
$dgDataGrid.AllowSorting = $True
$dgDataGrid.Location = new-object System.Drawing.Size(12,81)
$dgDataGrid.size = new-object System.Drawing.Size(1024,750)

$form.topmost = $true