Tuesday, September 26, 2006

Enumerating the devices registered via ActiveSync on all mailboxes on a server via a script

There are a bunch of tools around to get information about devices that are in use via Active sync such as the ActiveSync administration tool and also this method of using the logs that was blogged about on the Exchange Team blog. But if your after another method of enumerating all the devices in all mailboxes on a server here's something that might help out.

The script works by using WebDAV and the Exadmin virtual directory (which means you won’t need full rights to everybody’s mailbox to run this script just delegated Exchange administration rights). To work out what path to use the script first queries for the default recipient policy in a domain and retrieves the default SMTP address within that policy. This is used to build the path for the Exadmin virtual directory. Another ADSI query then queries for all mailboxes on a server and then feeds the name of these mailboxes into a function those queries for the ActiveSync folder within each mailbox. The script first searches the non_ipm_subtree of a mailbox to see if the Microsoft-Server-ActiveSync exists on that mailbox. If the folder is found another query is then done to enumerate the folders under that Microsoft-Server-ActiveSync folder. This will usually be PocketPC or Smartphone depending on the type of devices being used a search of any folders that are found is done which should then find a folder for each device that has been registered. Finally a search of the device folder is done to show the Sync files that are listed under that folder and what dates they where last modified. All the information that the searches recovered is then put together into a HTML table which is then written to a file located in the c:\temp folder called asreport.htm

To run the script it takes one command line parameter which is the name of the server you want to run it against (eg cscript showasyncv2.vbs servername). By default the script isn’t using SSL which may mean you need to adjust the following line if you are using SSL on the ExAdmin Directory

Eg change

falias = "http://" & servername & "/exadmin/admin/" & dpDefaultpolicy & "/mbx/"

to

falias = "https://" & servername & "/exadmin/admin/" & dpDefaultpolicy & "/mbx/"

I’ve put a downloadable copy of the script here the code itself looks like


servername = wscript.arguments(0)
set shell = createobject("wscript.shell")
strValueName =
"HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias"
minTimeOffset = shell.regread(strValueName)
toffset = datediff("h",DateAdd("n", minTimeOffset, now()),now())

report = "<table border=""1"" width=""100%"">" & vbcrlf
report = report & " <tr>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">DisplayName</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">Email Address</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">Device Type</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">Device ID</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">FolderSync</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">ContactSync</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">CalendarSync</font></b></td>" & vbcrlf
report = report & "<td align=""center"" bgcolor=""#000080""><b><font
color=""#FFFFFF"">autdstate.xml</font></b></td>" & vbcrlf
report = report & "</tr>" & vbcrlf
set req = createobject("microsoft.xmlhttp")
set com = createobject("ADODB.Command")
set conn = createobject("ADODB.Connection")
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strNameingContext = iAdRootDSE.Get("configurationNamingContext")
strDefaultNamingContext = iAdRootDSE.Get("defaultNamingContext")
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
polQuery = "<LDAP://" & strNameingContext &
">;(&(objectCategory=msExchRecipientPolicy)(cn=Default
Policy));distinguishedName,gatewayProxy;subtree"
svcQuery = "<LDAP://" & strNameingContext &
">;(&(objectCategory=msExchExchangeServer)(cn=" & Servername &
"));cn,name,legacyExchangeDN;subtree"
Com.ActiveConnection = Conn
Com.CommandText = polQuery
Set plRs = Com.Execute
while not plRs.eof
for each adrobj in plrs.fields("gatewayProxy").value
if instr(adrobj,"SMTP:") then dpDefaultpolicy = replace(adrobj,"SMTP:@","")
next
plrs.movenext
wend
wscript.echo dpDefaultpolicy
Com.CommandText = svcQuery
Set Rs = Com.Execute
while not rs.eof
GALQueryFilter = "(&(&(&(& (mailnickname=*)(!msExchHideFromAddressLists=TRUE)(|
(&(objectCategory=person)(objectClass=user)(msExchHomeServerName=" &
rs.fields("legacyExchangeDN") & ")) )))))"
strQuery = "<LDAP://" & strDefaultNamingContext & ">;" & GALQueryFilter &
";displayname,mail,distinguishedName,mailnickname,proxyaddresses;subtree"
com.Properties("Page Size") = 100
Com.CommandText = strQuery
Set Rs1 = Com.Execute
while not Rs1.eof
falias = "http://" & servername & "/exadmin/admin/" & dpDefaultpolicy & "/mbx/"
for each paddress in rs1.fields("proxyaddresses").value
if instr(paddress,"SMTP:") then falias = falias & replace(paddress,"SMTP:","") &
"/non_ipm_subtree"
next
wscript.echo falias
SerachAsync(falias)
rs1.movenext
wend
rs.movenext
wend
rs.close
set conn = nothing
set com = nothing
report = report & "</table>" & vbcrlf
Set fso = CreateObject("Scripting.FileSystemObject")
set wfile = fso.opentextfile("c:\temp\asreport.htm",2,true)
wfile.write report
wfile.close
set wfile = nothing
set fso = nothing

wscript.echo "Done"

sub SerachAsync(furl)
strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"" >"
strQuery = strQuery & "<D:sql>SELECT
""http://schemas.microsoft.com/mapi/proptag/x3001001E"""
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & furl & """') Where ""DAV:ishidden"" = False AND
""DAV:isfolder"" = True AND "
strQuery = strQuery & """http://schemas.microsoft.com/mapi/proptag/x3001001E"" =
'Microsoft-Server-ActiveSync'</D:sql></D:searchrequest>"
req.open "SEARCH", furl, false
req.setrequestheader "Content-Type", "text/xml"
req.setRequestHeader "Translate","f"
on error resume next
req.send strQuery
if err.number <> 0 then wscript.echo err.description
on error goto 0
If req.status >= 500 Then
ElseIf req.status = 207 Then
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("d:x3001001E")
if oNodeList.length <> 0 then
wscript.echo "Active-Sync Folder Exists"
displayAyncSub(furl & "/Microsoft-Server-ActiveSync")
else
wscript.echo "No Active-Sync Folder"
end if
Else
End If

end sub

sub displayAyncSub(furl)

strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"" >"
strQuery = strQuery & "<D:sql>SELECT
""http://schemas.microsoft.com/mapi/proptag/x3001001E"""
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & furl & """') Where ""DAV:ishidden"" = False AND
""DAV:isfolder"" = True</D:sql></D:searchrequest>"
req.open "SEARCH", furl, false
req.setrequestheader "Content-Type", "text/xml"
req.setRequestHeader "Translate","f"
on error resume next
req.send strQuery
if err.number <> 0 then wscript.echo err.description
on error goto 0
If req.status >= 500 Then
ElseIf req.status = 207 Then
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("d:x3001001E")
for each node in oNodeList
call displaydeviceSub(furl & "/" & node.text,node.text)
next
Else
End If
end sub

sub displaydeviceSub(furl,fname)

strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"" >"
strQuery = strQuery & "<D:sql>SELECT
""http://schemas.microsoft.com/mapi/proptag/x3001001E"""
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & furl & """') Where ""DAV:ishidden"" = False AND
""DAV:isfolder"" = True</D:sql></D:searchrequest>"
req.open "SEARCH", furl, false
req.setrequestheader "Content-Type", "text/xml"
req.setRequestHeader "Translate","f"
on error resume next
req.send strQuery
if err.number <> 0 then wscript.echo err.description
on error goto 0
If req.status >= 500 Then
ElseIf req.status = 207 Then
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("d:x3001001E")
for each node in oNodeList
report = report & "<tr>" & vbcrlf
report = report & "<td align=""center"">" & rs1.fields("displayname") &
"&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & rs1.fields("mail") & "&nbsp;</td>" &
vbcrlf
report = report & "<td align=""center"">" & fname & "&nbsp;</td>" & vbcrlf
report = report & "<td align=""center"">" & node.text & "&nbsp;</td>" & vbcrlf
report = report & finditems(furl & "/" & node.text)
report = report & "</tr>" & vbcrlf
next
Else
End If
end sub

function finditems(furl)

hascalsyc = 0
hasfolsyc = 0
hasconsyc = 0
hasautd = 0
rback = ""
strQuery = "<?xml version=""1.0""?><D:searchrequest xmlns:D = ""DAV:"" >"
strQuery = strQuery & "<D:sql>SELECT ""DAV:displayname"",
""DAV:getlastmodified"""
strQuery = strQuery & " FROM scope('shallow traversal of """
strQuery = strQuery & furl & """') Where ""DAV:isfolder"" =
False</D:sql></D:searchrequest>"
req.open "SEARCH", furl, false
req.setrequestheader "Content-Type", "text/xml"
req.setRequestHeader "Translate","f"
on error resume next
req.send strQuery
if err.number <> 0 then wscript.echo err.description
on error goto 0
rem wscript.echo req.responsetext
If req.status >= 500 Then
ElseIf req.status = 207 Then
set oResponseDoc = req.responseXML
set oNodeList = oResponseDoc.getElementsByTagName("a:displayname")
set oNodemodlist = oResponseDoc.getElementsByTagName("a:getlastmodified")
wscript.echo oNodeList.length
for i = 1 to oNodeList.length
set onode = oNodeList.nextNode
set onode1 = oNodemodlist.nextNode
select case lcase(onode.text)
case "calendarsyncfile" hascalsyc = 1
hascalsycval = DateAdd("h",toffset,(left(replace(replace(onode1.text,"T","
"),"Z",""),19)))
case "foldersyncfile" hasfolsyc = 1
hasfolsycval = DateAdd("h",toffset,(left(replace(replace(onode1.text,"T","
"),"Z",""),19)))
case "contactssyncfile" hasconsyc = 1
hasconsycval = DateAdd("h",toffset,(left(replace(replace(onode1.text,"T","
"),"Z",""),19)))
case "autdstate.xml" hasautd = 1
hasautdval = DateAdd("h",toffset,(left(replace(replace(onode1.text,"T","
"),"Z",""),19)))
end select
next
Else
End If
wscript.echo hasfolsyc
if hasfolsyc = 1 then
rback = rback & "<td align=""center"">" & hasfolsycval & "&nbsp;</td>" & vbcrlf
else
rback = rback & "<td align=""center"">No&nbsp;</td>" & vbcrlf
end if
if hasconsyc = 1 then
rback = rback & "<td align=""center"">" & hasconsycval & "&nbsp;</td>" & vbcrlf
else
rback = rback & "<td align=""center"">No&nbsp;</td>" & vbcrlf
end if
if hascalsyc <> 0 then
rback = rback & "<td align=""center"">" & hascalsycval & "&nbsp;</td>" & vbcrlf
else
rback = rback & "<td align=""center"">No&nbsp;</td>" & vbcrlf
end if
if hasautd <> 0 then
rback = rback & "<td align=""center"">" & hasautdval & "&nbsp;</td>" & vbcrlf
else
rback = rback & "<td align=""center"">No&nbsp;</td>" & vbcrlf
end if
finditems = rback
end function

19 comments:

Anonymous said...

Will this work if you have a front-end back-end architecture?

Glen said...

I've never tried this its using the admin virtual root some maybe not I would say it would be just better target the backend servers directly instead.

Cheers
Glen

david_mallett@msn.com said...

This works great on a couple of my BE servers, but fails on the third with the error "C:\Download\showasyncv3.vbs(48, 3) Microsoft VBScript runtime error: Object not a collection". Could this be an invalid character in an alias or non-standard e-mail address? How can I identify which is the offending article?

Regards,

David.

Glen said...

The problem is the script doesn't do any null checking. So the account its failing on has no proxyaddress for some reason. I've updated the download for this post with a version that doesn't null checking so this should fix that issue.

Cheers
Glen

Danlor said...

I get empty results everyt ime I run this... just a header, no details... There must be another step in here.

Anonymous said...

I know there has been another method by which to script this against multiple servers, however since I'm no VBScript expert, I use batch scripting and DOS.

First, change one line in Glen's script:

set wfile = fso.opentextfile("c:\temp\asreport.htm",2,true)

To:
set wfile = fso.opentextfile("c:\temp\" & servername & ".htm",2,true)

This will allow the exported name to reflect the name of the server. You can now export your Exchange server names and create a batch file for calling 'cscript vbsscript servername' - I personally use the CONCATENATE function in Excel to create my batch script.

Anyhow, this will export each server into it's own file. From there, I renamed all of the .htm files to .txt (ren *.htm *.txt) and merged all of them together (copy *.txt easreport.txt) and renamed that back to easreport.htm and opened in Excel (where I removed all of the excess header lines).

A lot of manual work involved, but for someone not savvy with scripting, it's an easy workaround.

Stewart said...

Worked perfectly and the end report was superb, thanks Glen

Anonymous said...

If the folder is empty after running, uncheck Require SSL for the ExAdmin virtual directory in IIS. Also see kb916960

TheyCallMePete said...

Hey Glen!

Awesome code. Have started using it to identify Winmo users in my environment. I do have a question regarding the date fields. I have certain records reporting FolderSync dates > Autdstate.xml and in other cases its the other way around.

Can you explain the logic with the dates that you are pulling?

Glen said...

Its bascially pulling information from this item im not sure i understand your question ?

Cheers
Glen

Glenn said...

mine has exports just the header line too? Suggestions?

Thanks,
Glenn

Anonymous said...

I am getting header of the report in the file, it does not give any result, any suggestion on this?

Glen said...

You could try replacing that line with

wfile.writeline(report)

Cheers
Glen

Drew said...

If you get an access denied error and no results. Go into IE, go to tools, Internet Options. From there go to the Security Tab, choose trusted sites, the click on Sites. Add the name or names of your Exchange servers. Uncheck require server verification. When they are all added click close and then ok. This did the trick for me.

Ann said...

Glen,

This is a great script, and we've been using it since 2009. I've noticed that it doesn't pick up old ActiveSync users if we've hidden their account from the Exchange address list. Would you happen to know a tweak that would make the script include those users as well? Thanks.

Ann

Glen Scales said...

You need to modify the following line

GALQueryFilter = "(&(&(&(& (mailnickname=*)(!msExchHideFromAddressLists=TRUE)(| (&(objectCategory=person)(objectClass=user)(msExchHomeServerName=" & rs.fields("legacyExchangeDN") & ")) )))))"

What should work is just using

GALQueryFilter = "(&(&(&(& (mailnickname=*)(msExchHideFromAddressLists=*)(| (&(objectCategory=person)(objectClass=user)(msExchHomeServerName=" & rs.fields("legacyExchangeDN") & ")) )))))"

Otherwise you would need to remove msExchHideFromAddressLists altogether from the command

Ann said...

The first one created a list of ONLY the hidden users, but the second included ALL users, and is just what I needed. Thank you!

Anonymous said...

Thank you Glen so much for the help and sharing the script.

Anonymous said...

Same here! Any ideas?