Thursday, October 02, 2008

Exchange Reverse Permission audit Powershell Gui version 2 Exchange 2007

This script builds on a script I posted earlier in the year that produced a GUI that showed permissions and reverse permissions for Exchange Mailboxes (eg what other mailboxes a particular mailbox has access to). The previous version just looked at the Mailbox Rights and Send AS/Receive AS rights. This new version using Exchange Web Services looks at firstly the rights on the shared folders in a mailbox (eg Root,calendar,inbox,notes,task,journal) and also the Outlook Delegates and the rights assigned to the delegates such as Private Items access and Meeting recipients as well as all the other permissions it did previously. Sorry i haven't got the snapshot functionality done yet which i think i promised before but...

The cool thing is this script now lets us answer a bunch of new questions such as

Which users have given acess to paricular folders in their Mailbox to other people(including the default user which may mean the have unwillingly delegated access to everyone). And we can look at the reverse of this which is what mailboxes folders a particular mailbox has been given access to.

We can see all the Outlook delegations that have been configured and who will receive who's meeting request etc. And the reverse which is what delegate meeting request a particular mailbox will receive.

The big thing is that this script pulls together all the disparate permissions you have to consider when determining mailbox access and displays them in one location so any of the above type of questions become exceedingly easier to answer. Well that's the theory anyway.

This script uses the EWS Powershell library I've been building for a while to provide some eaiser to use functions when accessing Exchange Web Services from Powershell . The ability to access/set folder permissions was added in SP1 with Exchange Web Services. Folder permissions are a little tricky to deal with as I've talked about in the past, basically you have two different types of permission sets, calendarpermissions and normal folder permissions. So its important to detect and differentiate between them and there is also a problem with calendar permissions in that the Outlook Roles don't map to the CalendarPermissionLevelType in EWS. Fortunately i solved this one in the past with a few helper routines that will map the custom levels back to their correct Outlook roles so they make a bit more sense. The delegate code is pretty simple and just returns the outlook delegates as a genericlist.

To report on folder permissions and Delegates using Exchange Web Services you need to have rights on the mailboxes you are going to report on. There are a few ways of granting these rights firstly you can do this by granting those rights to a specific user using add-mailboxpermission as documented here. Or you can grant the rights via EWS impersonation by allowing impersonation on the server and mailStores your going to access see this. To allow for these varying degrees of rights allocations when you run the script its will first prompt you for the EWS authentication and Autodiscover settings. If you have autodiscover configured correctly the script should be able to query AD to find the correct EWS URL to use otherwise you can manually input the URL to your CAS which would look something like "https://casservername.domain.com/ews/exchange.asmx". Once you have made the EWS authentication choices the script will start querying active directory and the mailboxes for every users permissions and depending on the number of mailboxes and users you have hopefully eventually get back to you with a result.

To use this script you need to download the latest copy of the EWSUtil from here and put it in the c:\temp directory as this is where the script expect the dll eg

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

I've put a download of the script here the new bits of the code look like.

if ($seAuthCheck.Checked -eq $false) {
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$useImp, $unUserNameTextBox.Text, $unPasswordTextBox.Text, $unDomainTextBox.Text,$casUrl)
}
else{
$ewc = new-object EWSUtil.EWSConnection($mbMailboxEmail,$useImp, "", "", "",$casUrl)
}
$fldarry = new-object EWSUtil.EWS.BaseFolderIdType[] 6
for ($fcint=0;$fcint -lt 6;$fcint++){
$dTypeFld = new-object EWSUtil.EWS.DistinguishedFolderIdType

switch ($fcint){
0 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::inbox}
1 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::calendar}
2 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::contacts}
3 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::tasks}
4 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::journal}
5 {$dTypeFld.Id = [EWSUtil.EWS.DistinguishedFolderIdNameType]::msgfolderroot}
}
$mbMailbox = new-object EWSUtil.EWS.EmailAddressType
$mbMailbox.EmailAddress = $mbMailboxEmail
$dTypeFld.Mailbox = $mbMailbox
$fldarry[$fcint] = $dTypeFld
}
$Folders = $ewc.GetFolder($fldarry)
If ($Folders.Count -ne 0) {
ForEach ($Folder in $Folders) {
if ($Folder.GetType() -eq [EWSUtil.EWS.CalendarFolderType]){
ForEach ($Permissions in $Folder.PermissionSet.CalendarPermissions){
if ($Permissions.UserId.DistinguishedUserSpecified -eq $false){
$sidbind = "LDAP://sid="
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),$aceuser.samaccountname.ToString(),$ewc.enumOutlookRole($Permissions),"Allow")
$fpCount++
if ($rvFolderPerms.Containskey($aceuser.samaccountname.ToString())){
$rvFolderPerms[$aceuser.samaccountname.ToString()] = [int]$rvFolderPerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvFolderPerms.add($aceuser.samaccountname.ToString(),1)
}
}
else{
if ($Permissions.UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Default){
if ($Permissions.CalendarPermissionLevel -ne [EWSUtil.EWS.CalendarPermissionLevelType]::None){
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),"Default",$ewc.enumOutlookRole($Permissions),"Allow")
$dfCount++
}
}
}
}


}
else {
ForEach ($Permissions in $Folder.PermissionSet.Permissions){
if ($Permissions.UserId.DistinguishedUserSpecified -eq $false){
$sidbind = "LDAP://sid="
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),$aceuser.samaccountname.ToString(),$Permissions.PermissionLevel.ToString(),"Allow")
$fpCount++
if ($rvFolderPerms.Containskey($aceuser.samaccountname.ToString())){
$rvFolderPerms[$aceuser.samaccountname.ToString()] = [int]$rvFolderPerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvFolderPerms.add($aceuser.samaccountname.ToString(),1)
}
}
else{
if ($Permissions.UserId.DistinguishedUser -eq [EWSUtil.EWS.DistinguishedUserType]::Default){
if ($Permissions.PermissionLevel -ne [EWSUtil.EWS.PermissionLevelType]::None){
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),("FolderRight-" + $Folder.DisplayName),"Default",$Permissions.PermissionLevel.ToString(),"Allow")
$dfCount++
}
}
}
}
}
}
}
$delegates = $ewc.getdeletgates($mbMailboxEmail)
foreach ($delegate in $delegates){
$sidbind = "LDAP://sid="
$AceName = $ace.IdentityReference.Value
$aceuser = [ADSI]$sidbind
$dgCount++
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),"DelegateRight",$aceuser.samaccountname.ToString(),"Mailbox Delegated","Allow")
if ($delegate.DelegateUser.ReceiveCopiesOfMeetingMessagesSpecified -eq $true){
if ($delegate.DelegateUser.ReceiveCopiesOfMeetingMessages-eq $true){
$dgCount++
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),"DelegateRight",$aceuser.samaccountname.ToString(),"Recieve Meetings","Allow")
if ($rvDelegatePerms.Containskey($aceuser.samaccountname.ToString())){
$rvDelegatePerms[$aceuser.samaccountname.ToString()] = [int]$rvDelegatePerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvDelegatePerms.add($aceuser.samaccountname.ToString(),1)
}
}
}

if ($delegate.DelegateUser.ViewPrivateItemsSpecified -eq $true){
if ($delegate.DelegateUser.ViewPrivateItems-eq $true){
$dgCount++
[VOID]$rsTable.rows.add($uoUserobject.samaccountname.ToString(),"DelegateRight",$aceuser.samaccountname.ToString(),"View Private Items","Allow")
if ($rvDelegatePerms.Containskey($aceuser.samaccountname.ToString())){
$rvDelegatePerms[$aceuser.samaccountname.ToString()] = [int]$rvDelegatePerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvDelegatePerms.add($aceuser.samaccountname.ToString(),1)
}
}
}
if ($rvDelegatePerms.Containskey($aceuser.samaccountname.ToString())){
$rvDelegatePerms[$aceuser.samaccountname.ToString()] = [int]$rvDelegatePerms[$aceuser.samaccountname.ToString()] +1
}
else {
$rvDelegatePerms.add($aceuser.samaccountname.ToString(),1)
}

}

31 comments:

Al Sheppard said...

Jeez, I'm impressed by this one and I can see it will be very helpful, however it only seems to work with the first 1000 or so mailbox and mailusers in our test setup - how can I get all of them - is there a ResultSize that can be configured?

Thanks

Alastair

Glen said...

I'd somehow managed to leave

$dfsearcher.PageSize = 1000

Out which is a bit weird because i had this in the original script. Anyway i've updated the download which should fix the issue with only 1000 results being returned.

Cheers
Glen

Anonymous said...

Great script!
Do you have a script that will do this on an exchange 2003 server?

Glen said...

Version 1 will work on 2003 http://gsexdev.blogspot.com/2008/04/exchange-permission-and-reverse.html but this doesn't do mailbox or delegate permission. I have older vbs scripts that do mailbox permission but not anything that will do delegates as there wasn't really any good API in 2003 to retrieve this information. http://gsexdev.blogspot.com/2005/06/reverse-permissions-audit-scripts-part.html

Cheers
Glen

Anonymous said...

Glen,

This is a great script. I have a question however. If I would want to change permissions on the user's mailbox (for example) manually as admin where would I do it? ADUC->Security, ADSIEDIT? I ran your script and found that one of my users is denying "Full Mailbox Access" of his mailbox (MailboxRight) to Administrator's account. However when I look under "Manage Full Access Permission ..." nothing is set there except "NT Authority/Self." But if I grant Administrator's account "Full Mailbox Access" it seems that it removes "Deny" permission for Administrator and then grants Administrator Full access (see below):

Summary: 2 item(s). 2 succeeded, 0 failed.
Elapsed time: 00:00:00


TAA\Administrator
Completed

Exchange Management Shell command completed:
Remove-MailboxPermission -Identity 'CN=Maxwell\, Robert,OU=TAA Users,DC=TAA,DC=net' -User 'TAA\Administrator' -Deny -InheritanceType 'All' -AccessRights 'FullAccess'

Elapsed Time: 00:00:00

TAA\Administrator
Completed

Exchange Management Shell command completed:
Add-MailboxPermission -Identity 'CN=Maxwell\, Robert,OU=TAA Users,DC=TAA,DC=net' -User 'TAA\Administrator' -AccessRights 'FullAccess'

Elapsed Time: 00:00:00

This leads me to believe that "deny" permission is set somewhere else, but I don't know where. Can you help?

Glen said...

The only method you should use to set mailbox permissions in Exchange 2007 is to use the Exchange Management Console or the Exchange Management Shell. You should not try to modify mailbox permissions with ADSI edit. By default administrators are denied access to every mailbox other then their own this is gernally a inherited rights not a Explicit right. This script only checks explicit rights. I would suggest having a read of http://support.microsoft.com/default.aspx?scid=kb;EN-US;310866

Cheers
Glen

David Allen said...

How would one go about modifying this script to only make it run against a specific OU? We would like to be able to track permissions for our conference room resources, but not have to audit the entire GAL to do so.

Thanks!

-David

Glen said...

All you need to is change the root search path so change

$dfDefaultRootPath = "LDAP://" + $root.DefaultNamingContext.tostring()

to

$dfDefaultRootPath = "LDAP://yourOuDN "

replace yourOuDN with the Distigushed name of the OU you want as the search path.

Cheers
Glen

Anonymous said...

I tried running this script and it works great but for one user i get the following error "Exception calling "GetFolder" with "1" argument(s): "Impersonation failed."
At :line:140 char:26
+ $Folders = $ewc.GetFolder <<<< ($fldarry)"

I have verfied that I have the proper permissions to impersonate the user.

any ideas?

Thanks

Glen said...

Is this user disabled ? You cant impersonated a disabled user well you can but you end up impersonating the disabled part of the user meaning that you will never be able to access that users mailbox.

rafael said...

Hi, I'm trying this script but I get the following - can you tell me what you think the problem might be? It seems to throw these same errors for each mailbox it tried to process. Server is 2008 SP2 Enterprise with E2K7 SP1 rollup 7.
*************************

Processing Mailbox - johndoe
Doing Mailbox Permissions with EWS
New-Object : Exception calling ".ctor" with "6" argument(s): "The remote server returned an error: (404) Not Found."
At C:\temp\revpermguiv2.ps1:121 char:20
+ $ewc = new-object <<<< EWSUtil.EWSConnection($mbMailboxEmail,$useImp, "", "", "",$casUrl)
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

You cannot call a method on a null-valued expression.
At C:\temp\revpermguiv2.ps1:140 char:27
+ $Folders = $ewc.GetFolder <<<< ($fldarry)
+ CategoryInfo : InvalidOperation: (GetFolder:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At C:\temp\revpermguiv2.ps1:143 char:23
+ if ($Folder.GetType <<<< () -eq [EWSUtil.EWS.CalendarFolderType]){
+ CategoryInfo : InvalidOperation: (GetType:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At C:\temp\revpermguiv2.ps1:197 char:33
+ $delegates = $ewc.getdeletgates <<<< ($mbMailboxEmail)
+ CategoryInfo : InvalidOperation: (getdeletgates:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

You cannot call a method on a null-valued expression.
At C:\temp\revpermguiv2.ps1:199 char:73
+ $sidbind = "LDAP://SID=" + $delegate.DelegateUser.UserId.SID.ToString <<<< () + ""
+ CategoryInfo : InvalidOperation: (ToString:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull

Glen said...

sounds like Autodiscover is failing you might want to try putting in the you cas uri eg https://yourservername/ews/exchange.asmx

Cheers
Glen

Anonymous said...

This script works great, one question though. When it completes you have the option to export it to a csv file. Is there anyway to automatically make that happen. I love for the script to run and export to csv so I can run it on a schedule.

Glen said...

You need to really rewirte the script then a pull the parts you want out and come up with something that can be run from Task Scheduler.

Cheers
Glen

Anonymous said...

I'd like to see this automated too. Is anyone out there doing this? Have it save to a file when it's done without user interaction.

Anonymous said...

Great Script, but I have difficulty distinguishing between different ACE types (I think it supposed to be ACL types it's not typo is t?).

What is the differece between Mailbox-Rights, Folder-Rights and Delegate-Rights?

Glen said...

ACE makes more sense to me as this script looks at individual rights that are on ACL's.

Folder Rights are the Mapi DACLS

Delegate Rigths are these rights that are set via the Outlook Delegation API's

If you want more details look at the appropriate Exchange protocol document http://msdn.microsoft.com/en-us/library/cc425499(EXCHG.80).aspx

Cheers
Glen

Nivritti said...

Im having trouble with getting the status of the mailbox deligate flag "view private items". any ideas? My account is Exchange Organisation Administrator and also FULL on all mailboxes.

Anonymous said...

Great script!

Anonymous said...

Glen,
Very helpful, but I have a question. I have a user that has given another user "reviewer rights" to the mailbox and "publishing editor" to the inbox. Yet when i run this script nether of those are displayed. It is working for other mailboxes within the same OU . I'm planning on providing a list of users that have rights to the "Partners of our firm" mailboxes and I'm afraid that my list is not complete. I received the following errors while running the script and I'm not sure if one of these could be an issue. Any feedback on this would be much appreciated.

New-Object : Exception calling ".ctor" with "6" argument(s): "Exception of type
'EWSUtil.AutoDiscoveryException' was thrown."
At C:\scripts\revpermguiv2.ps1:121 char:20
+ $ewc = new-object <<<< EWSUtil.EWSConnection($mbMailboxEmail,$useImp
, "", "", "",$casUrl)
You cannot call a method on a null-valued expression.
At C:\scripts\revpermguiv2.ps1:199 char:73
+ $sidbind = "LDAP://"
Cannot index into a null array.
At C:\scripts\revpermguiv2.ps1:19 char:66
+ [byte[]]$DaclByte = $emProps["msexchmailboxsecuritydescriptor"][0 <<<< ]
New-Object : Exception calling ".ctor" with "6" argument(s): "Exception of type
'EWSUtil.AutoDiscoveryException' was thrown."
At C:\scripts\revpermguiv2.ps1:121 char:20
+ $ewc = new-object <<<< EWSUtil.EWSConnection($mbMailboxEmail,$useImp
, "", "", "",$casUrl)
Cannot index into a null array.
At C:\scripts\revpermguiv2.ps1:19 char:66
+ [byte[]]$DaclByte = $emProps["msexchmailboxsecuritydescriptor"][0 <<<< ]
New-Object : Exception calling ".ctor" with "6" argument(s): "Exception of type
'EWSUtil.AutoDiscoveryException' was thrown."
At C:\scripts\revpermguiv2.ps1:121 char:20
+ $ewc = new-object <<<< EWSUtil.EWSConnection($mbMailboxEmail,$useImp
, "", "", "",$casUrl)

Anonymous said...

This is money!!! I have been doing Exchange migrations for a long time and my eyes are still bloodshot from PFDavAdmin ... nice work!!

Anonymous said...

Hi Glen

Great Script btw

i modified your script because we only want to see the MBoxPerm, Send-AS and export them into a file.
but i've one problem
i get permissions which aren't defined anymore.
e.g. my user is still shown to have Full Mailbox Access, Take Ownership and Delete mailbox storage although i removed my access to the mailbox somewhen last week

i crosschecked with your original script and get the same permissions.

do you know of such an issue ?

best regards
thomas

Anonymous said...

Can you include the "Notes" folder as well?

Anonymous said...

Hello Glen,

This appears to be running without any errors, but I receive the following on virtually every account that it encounters.

Processing Mailbox - Bravo, Johnny.
Doing Mailbox Permissions with EWS
Error Getting Delegates from Mailbox JBravo@domain.com

I am using an account that has every level of access to Exchange available.

Regards,
Brian

Devin Greco said...

Sorry it worked, patience is a virtue.

Devin Greco said...

Glen,
Worked well beyond my expectations. I just want to point out I tested this and it also worked flawlessly in Exchange 2010. God send.

Anonymous said...

HI I am getting this error when running

Error Getting Delegates from Mailbox Mailbox name, does this mean there are no delegates to extract?

Anonymous said...

Glen,
anyway I can export everything I see in gui to csv(all tabs)
or just directly export it without showing gui at all to csv?
Thanks

Mark Harrison said...

This is brilliant. The only thing that would make it better is being able to use it for a single user (username on first form perhaps), rather than have it go through all 17,000 mailboxes we have. Can you point me to the line where I could change it to just one mailbox? I can do the form bit and setting variables etc, just not sure which line..

Glen Scales said...

Not really it has to evaluate all the users to give reverse permissions, otherwise if you just want the permission use Get-MailboxPermissions

Keith said...

Glen,
This is a brilliant script, thanks for sharing this knowledge and making it available.
Two questions:
1. Is there any way to change or add a column for the users Display Name or CN? My SAMAccount names are 4 digits so it's a bit hard to decipher.
2. Is there any way to automatically export the results to a CSV or XLS file? \
Thanks