Tuesday, May 08, 2007

Exchange 2007 Mailbox Size Powershell Form Script version 2

I've created Version 3 of the script now that gets around having to use EWS

[Updated 17-5-1007 fixed URL bug]


A while back I posted version 1 of this script and a few people have made me aware lately that there are a few problems with the logic I used in this script. The first problem with this script is that it expects the CAS (Client Access Role) and the Mailbox role to be on the same server which may be true if your poor like me but for a lot of large deployments, roles will be split out onto different servers. So the method I’ve used will fail if this is the case because Exchange Web Services is part of the CAS role.

To solve this problem in version 2 I’ve used the Autodiscovery service which you can query with the email address of the user you want to access and Autodiscovery will then return the URL to use for EWS for this user. To make an autodicovery request you first need to find the service connection point for the Auto discovery service (the url to use in your request). The easy way to get this info is to use the Get-ClientAccessServer cmdlet eg

[array]$SCPCurrent = Get-ClientAccessServer

this will return an array that contains one or more entries from the CAS servers in your Exchange Organization and then you can pull the SCP from the first entry in the array.

$strRootURI = $SCPCurrent[0].AutoDiscoverServiceInternalUri.absoluteuri

Another method to get the SCP is just to query Active Directory directly via LDAP. Once you have the SCP entry all you need to do is then send a XML formatted request to the Autodiscovery service and then you should be able to parse the EWS url from the ASUrl property in the response

Eg this is the code section that was added to handle autodisco

## AutoDisco for EWS
[array]$SCPCurrent = Get-ClientAccessServer
$autodiscoResponse = "<Autodiscover xmlns=`"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006`">"
`
+ " <Request>"`
+ " <EMailAddress>" + $siSIDToSearch.WindowsEmailAddress + "</EMailAddress>"`
+ " <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>"`
+ " </Request>"`
+ "</Autodiscover>"
$strRootURI = $SCPCurrent[0].AutoDiscoverServiceInternalUri.absoluteuri
$WDRequest = [System.Net.WebRequest]::Create($strRootURI)
$WDRequest.ContentType = "text/xml"
$WDRequest.Headers.Add("Translate", "F")
$WDRequest.Method = "Post"
$WDRequest.UseDefaultCredentials = $True
$bytes = [System.Text.Encoding]::UTF8.GetBytes($autodiscoResponse)
$WDRequest.ContentLength = $bytes.Length
$RequestStream = $WDRequest.GetRequestStream()
$RequestStream.Write($bytes, 0, $bytes.Length)
$RequestStream.Close()
$WDResponse = $WDRequest.GetResponse()
$ResponseStream = $WDResponse.GetResponseStream()
$ResponseXmlDoc = new-object System.Xml.XmlDocument
$ResponseXmlDoc.Load($ResponseStream)
$EWSNodes = @($ResponseXmlDoc.getElementsByTagName("ASUrl"))
if ($EWSNodes.length -ne 0){
$ewsURL = $EWSNodes[0].'#text'
}
##


Another problem with this script revolves around authentication by default I’ve used impersonation as a method of accessing people’s mailboxes instead of delegation which was preferable because of the misuse that often happens with delegated accounts. But if you have resource mailboxes or any other disabled mailboxes impersonation will fail because you can’t impersonate a disabled account. So what I needed to do was add some more logic into to first detect if the account was disabled and if it was to change the request so instead of using impersonation that code dropped back to using delegation. This involved changing the SOAP request that was submitted. Delegation is described in the SDK here .

For delegation to work you must have given the account that the script is running under rights to the mailboxes in question. The Exchange Management Shell makes this pretty easy for instance to assign an account delegate access to every Room Mailbox you could use something like

get-user -recipienttypedetails 'RoomMailbox' | foreach-object {

Add-MailboxPermission $_.Identity -AccessRights FullAccess -user username

}

For details on how the rest of the code works please have a look at the previous post (link at the top of this post)

I’ve put a download copy of the new version here the script looks like

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

$unUserName = "UserName"
$psPassword = "password"
$dnDomainName = "domain"
$cdUsrCredentials = new-object System.Net.NetworkCredential($unUserName , $psPassword
, $dnDomainName)

function getMailboxSizes(){

$lbListView.clear()
$lbListView.Columns.Add("UserName",150)
$lbListView.Columns.Add("# Items",70)
$lbListView.Columns.Add("MB Size(MB)",80)
$lbListView.Columns.Add("DelItems (KB)",90)
$lbListView.Columns.Add("ID",0)

get-mailboxstatistics -Server $snServerNameDrop.SelectedItem.ToString() |
ForEach-Object{
$item1 = new-object System.Windows.Forms.ListViewItem($_.DisplayName)
$item1.SubItems.Add($_.ItemCount)
$item1.SubItems.Add($_.TotalItemSize.Value.ToMB() )
$item1.SubItems.Add($_.TotalDeletedItemSize.Value.ToKB())
$item1.SubItems.Add($_.Identity)

$lbListView.items.add($item1)
}

$form.Controls.Add($lbListView)
}

function enumFolderSizes($fsFolderIDtoSearch){
$private:enfsFilterString = "ParentID = '" + $fsFolderIDtoSearch + "'"
$private:enSubFolders = $fsTable.select($enfsFilterString)
for($fcount1 = 0;$fcount1 -le $enSubFolders.GetUpperBound(0); $fcount1++){
$global:fldSize = $global:fldSize + $enSubFolders[$fcount1][3]
$global:itemCount = $global:itemCount + $enSubFolders[$fcount1][5]
if ($enSubFolders[$fcount1][4] -ne 0){
enumFolderSizes($enSubFolders[$fcount1][1])
}

}
}

function BackFolder(){
$private:bfFilterString = "FolderID = '" + $global:LastFolder + "'"
$private:bfFolder = $fsTable.select($bfFilterString)
GetSubFolderSizes($bfFolder[0][2])

}

function GetSubFolderSizes($fiFIDToSearch){
$global:LastFolder = $fiFIDToSearch
$upButton.visible = $true
$lbFldListView.clear()
$lbFldListView.Columns.Add("Folder Name",150)
$lbFldListView.Columns.Add("# Items",80)
$lbFldListView.Columns.Add("Size(MB)",80)
$lbFldListView.Columns.Add("Has Sub",80)
$lbFldListView.Columns.Add("FID",0)
$subfsFilterString = "ParentID = '" + $fiFIDToSearch + "'"
$subFolders = $fstable.select($subfsFilterString)
for($fcount2 = 0;$fcount2 -le $subFolders.GetUpperBound(0); $fcount2++){
$global:fldSize = $subFolders[$fcount2][3]
$global:itemCount = $subFolders[$fcount2][5]
if ($subFolders[$fcount2][4] -ne 0){
enumFolderSizes($subFolders[$fcount2][1])
}
$item1 = new-object System.Windows.Forms.ListViewItem($subFolders[$fcount2][0])
$item1.SubItems.Add($global:itemCount)
$item1.SubItems.Add([math]::round(($fldsize/1mb),2))
if ($subFolders[$fcount2][4] -ne 0){
$item1.SubItems.Add("Yes")
}
else {
$item1.SubItems.Add("No")
}
$item1.SubItems.Add($subFolders[$fcount2][1])
$lbFldListView.items.add($item1)
}


}


function GetFolderSizes($siSIDToSearch){
$fsTable.clear()
$lbFldListView.clear()
$lbFldListView.Columns.Add("Folder Name",150)
$lbFldListView.Columns.Add("# Items",80)
$lbFldListView.Columns.Add("Size(MB)",80)
$lbFldListView.Columns.Add("Has Sub",80)
$lbFldListView.Columns.Add("FID",0)
$snServername = $snServerNameDrop.SelectedItem.ToString()
$siSIDToSearch = get-user $siSIDToSearch
write-host $siSIDToSearch.WindowsEmailAddress
## AutoDisco for EWS
[array]$SCPCurrent = Get-ClientAccessServer
$autodiscoResponse = "<Autodiscover xmlns=`"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006`">"
`
+ " <Request>"`
+ " <EMailAddress>" + $siSIDToSearch.WindowsEmailAddress + "</EMailAddress>"`
+ "
<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>"`
+ " </Request>"`
+ "</Autodiscover>"
$strRootURI = $SCPCurrent[0].AutoDiscoverServiceInternalUri.absoluteuri
$WDRequest = [System.Net.WebRequest]::Create($strRootURI)
$WDRequest.ContentType = "text/xml"
$WDRequest.Headers.Add("Translate", "F")
$WDRequest.Method = "Post"
$WDRequest.UseDefaultCredentials = $True
$bytes = [System.Text.Encoding]::UTF8.GetBytes($autodiscoResponse)
$WDRequest.ContentLength = $bytes.Length
$RequestStream = $WDRequest.GetRequestStream()
$RequestStream.Write($bytes, 0, $bytes.Length)
$RequestStream.Close()
$WDResponse = $WDRequest.GetResponse()
$ResponseStream = $WDResponse.GetResponseStream()
$ResponseXmlDoc = new-object System.Xml.XmlDocument
$ResponseXmlDoc.Load($ResponseStream)
$EWSNodes = @($ResponseXmlDoc.getElementsByTagName("ASUrl"))
if ($EWSNodes.length -ne 0){
$ewsURL = $EWSNodes[0].'#text'
}
##
write-host $ewsURL
$uoUser = [ADSI]("LDAP://" + $siSIDToSearch.DistinguishedName.ToString())
$smSoapMessage = "<?xml version='1.0' encoding='utf-8'?>" `
+ "<soap:Envelope xmlns:soap=`"http://schemas.xmlsoap.org/soap/envelope/`" " `
+ " xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`"
xmlns:xsd=`"http://www.w3.org/2001/XMLSchema`"" `
+ " xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`" >" `
+ "<soap:Header>"
if ($uoUser.PSBase.InvokeGet("AccountDisabled") -ne $true){
$smSoapMessage = $smSoapMessage + "<t:ExchangeImpersonation>" `
+ "<t:ConnectingSID>" `
+ "<t:SID>" + $siSIDToSearch.SID + "</t:SID>" `
+ "</t:ConnectingSID>" `
+ "</t:ExchangeImpersonation>" `
}
$smSoapMessage = $smSoapMessage + "</soap:Header>" `
+ "<soap:Body>" `
+ "<FindFolder
xmlns=`"http://schemas.microsoft.com/exchange/services/2006/messages`" " `
+ "xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`"
Traversal=`"Deep`"> " `
+ "<FolderShape>" `
+ "<t:BaseShape>AllProperties</t:BaseShape>" `
+ "<AdditionalProperties
xmlns=""http://schemas.microsoft.com/exchange/services/2006/types"">" `
+ "<ExtendedFieldURI PropertyTag=""0x0e08"" PropertyType=""Integer"" />" `
+ "</AdditionalProperties>" `
+ "</FolderShape>" `
+ "<ParentFolderIds>"
if ($uoUser.PSBase.InvokeGet("AccountDisabled") -eq $true){
$smSoapMessage = $smSoapMessage + "<DistinguishedFolderId Id=`"root`" "`
+ "xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`">" `
+ "<Mailbox><EmailAddress>"+ $siSIDToSearch.WindowsEmailAddress `
+ "</EmailAddress></Mailbox></DistinguishedFolderId>"
}
else{
$smSoapMessage = $smSoapMessage + "<t:DistinguishedFolderId Id=`"root`"/>"
}
$smSoapMessage = $smSoapMessage + "</ParentFolderIds>" `
+ "</FindFolder>" `
+ "</soap:Body></soap:Envelope>"
$strRootURI = $ewsURL
$WDRequest = [System.Net.WebRequest]::Create($strRootURI)
$WDRequest.ContentType = "text/xml"
$WDRequest.Headers.Add("Translate", "F")
$WDRequest.Method = "Post"
$WDRequest.Credentials = $cdUsrCredentials
$bytes = [System.Text.Encoding]::UTF8.GetBytes($smSoapMessage)
$WDRequest.ContentLength = $bytes.Length
$RequestStream = $WDRequest.GetRequestStream()
$RequestStream.Write($bytes, 0, $bytes.Length)
$RequestStream.Close()
$WDResponse = $WDRequest.GetResponse()
$ResponseStream = $WDResponse.GetResponseStream()
$ResponseXmlDoc = new-object System.Xml.XmlDocument
$ResponseXmlDoc.Load($ResponseStream)
$DisplayNameNodes = @($ResponseXmlDoc.getElementsByTagName("t:DisplayName"))
$ExtenedPropertyField = @($ResponseXmlDoc.getElementsByTagName("t:Value"))
$FolderIdNodes = @($ResponseXmlDoc.getElementsByTagName("t:FolderId"))
$ParentFolderIdNodes =
@($ResponseXmlDoc.getElementsByTagName("t:ParentFolderId"))
$ChildFolderCountNodes =
@($ResponseXmlDoc.getElementsByTagName("t:ChildFolderCount"))
$TotalItemCountNodes = @($ResponseXmlDoc.getElementsByTagName("t:TotalCount"))
for($i=0;$i -lt $DisplayNameNodes.Count;$i++){
if ($DisplayNameNodes[$i].'#text' -eq "Top of Information Store"){$rootFolderID
= $FolderIdNodes[$i].GetAttributeNode("Id").'#text'}
$fiFolderID = $FolderIdNodes[$i].GetAttributeNode("Id")
$pfParentFolderID = $ParentFolderIdNodes[$i].GetAttributeNode("Id")
$fsTable.Rows.Add($DisplayNameNodes[$i].'#text',$fiFolderID.'#text',$pfParentFolderID.'#text',$ExtenedPropertyField[$i].'#text',$ChildFolderCountNodes[$i].'#text',$TotalItemCountNodes[$i].'#text')
}
$fsFilterString = "ParentID = '" + $rootFolderID + "'"
$rrRootFolders = $fstable.select($fsFilterString)
for($fcount = 0;$fcount -le $rrRootFolders.GetUpperBound(0); $fcount++){
if ($rrRootFolders[$fcount][0] -ne "Top of Information Store"){
$global:fldSize = $rrRootFolders[$fcount][3]
$global:itemCount = $rrRootFolders[$fcount][5]
if ($rrRootFolders[$fcount][4] -ne 0){
enumFolderSizes($rrRootFolders[$fcount][1])
}
$item1 = new-object
System.Windows.Forms.ListViewItem($rrRootFolders[$fcount][0])
$item1.SubItems.Add($global:itemCount)
$item1.SubItems.Add([math]::round(($fldsize/1mb),2))
if ($rrRootFolders[$fcount][4] -ne 0){
$item1.SubItems.Add("Yes")
}
else {
$item1.SubItems.Add("No")
}
$item1.SubItems.Add($rrRootFolders[$fcount][1])
$lbFldListView.items.add($item1)
}
}

$form.Controls.Add($lbFldListView)
}
$form = new-object System.Windows.Forms.form
$global:LastFolder = ""
# Add DataTable

$Dataset = New-Object System.Data.DataSet
$fsTable = New-Object System.Data.DataTable
$fsTable.TableName = "Folder Sizes"
$fsTable.Columns.Add("DisplayName")
$fsTable.Columns.Add("FolderID")
$fsTable.Columns.Add("ParentID")
$fsTable.Columns.Add("Size)",[int])
$fsTable.Columns.Add("ChildFolderCount",[int])
$fsTable.Columns.Add("TotalCount",[int])
$Dataset.tables.add($fsTable)

# 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(100,20)
$snServerNamelableBox.Text = "ServerName"
$form.Controls.Add($snServerNamelableBox)

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

# Add List Box to DisplayMailboxs


$lbListView = new-object System.Windows.Forms.ListView
$lbListView.Location = new-object System.Drawing.Size(10,50)
$lbListView.size = new-object System.Drawing.Size(400,500)
$lbListView.LabelEdit = $True
$lbListView.AllowColumnReorder = $True
$lbListView.CheckBoxes = $False
$lbListView.FullRowSelect = $True
$lbListView.GridLines = $True
$lbListView.View = "Details"
$lbListView.Sorting = "Ascending"
$lbListView.add_click({GetFolderSizes($this.SelectedItems.item(0).subitems[4].text)});



# Add List Box to Display FolderSizes


$lbFldListView = new-object System.Windows.Forms.ListView
$lbFldListView.Location = new-object System.Drawing.Size(500,50)
$lbFldListView.size = new-object System.Drawing.Size(400,500)
$lbFldListView.LabelEdit = $True
$lbFldListView.AllowColumnReorder = $True
$lbFldListView.FullRowSelect = $True
$lbFldListView.GridLines = $True
$lbFldListView.View = "Details"
$lbFldListView.Sorting = "Ascending"
$lbFldListView.add_click({GetSubFolderSizes($this.SelectedItems.item(0).subitems[4].text)});


# UP folder Button

$upButton = new-object System.Windows.Forms.Button
$upButton.Location = new-object System.Drawing.Size(500,19)
$upButton.Size = new-object System.Drawing.Size(120,23)
$upButton.Text = "Back Folder level"
$upButton.visible = $false
$upButton.Add_Click({BackFolder})
$form.Controls.Add($upButton)

$form.Text = "Exchange 2007 Mailbox Size Form"
$form.size = new-object System.Drawing.Size(1000,600)
$form.autoscroll = $true
$form.topmost = $true
$form.Add_Shown({$form.Activate()})
$form.ShowDialog()

21 comments:

p0wer said...

It sure does look nice, great work. The main list displays fine, but when I click on an entry to display a drill-down, I'm getting this error:

Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (403) Forbidden."
At C:\Documents and Settings\xxx\mbsizereportv2\mbsizereportv2.ps1:172 char:37
+ $WDResponse = $WDRequest.GetResponse( <<<< )
Exception calling "Load" with "1" argument(s): "Root element is missing."
At C:\Documents and Settings\xxx\mbsizereportv2\mbsizereportv2.ps1:175 char:21
+ $ResponseXmlDoc.Load( <<<< $ResponseStream)

Glen said...

Thanks for this you found another mistake. In line 161

$strRootURI = $ewsURL.replace("https:","http:")

should have been

$strRootURI = $ewsURL

I had this in when i was testing it and forgot to remove it. Sorry let me know if it still doesn't work

Cheers
Glen

p0wer said...

It still does not work. This time it says:

Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (401) Unauthorized."
At C:\Documents and Settings\xxx\mbsizereportv2\mbsizereportv2.ps1:173 char:37
+ $WDResponse = $WDRequest.GetResponse( <<<< )
Exception calling "Load" with "1" argument(s): "Root element is missing."
At C:\Documents and Settings\xxx\mbsizereportv2\mbsizereportv2.ps1:176 char:21
+ $ResponseXmlDoc.Load( <<<< $ResponseStream)

It should work with SSL, why not? However, this seems like some misconfiguration on my server since it was not set up by me. I've tried running this script from administrator account as well, with no difference. I should add that other scripts like Top10 or age reports do not show any results either, only empty reports

Glen said...

Okay that doesn't look like a script error but a permissions error. The script uses Impersonation to access the Exchange store on behalf of the user. Impersonation needs to setup first for this to work i discussed how to setup impersonation in the first post "To access the folders within a mailbox the EWS code relies on Impersonation to be configured the Exchange SDK has details on how to give an account Impersonation rights it involves granting two rights the first is on the Server which allows a user to submit impersonation calls and the second is either on the user account you wish to access itself or the Mailbox database if you wanted to access all mailboxes within a particular mail store for this script you want to grant the latter permission on all stores that you want to be able to get the mailbox folder sizes from. For details of how to do this see. Once you have setup impersonation you then need to configure the code with the details of the user you have setup to allow impersonation. In line 3 there are the following 4 lines that affect this.
$unUserName = "username"
$psPassword = "password$"
$dnDomainName = "domain"
$cdUsrCredentials = new-object System.Net.NetworkCredential($unUserName , $psPassword , $dnDomainName)


If you instead want to use NTLM authentication from the user that is running the script so you don’t have hard code the username and password if you comment (or delete) the lines above in the script and change the following line (124) from


$WDRequest.Credentials = $cdUsrCredentials


To

$WDRequest.UseDefaultCredentials = $True

This will mean the code will run the EWS Findfolder operation under the security context of the user executing the script. The error’s you receive if you get access denied aren’t very descriptive you usually just get a 501 error so be wary or this.
"

The only thing that would cause this script to fail over SSL is if you are using the defualt SSL ceritifcation which is self signed and the machine where the script is running doesn't trust that certificate. The easy fix for this is to import the certificate from OWA locally to the PC where the script is running.

Glen said...

One more thing with the other scripts not working make sure you change then to use https if your using Exchange 2007. By default Exchange 2007 doesn't allow http to the admin virtual directory. The other thing with those scripts is you can remove or rem the on error resume next which will allow you to see any errors that might occur

Cheers
Glen

p0wer said...

I'm still getting error 401 even though I've set up impersonation on both server and database. Like I said, this also happens when using administrative account. It looks like Exchange 2007 was poorly set up since I also have problem with OAB :(

Glen said...

Sorry it doesn't work :(

Its sounds a bit weird did you configure the username,password and domain in the script. The other things i would try is flipping it from using the username in the script and just use the logged on credentials eg

WDRequest.Credentials = $cdUsrCredentials

To

$WDRequest.UseDefaultCredentials = $True

Andrew said...

Glen,

This script is awesome. Thanks for writing it. Is there a way I can sort the form fields to see who has the largest mailboxes?

Thanks again

Glen said...

Because i used Listview form controls they don't really support sorting. I would have preferred to use a datagrid that does allow sorting but the there is an issue with the way Powershell threads that i'd doesn't allows you to then execute the EWS code to retrieve mailbox folder sizes.

What you can do is in the code itself if you modify the get-mailboxstatistics line you can add sorting eg | sort TotalItemSize etc

Cheers
Glen

Justin said...

Possibly a great tool...I am getting the following error when drilling into a mailbox...

Exception calling "GetRequestStream" with "0" argument(s): "The underlying conn
ection was closed: Could not establish trust relationship for the SSL/TLS secur
e channel."
At C:\mbsizereportv2.ps1:112 char:45
+ $RequestStream = $WDRequest.GetRequestStream( <<<< )
You cannot call a method on a null-valued expression.
At C:\mbsizereportv2.ps1:113 char:21
+ $RequestStream.Write( <<<< $bytes, 0, $bytes.Length)
You cannot call a method on a null-valued expression.
At C:\mbsizereportv2.ps1:114 char:21
+ $RequestStream.Close( <<<< )
Exception calling "GetResponse" with "0" argument(s): "The underlying connectio
n was closed: Could not establish trust relationship for the SSL/TLS secure cha
nnel."
At C:\mbsizereportv2.ps1:115 char:37
+ $WDResponse = $WDRequest.GetResponse( <<<< )
You cannot call a method on a null-valued expression.
At C:\mbsizereportv2.ps1:116 char:48
+ $ResponseStream = $WDResponse.GetResponseStream( <<<< )
Exception calling "Load" with "1" argument(s): "The URL cannot be empty.
Parameter name: url"
At C:\mbsizereportv2.ps1:118 char:21
+ $ResponseXmlDoc.Load( <<<< $ResponseStream)

Exception calling "Create" with "1" argument(s): "Invalid URI: The URI is empty
."
At C:\mbsizereportv2.ps1:162 char:45
+ $WDRequest = [System.Net.WebRequest]::Create( <<<< $strRootURI)
Exception setting "UseDefaultCredentials": "This property cannot be set after w
riting has started."
At C:\mbsizereportv2.ps1:167 char:12
+ $WDRequest.U <<<< seDefaultCredentials = $True
Exception setting "ContentLength": "This property cannot be set after writing h
as started."
At C:\mbsizereportv2.ps1:169 char:12
+ $WDRequest.C <<<< ontentLength = $bytes.Length
Exception calling "GetRequestStream" with "0" argument(s): "The underlying conn
ection was closed: Could not establish trust relationship for the SSL/TLS secur
e channel."
At C:\mbsizereportv2.ps1:170 char:45
+ $RequestStream = $WDRequest.GetRequestStream( <<<< )
You cannot call a method on a null-valued expression.
At C:\mbsizereportv2.ps1:171 char:21
+ $RequestStream.Write( <<<< $bytes, 0, $bytes.Length)
You cannot call a method on a null-valued expression.
At C:\mbsizereportv2.ps1:172 char:21
+ $RequestStream.Close( <<<< )
Exception calling "GetResponse" with "0" argument(s): "The underlying connectio
n was closed: Could not establish trust relationship for the SSL/TLS secure cha
nnel."
At C:\mbsizereportv2.ps1:173 char:37
+ $WDResponse = $WDRequest.GetResponse( <<<< )
You cannot call a method on a null-valued expression.
At C:\mbsizereportv2.ps1:174 char:48
+ $ResponseStream = $WDResponse.GetResponseStream( <<<< )
Exception calling "Load" with "1" argument(s): "The URL cannot be empty.
Parameter name: url"
At C:\mbsizereportv2.ps1:176 char:21
+ $ResponseXmlDoc.Load( <<<< $ResponseStream)

Glen said...

That error is because you are using the default Self Signed Certificate. The script cant deal with the normal SSL cert error popups you get when you use a browser in OWA. To fix this all you need to do is import the SSL certificate used in OWA into your local cert store so you no longer get any cert errors in OWA. Once this is done it will fix the error in the script

Cheers
Glen

vikas said...

Hi Glen,

Congrats for the good work you are doing. Your posts are really useful.

I am new in the field of Exchange programming and powershell. Currently I am using ASP.Net & EWS, and I have to show the mail size and percentage of the mail quota being used to each user.

Can the same script be used for an web based mail solution?

Vikas

Glen said...

Depends what information you want to show. Quota information isn't stored in the Exchange Store its in Active Directory so you need to get that information from there (or sync it to a database and query it from there which is what i think Hosted services does). If you writing your own webmail service then i would write a WebService specifically for providing this information then you can hide what you doing in the backend in regards to rights API's etc and just present your Webmail server with a simple Web Service to retrieve this info. With EWS you can pull the size of the Mailbox folders you then need to sum these to get the full mailbox size.

Cheers
Glen

Uwe said...

Hey,
this is a great script, but i have problems to get it run. I have done the impersonation for my user, but with no access.

I get the following error:

Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (500) I
nternal Server Error."
At D:\Install\mbsizereportv2.ps1:172 char:37
+ $WDResponse = $WDRequest.GetResponse( <<<< )
Exception calling "Load" with "1" argument(s): "Root element is missing."
At D:\Install\mbsizereportv2.ps1:175 char:21
+ $ResponseXmlDoc.Load( <<<< $ResponseStream)

Please can you help me?
Thanks
Uwe

Glen said...

I don't think its security related because you would usually get a 401 error is that was the case. Maybe the autodicovery section is returning a invalid CAS address. The script should echo out the CAS address it finds which you should see before the error is produced. Is that CAS address accessible. Have you made sure that you trust the SSL cert thats being used on that CAS.

Cheers
Glen

Mike said...

Thanks Glen for putting this all together, it's the nicest presentation of the cmdlet I've seen.

Couple things I've noticed:

1) I used your script to find a user with a huge mailbox (3.2Gb) and it showed correctly, but in the mailbox folder list, it showed a large folder (> 2gb) as a negative number. Possible signed overflow?

2) You've mentioned above the non-sorting of the list and the code change to get a sorted list. Would it be possible to implement buttons on the control to do this? ie. one button that will return an alphabetical list, and one to return a sorted mailbox size list.

Glen said...

Hi Mike,

Yeah the 2 GB issue is because its using 32 Integers I should have used 64 Bit Intergers (I just dont have any mailboxes that large on the server im using this on). I'll try to fix that bug by the end of the week).

You could do the sorting with buttons maybe radio buttons would be better so you just select the way you want it sorted before you do the query. Then the routines behind can handle this. It should be relatively easy I'll see if i can try that latter in the week (bit flat out at the moment).

Cheers
Glen

Arnold said...

Glen,

Your MBSize script works very well, I was hoping you could add a couple of features:

1. The ability to sort by size, I click on the heading and nothing happens.

2. I would like to schedule a report to be generated at a certain time. Maybe something like: mbsizereportv3.ps1 -server "MyExchange2007" -sender "MBSizeReporter@mydomain.com" -receiptent "JoeBlow@mydomain.com" -scheduleday "Sunday" -scheduletime "7:00pm" -frequency "Weekly"

I am still pretty green on the Management Shell, so maybe there is an easier way...

Glen said...

Okay Version 3 is now live please seen the link at the top of this post i hope this address most things

Cheers
Glen

Andre said...

this thing looks great. I'm getting an error though. I get
"The term 'get-mailboxserver' is not recognized as a cmdlet, fucntion, operable program, or script file. verty the term and try again. Line 239 char:18


This is odd considering I can run that cmdlet from my powershell. Any ideas?

Glen said...

This script needs to be run from within the Exchange Management Shell because it uses the Exchange 2007 cmdlets. (you can load these in a normal powershell session using Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin) Also i would recommend that you run Version 4 http://gsexdev.blogspot.com/2008/02/version-4-of-mailbox-size-gui-and-using.html which fixes a lot bugs that where in this version

Cheers
Glen