Monday, June 27, 2005

Reverse Permissions Audit Scripts Part 2

*****If your looking for a Exchange 2007/2010 solution have a look at this post http://gsexdev.blogspot.com/2008/04/exchange-permission-and-reverse.html *****

In April I posted this script that did a reverse lookup of mailbox permissions that had been assigned via Active Directory Users and Computers. This was to give a view by user of what mailbox’s a particular user had access to (instead of the normal view which is who has access to this mailbox). The second part of the permission story with exchange is the actually MAPI permissions that are set on each of the folders in a mailbox. When you delegate folders in outlook this will modify the rights of each of the folders that you delegate (eg calendar,contact,tasks). To view the Mapi permissions on a folder programmatically from a script there are a few methods that can be used the easiest way to do it is to use the Acl.dll from the platform SDK see KB240911 for details for more details. The basis of this article can be used to create a conventional script that will log the rights on each folder and save this to a CSV file. You can then open the CSV in Excel do a auto filter on the folder you want to audit and you very quick and useful report. I’ve included a conventional version of a script that does just this in the download for this post this script takes the name of the server you want to run it against as a command line parameter. Both these scripts require the acl.dll to be registered on the machine you are running the script on. Acl.dll can be found in the Platform SDK or downloaded as part of this CDOlive sample .

With the reverse MAPI permission script what I’ve done is to take the data shaping logic I used previously and applied it to all the root mailbox folders eg the root of the mailbox tree and the calendar,inbox,contacts,tasks,notes and sent item folders. So what the script will output is a per user view of what other Users’s mailbox folders each user has access to. So this script could be used to answer the question what calendars is fred-blogs able to see. To do this the script first looks up Active Directory for all users that have a mailbox in a domain and creates a parent record-set of these values. Then for every user it tries to establish a MAPI connection to there mailbox via CDO 1.2 and enumerates permissions on the root folders in the mailbox and writes these permissions to the child record-set. The recordset’s are then related on the Exchange LegacyDN and the address retrieved from CDO addressEntry object.

The script tries to connect to every mailbox in a domain and expects that the user running it will have full rights to every mailbox in the domain equiv to http://support.microsoft.com/?kbid=262054 . I’ve put a download copy of both scripts that I’ve talked about here. The code for the reverse permissions script looks like


on error resume next
set conn1 = createobject("ADODB.Connection")
strConnString = "Data Provider=NONE; Provider=MSDataShape"
conn1.Open strConnString
set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strNameingContext = iAdRootDSE.Get("defaultNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
Query = "<LDAP://" & strNameingContext & ">;(&(&(&(& (mailnickname=*)" & _
"(!msExchHideFromAddressLists=TRUE) (| (&(objectCategory=person)(objectClass=user)"
& _
"(|(homeMDB=*)(msExchHomeServerName=*))) )))))" & _
";samaccountname,legacyExchangeDN,msExchHomeServerName," &_
"displayname,distinguishedName;subtree"
Com.ActiveConnection = Conn
Com.CommandText = Query
Com.Properties("Page Size") = 1000
set objParentRS = createobject("adodb.recordset")
set objChildRS = createobject("adodb.recordset")
strSQL = "SHAPE APPEND" & _
" NEW adVarChar(255) AS UOADDisplayName, " & _
" NEW adVarChar(255) AS UOADTrusteeName, " & _
" NEW adVarChar(255) AS UOADLegacyDN, " & _
" ((SHAPE APPEND " & _
" NEW adVarChar(255) AS MRmbox, " & _
" NEW adVarChar(255) AS MRFolder, " & _
" NEW adVarChar(255) AS MRTrusteeName, " & _
" NEW adVarChar(255) AS MRRights) " & _
" RELATE UOADLegacyDN TO MRTrusteeName) AS rsUOMR"
objParentRS.LockType = 3
objParentRS.Open strSQL, conn1

Set Rs = Com.Execute
While Not Rs.EOF
objParentRS.addnew
objParentRS("UOADDisplayName") = rs.fields("displayname")
objParentRS("UOADTrusteeName") = rs.fields("samaccountname")
objParentRS("UOADLegacyDN") = rs.fields("legacyExchangeDN")
objParentRS.update
Set objChildRS = objParentRS("rsUOMR").Value
inplinearray = Split(rs.fields("msExchHomeServerName").value, "=", -1, 1)
CDOAddr =
getpermissions(inplinearray(ubound(inplinearray)),rs.fields("samaccountname").value,rs.fields("displayname").value)
rs.movenext
Wend
wscript.echo "Number of Mailboxes Checked " & objParentRS.recordcount
Wscript.echo

objParentRS.MoveFirst
Do While Not objParentRS.EOF
Set objChildRS = objParentRS("rsUOMR").Value
if objChildRS.recordcount <> 0 then wscript.echo objParentRS("UOADDisplayName")
Do While Not objChildRS.EOF
wscript.echo " " & objChildRS.fields("MRmbox") & _
" " & objChildRS.fields("MRFolder") & " " & objChildRS.fields("MRRights")
objChildRS.movenext
loop
objParentRS.MoveNext
loop

function getpermissions(servername,mailboxname,displayname)
wscript.echo "Processing " & mailboxname
Set objSession = CreateObject("MAPI.Session")
objSession.Logon "","",false,true,true,true,servername & vbLF & mailboxname
if err.number <> 0 then wscript.echo err.description
err.clear
set objCuser = objSession.CurrentUser
Set CdoInfoStore = objSession.GetInfoStore
Set CdoFolderRoot = CdoInfoStore.RootFolder
Set ACLObj = CreateObject("MSExchange.aclobject")
ACLObj.CDOItem = CdoFolderRoot
Set FolderACEs = ACLObj.ACEs
For each fldace in FolderACEs
if cstr(objCuser.address) <> cstr(GetACLEntryName(fldace.ID, objSession)) then
objChildRS.addnew
objChildRS("MRmbox") = mailboxname
objChildRS("MRFolder") = "Root"
objChildRS("MRTrusteeName") = GetACLEntryName(fldace.ID, objSession)
objChildRS("MRRights") = DispACERules(fldace)
objChildRS.update
end if
Next
Set CdoFolders = CdoFolderRoot.Folders
Set CdoFolder = CdoFolders.GetFirst
do while Not (CdoFolder Is Nothing)
ACLObj.CDOItem = CdoFolder
Set FolderACEs = ACLObj.ACEs
For each fldace in FolderACEs
if cstr(objCuser.address) <> cstr(GetACLEntryName(fldace.ID, objSession)) then

objChildRS.addnew
objChildRS("MRmbox") = mailboxname
objChildRS("MRFolder") = CdoFolder.Name
objChildRS("MRTrusteeName") = GetACLEntryName(fldace.ID, objSession)
objChildRS("MRRights") = DispACERules(fldace)
objChildRS.update
end if
Next
Set CdoFolder = CdoFolders.GetNext
loop
if Not objSession Is Nothing Then objSession.Logoff
set objSession = nothing
End function

Function GetACLEntryName(ACLEntryID,SubSession)

select case ACLEntryID
case "ID_ACL_DEFAULT"
GetACLEntryName = "Default"
case "ID_ACL_ANONYMOUS"
GetACLEntryName = "Anonymous"
case else
Set tmpEntry = SubSession.GetAddressEntry(ACLEntryID)
tmpName = tmpEntry.address
GetACLEntryName = tmpName
end select
Set objSession = nothing
Set CdoFolderRoot = nothing
Set ACLObj = nothing
Set FolderACEs = nothing
Set objSession = nothing

End Function

Function DispACERules(DisptmpACE)

Select Case DisptmpACE.Rights

Case ROLE_NONE, 0 ' Checking in case the role has not been set on that entry.
DispACERules = "None"
Case 1024 ' Check value since ROLE_NONE is incorrect
DispACERules = "None"
Case ROLE_AUTHOR
DispACERules = "Author"
Case 1051 ' Check value since ROLE_AUTHOR is incorrect
DispACERules = "Author"
Case ROLE_CONTRIBUTOR
DispACERules = "Contributor"
Case 1026 ' Check value since ROLE_CONTRIBUTOR is incorrect
DispACERules = "Contributor"
Case 1147 ' Check value since ROLE_EDITOR is incorrect
DispACERules = "Editor"
Case ROLE_NONEDITING_AUTHOR
DispACERules = "Nonediting Author"
Case 1043 ' Check value since ROLE_NONEDITING AUTHOR is incorrect
DispACERules = "Nonediting Author"
Case 2043 ' Check value since ROLE_OWNER is incorrect
DispACERules = "Owner"
Case ROLE_PUBLISH_AUTHOR
DispACERules = "Publishing Author"
Case 1179 ' Check value since ROLE_PUBLISHING_AUTHOR is incorrect
DispACERules = "Publishing Author"
Case 1275 ' Check value since ROLE_PUBLISH_EDITOR is incorrect
DispACERules = "Publishing Editor"
Case ROLE_REVIEWER
DispACERules = "Reviewer"
Case 1025 ' Check value since ROLE_REVIEWER is incorrect
DispACERules = "Reviewer"
Case Else
DispACERules = "Custom"
End Select

End Function

18 comments:

Dale said...

Has anyone had any luck with this script? I was able to get it to run but it processes 4 random mailboxes and then challenges me for a username and password to connect to the exchange server. I made sure I created a group that has full mailbox access and added myself although I am a domain admin as well. If I look at the mailboxes I am the last one to have logged on to most peoples boxes but not all. Any ideas?

Anonymous said...

Thanks for the script.

How could this be used to find Outlook users who have accidentally opened up certain of their subfolders to the "world" for reading?

Glen said...

This script only does root folders which is generally all you really need to worry about because if a person doesn't have rights to the root folder they wont be able to connect the subfolder even if they do have rights there. Otherwise you need to rewrite the script to recurse every subfolder not imposible but would increase the run time of the script considerable if you uses have a lot of sub folders.

Cheers
Glen

Anonymous said...

Glen,

Nice work. However, with this I'm getting an error...

Microsoft VBScript runtime error: ActiveX component can't create object: 'MSExchange.aclobject'

Anonymous said...

Glen,

Nice work. However, with this I'm getting an error...

Microsoft VBScript runtime error: ActiveX component can't create object: 'MSExchange.aclobject'

Glen said...

The error means that you dont have the Acl.dll registered on the machine you are trying to use the script on.You can get a copy of the acl.dll from http://www.cdolive.net/download/aclasp.zip you then need to register it with regsvr32

Cheers
Glen

Anonymous said...

When I run convperm.vbs, it prompts me to click OK after each folder. How do I turn this off?

Lasse Franck said...

you should run the script from a command prompt by typing:
CD x:\folder\where\script\is
(where x is the drive where the script is)
CSCRIPT scriptname.vbs

Normally, if you double click a .vbs file, it will launch wscript, and with wscript all echo's are generating message boxes.

Lasse Franck said...

...and btw I like the script though I will need to modify it heavily.
I need its structure to migrate permissions from one Exchange server to another - in two different domains where there never can be a trust between (so i.e. goodbye sIDHistory). Therefore I need to map old and new SID's in a database, look up all old permissions and probably dump them to a database, and then generate script that will set permissions on the newly created and imported mailboxes.

Alex said...

Hi Glen,
Great script, this is exactly what I am looking for to audit what Outlook permissions have been set. I know this was posted a few years back, but wonder if you can advise on a problem I have.
The script works great but only for around 30 mailboxes, following that I can't logon to any mailboxes at all.
The error VB returns is 'The information store could not be opened. [MAPI 1.0 -
[MAPI_E_LOGON_FAILED(80040111)]]'.

I couldn't find anything not being cleaned down by the script but tried modifying it to call a single mailbox and quit (I called this vbscript from within a while loop passing each mailbox name in turn), same result so sure it's not a code problem. Is there some security thing, or mapi limit that I am hitting?


Appreciate any help or pointers,
Cheers,
Alex

Glen said...

Hi Alex,

I've had a few people report similar issues its seems to be with the way ACL.dll and CDO 1.2 work that the connection to Exchange isn't properly shut down when the sub routine has finished processing the mailbox. What some people have done is split this into two scripts so the first script does the ADSI query of the Active Directory and then executes the second script which contains all the mapi/CDO code. Using this method does appear to solve the connection issue for some people.The other option is to use RDO which doesn't seem to suffer from the same connection bugs as CDO does.

Are you using 2007 ? I have a better method using EWS in 2007 to do this.

Cheers
Glen

Anonymous said...

I am trying to use this and the mailbox is on Exchange 2007. Can you please post your "better method" you mentioned in the last post.

Glen said...

Me and my big mouth the better method to use in Exchange 2007 is Exchange Web Services has the ability to read permission on mailbox folder. I haven't yet created anything that used this method yet. Maybe in the future.

Cheers
Glen

Anonymous said...

Thanks for the reply.

also, I know that you have already posted a 'workaround' for [MAPI 1.0 -
[MAPI_E_LOGON_FAILED(80040111)]].. but is there any other solution? I am able to change the LDAP query and get this to go against 1 or mailboxes but as soon as i use somthing like (msexchhomservername=*server1*) I will get 8040111 errors immiediatly. Any ideas?

sierrasnow said...

man if I could get this permission script run, Glen'd be my answer to job security. Need help on the split. Can you post a split script as you mention doing to get around the CDO limitation? I'm able to run the revperm1.vbs, but like others getting the mapi failure after about 35 mailboxes. And I'm at a loss on how and where to do the split and call one from the other.

I'll ya some send sourdough bread from SF as thanks!

Glen said...

Have a look at http://msgdev.mvps.org/exdevblog/dumpperms.zip this is a two script approach it works a little different in that it dumps the permissions out to one large text file then agregates this text

Cheers
Glen

Sean McGilvray said...

How do you output this to a file. Thank you,

Sean McGilvray

Glen said...

see http://msgdev.mvps.org/exdevblog/dumpperms.zip

Cheers
Glen