Monday, January 31, 2011

Dealing with Throttling restrictions in Exchange Web Services on Exchange 2010 SP1 in scripts and code

In Exchange 2010 SP1 throttling is enabled by default and while for the best part this is a good thing there are a few things you may need to be careful of if you have written code that retrieves a large number of items from a mailbox folder.

While you can disable or set custom throttling polices for your application or scripts its generally a better idea to build your code to work within these throttling thresholds. With EWS the throttling policies affect a number of different areas from the number of concurrent connections one user can establish to a server, limits on the number of subscriptions and also the CPU time one particular user can consume when executing particular operations. For the most part if your using simple single threaded scripts where your not executing requests concurrently most of the settings wont have any bearing. The one policy setting that will have a direct affect on existing scripts or code is the EWSFindCountLimit which by default is limited to 1000. This is discussed in a little more detail here https://blogs.msdn.com/b/exchangedev/archive/2010/03/12/throttling-policies-and-the-ewsfindcountlimit.aspx.

The upshot of this is if you have code where you have set a page size greater than 1000 and your not checking IncludesLastItemInRange to ensure you have retrieved all the items within the scope of your findItems or findfolder request then your current code may will only retrieve the first 1000 items in a folder .To cater for this if your using Powershell and the EWS Managed API you need to use something like a Do{}(Where) to write code loop to ensure you make followup finditem/findfolder requests to retrieve all the items from a particular folder. For example to loop through all the messages within an inbox using a pagesize of 1000 would look like

$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$fiResult = ""
do{
$fiResult = $Service.FindItems($folderid,$ivItemView)
foreach($Items in $fiResult.Items){
$Items.Subject
}
$ivItemView.offset += $fiResult.Items.Count
}while($fiResult.MoreAvailable -eq $true)


The other setting to be careful of is the EWSFastSearchTimeoutInSeconds which is set to 60 seconds by default. If your querying folders with a large number of items with complex filters then this could be a problem. Fortunately the Entry point EWS offers to make Exchange Search queries should help counter problems with this.

Monday, January 24, 2011

Default Calendar permission Powershell GUI for Exchange 2010 SP1 and up

One of the new cmdlets in Exchange 2010 SP1 is the Set-MailboxFolderPermission cmdlet which was an addition to the other Exchange 2010 RTM folder permission cmdlets get-MailboxFolderPermission and add-MailboxFolderPermission. What this cmdlet gives you the ability to do is update the permissions of any folder within a mailbox as long as you are a member of the RBAC roles that allows you set them as specified in http://technet.microsoft.com/en-us/library/dd638132.aspx. Another thing this cmdlet does is give me the ability to revisit the default calendar folder permission GUI from this post(which used EWS and will still work okay on 2010) and rewrite this to be able to be used in Remote powershell so it will work against any 2010 SP1 server as well as anything else that supports these cmdlets such as live@edu or Office365.

If you don't really want a GUI and would rather just export the permissions to CSV modify them and then reimport them that's easy to do for example to export the settings to a csv file.

$rptCollection = @()
$mailboxes = get-mailbox -ResultSize Unlimited
$mailboxes | foreach-object{
$alias = $_.alias + ":\Calendar"
$displayName = $_.DisplayName
write-host $alias
$permissions = Get-MailboxFolderPermission $alias | Where-Object {$_.Identity.ToString() -eq "Default"}
if($permissions -ne $null){
$stringPerms = ""
foreach($perms in $permissions.AccessRights){$stringPerms = $stringPerms + $perms + " "}
Add-Member -InputObject $permissions -MemberType NoteProperty -Name "Alias" -Value $alias -Force
Add-Member -InputObject $permissions -MemberType NoteProperty -Name "StringAccessRights" -Value $stringPerms -Force
$rptCollection += $permissions
}

}
$rptCollection | export-csv -notypeInformation c:\defaultperms.csv

To then re-import them from a csv file you can use

import-csv c:\defaultperms.csv | foreach-object{
"Seting Rights on " + $_.alias
Set-MailboxFolderPermission -id $_.alias -User $_.Identity -AccessRights $_.StringAccessRights

}

Personally i like the GUI which is a little more snappier with the GUI I left the permission descriptions in Outlook format rather then switching them to the accessrights role format(the great thing about standards is there are so many to choose from). To run this GUI you need to first start a Remote powershell session and import the Exchange 2010 cmdlets or run it directly from within the EMS. I've put a download of the new script here the code looks like

[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")


function GetPerms(){
$logTable.clear()

If ($seAuthCheck.Checked -eq $true){
$mailboxes = get-mailbox -server $snServerNameTextBox.Text -ResultSize Unlimited
}
else{
$mailboxes = get-mailbox -ResultSize Unlimited
}
$mailboxes | foreach-object{
$alias = $_.alias + ":\Calendar"
$displayName = $_.DisplayName
write-host $alias
$permissions = Get-MailboxFolderPermission $alias | Where-Object {$_.Identity.ToString() -eq "Default"}
if($permissions -ne $null){
Add-Member -InputObject $permissions -MemberType NoteProperty -Name "Alias" -Value $alias -Force
$stringPerms = ""
foreach($perms in $permissions.AccessRights){$stringPerms = $stringPerms + $perms + " "}
$logTable.rows.add($displayName,$permissions.alias,$stringPerms)
}

}
$dgDataGrid.DataSource = $logTable

}

Function UpdatePerms{
if ($dgDataGrid.SelectedRows.Count -eq 0){
$mbtoSet = $dgDataGrid.Rows[$dgDataGrid.CurrentCell.RowIndex].Cells[1].Value
$newperm = ""
switch ($npNewpermDrop.Text){
"None" {$newperm = "None"}
"FreeBusyTimeOnly" {$newperm = "AvailabilityOnly"}
"FreeBusyTimeAndSubjectAndLocation" {$newperm = "LimitedDetails"}
"Reviewer" {$newperm = "Reviewer"}
"Contributer" {$newperm = "Contributer"}
"Author" {$newperm = "Author"}
"NonEditingAuthor" {$newperm = "NonEditingAuthor"}
"PublishingAuthor"{$newperm = "PublishingAuthor"}
"Author" {$newperm = "Author"}
"Editor" {$newperm = "Editor"}
"PublishingEditor"{$newperm = "PublishingEditor"}
}
Set-MailboxFolderPermission -id $mbtoSet -User Default -AccessRights $newperm
write-host "Permission updated" + $npNewpermDrop.Text
}
else{
$lcLoopCount = 0
while ($lcLoopCount -le ($dgDataGrid.SelectedRows.Count-1)) {
$mbtoSet = $dgDataGrid.SelectedRows[$lcLoopCount].Cells[1].Value

switch ($npNewpermDrop.Text){
"None" {$newperm = "None"}
"FreeBusyTimeOnly" {$newperm = "AvailabilityOnly"}
"FreeBusyTimeAndSubjectAndLocation" {$newperm = "LimitedDetails"}
"Reviewer" {$newperm = "Reviewer"}
"Contributer" {$newperm = "Contributer"}
"Author" {$newperm = "Author"}
"NonEditingAuthor" {$newperm = "NonEditingAuthor"}
"PublishingAuthor"{$newperm = "PublishingAuthor"}
"Author" {$newperm = "Author"}
"Editor" {$newperm = "Editor"}
"PublishingEditor"{$newperm = "PublishingEditor"}
}
Set-MailboxFolderPermission -id $mbtoSet -User Default -AccessRights $newperm
write-host "Permission updated" + $npNewpermDrop.Text
$lcLoopCount += 1
}
}
write-host "end PermUpdate"
write-host "Refresh Perms"
GetPerms
}


$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"
$logTable.Columns.Add("DisplayName");
$logTable.Columns.Add("MailboxFolderId");
$logTable.Columns.Add("Default-Permissions");



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

# Add ServerNameText
$snServerNameTextBox = new-object System.Windows.Forms.TextBox
$snServerNameTextBox.Location = new-object System.Drawing.Size(90,60)
$snServerNameTextBox.size = new-object System.Drawing.Size(150,20)
$snServerNameTextBox.Enabled = $false
$form.Controls.Add($snServerNameTextBox)

$seAuthCheck = new-object System.Windows.Forms.CheckBox
$seAuthCheck.Location = new-object System.Drawing.Size(250,60)
$seAuthCheck.Size = new-object System.Drawing.Size(130,25)
$seAuthCheck.Text = "Filter by"
$seAuthCheck.Add_Click({if ($seAuthCheck.Checked -eq $true){
$snServerNameTextBox.Enabled = $true
}
else{
$snServerNameTextBox.Enabled = $false}})
$form.Controls.Add($seAuthCheck)

# Add Get Perms Button

$gpgetperms = new-object System.Windows.Forms.Button
$gpgetperms.Location = new-object System.Drawing.Size(10,20)
$gpgetperms.Size = new-object System.Drawing.Size(140,23)
$gpgetperms.Text = "Enumerate Permissions"
$gpgetperms.Add_Click({GetPerms})
$form.Controls.Add($gpgetperms)

# 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)
$npNewpermDrop.Items.Add("None")
$npNewpermDrop.Items.Add("FreeBusyTimeOnly")
$npNewpermDrop.Items.Add("FreeBusyTimeAndSubjectAndLocation")
$npNewpermDrop.Items.Add("Reviewer")
$npNewpermDrop.Items.Add("Contributer")
$npNewpermDrop.Items.Add("Author")
$npNewpermDrop.Items.Add("NonEditingAuthor")
$npNewpermDrop.Items.Add("PublishingAuthor")
$npNewpermDrop.Items.Add("Editor")
$npNewpermDrop.Items.Add("PublishingEditor")
$form.Controls.Add($npNewpermDrop)

# 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"
$exButton.Add_Click({UpdatePerms})
$form.Controls.Add($exButton)

# 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"
$form.Controls.Add($OfGbox)

# Add DataGrid View

$dgDataGrid = new-object System.windows.forms.DataGridView
$dgDataGrid.Location = new-object System.Drawing.Size(10,130)
$dgDataGrid.size = new-object System.Drawing.Size(750,550)
$dgDataGrid.AutoSizeColumnsMode = "AllCells"
$dgDataGrid.SelectionMode = "FullRowSelect"
$form.Controls.Add($dgDataGrid)


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

$form.autoscroll = $true
$form.topmost = $true
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

Monday, January 17, 2011

Calculating Mailbox Sizes using Exchange Web Services and Powershell

Normally if you want to work out the size of a mailbox or the size of a mailbox folder as an administrator you would use get-mailboxstatistics and get-mailboxfolderstatistics the Exchange Management Shelll cmdlets which are the fastest and easiest way of retrieving this information on 2007 and 2010. However if you want to get the Mailbox size using normal user rights or the EMS cmdlets aren't an option then EWS can be used. There is no one property inside a mailbox that can be used to get the total size of a mailbox so like you needed to do in 2003 and earlier to calculate the size of a mailbox you need to get the size of each mailbox folder then sum these together. To get the Size of the Mailbox Folder and Deleted Items within a folder requires the use of the following extended Mapi properties

PR_MESSAGE_SIZE_EXTENDED and PR_DELETED_MESSAGE_SIZE_EXTENDED

To get the properties on all of the folders in a Mailbox a Deep Traversal findfolders operations from the Root of the Mailbox can be used.

One of useful things you can do with EWS that you can't do in EMS with the cmdlets is filter this query by the Mapi FolderClass (which is different from the folderscope property). In the large script posted below I've commented out the filter to do this but the lines are

##$folderItemType = "IPF.Note"
##$sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass, $folderItemType)
##$fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)


The normal EWS Managed API power shell script I've put a download of here the code looks like

$MailboxName = "user@domain.com"
##$folderItemType = "IPF.Note"
$rptCollection = @()


$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)


$windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$sidbind = "LDAP://<SID=" + $windowsIdentity.user.Value.ToString() + ">"
$aceuser = [ADSI]$sidbind
$service.AutodiscoverUrl($aceuser.mail.ToString(),{$true})

$TotalItemCount = 0
$TotalItemSize = 0

"Checking : " + $MailboxName
$folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000)
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);
$PR_DELETED_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26267,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);
$PR_DELETED_MSG_COUNT = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26176,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$psPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED);
$psPropertySet.Add($PR_DELETED_MESSAGE_SIZE_EXTENDED);
$psPropertySet.Add($PR_DELETED_MSG_COUNT);
$fvFolderView.PropertySet = $psPropertySet;
##$sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass, $folderItemType)
##$fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)
$fiResult = $Service.FindFolders($folderidcnt,$fvFolderView)
foreach($ffFolder in $fiResult.Folders){
$TotalItemCount = $TotalItemCount + $ffFolder.TotalCount;
$FolderSize = $null;
if ($ffFolder.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED,[ref] $FolderSize))
{
$TotalItemSize = $TotalItemSize + [Int64]$FolderSize
}
$DeletedItemFolderSize = $null;
if ($ffFolder.TryGetProperty($PR_DELETED_MESSAGE_SIZE_EXTENDED, [ref] $DeletedItemFolderSize))
{
$TotalDeletedItemSize = $TotalDeletedItemSize + [Int64]$DeletedItemFolderSize
}
$DeletedMsgCount = $null;
if ($ffFolder.TryGetProperty($PR_DELETED_MSG_COUNT, [ref] $DeletedMsgCount))
{
$TotalDeletedItemCount = $TotalDeletedItemCount + [Int32]$DeletedMsgCount;
}
}
$rptobj = "" | select DisplayName,LegacyDN,TotalItemSize,TotalItemCount,TotalDeletedItemSize,TotalDeletedItemCount
$rptobj.DisplayName = $_.DisplayName
$rptobj.LegacyDN = $_.LegacyExchangeDN
$rptobj.TotalItemCount = $TotalItemCount
$rptobj.TotalItemSize = $TotalItemSize
$rptobj.TotalDeletedItemSize = $TotalDeletedItemSize
$rptobj.TotalDeletedItemCount = $TotalDeletedItemCount
$rptCollection += $rptobj

$rptCollection