Wednesday, April 26, 2006

Exchange Where is scripted Instant Message response Bot using LCS and Communicator Web Access (Ajax)

In my pervious post I showed how to create a simple LCS scripted response bot that did something a little bit boring such as tell you what day it is. Presence technology is something that’s changing and growing all the time the Outlook integration of the Office 2005 communicator is one example. The script is partially inspired by this but I decided to see if I could push the limits when it comes to privacy and the presence type information that is available to system administrators that you may not think normally about using.

So what does this bot do ? The bot allows you to ask it the question where is fred@domain.com. It then takes this information and does the following

First it queries the calendar to see if they are currently in an appointment and creates a response that shows details of the appointment a person may currently be attending and the location that appointment is and what time it started and finished. It also sends a full list of their calendar appointments over the next 8 hours what time they start and finish and where they are located.

After this it does a query of the users Inbox and looks for any email that the asker has sent to this user over the past 3 days and see’s if any of these emails are unread. If any of these emails are marked unread in the users inbox it creates a list of which email they are and responds to the requestor.

After this it does a query of the users Sent items folder and looks at the last time the user sent an email. It doesn’t look at the details of the actual message it just returns back to the asker the time the last email was sent. (This is to give the asker an impression of when the last time this user was actively sending email)

The last query it does if for Exchange 2003 server only what it does is queries the Exchange_logon class to see if this user is currently logged on to the Exchange server anywhere. So basically it tells the users if this person is currently logged onto Outlook from a desktop or whether they are logged on maybe via Outlook web Access.

An Example of the Output it returns looks like.


The script

For detail on the response bot itself see my other post the Exchange whereis function does a number of queries to gather the information I described above. The first thing that happens however is that the email address of the user that is the subject of the who is first is queried in Active Directory to work out what exchange server there mailbox is located on. Another ADSI query is done to find the primary email address from the asker's sip address which is necessary for the unread email query
All the mailbox queries are performed over WebDAV I’ve created two versions of this script one is for if your running FBA on the Exchange server and the other is a NTLM version. For the Logon status query I’ve used a version of one of my previous scripts that uses the ADO shaping data provider to shape ADSI and Logon Data from the Exchange_Logon class to show the logon status of the user. Because this script actually logs onto a users mailbox the script must filter out any logons that I picks up that the script has made. Because of the number of queries performed and depending on the number of items in a mailbox it can take a little time for all the queries to complete. To show a recognition that it has excepted the query it responds back to the user straight away that its checking and to wait a minute.

To run this script can be a little tricky basically you need to configure the script with credentials to use to logon to LCS and logon to Exchange. I’ve used the same credentials for both LCS and Exchange meaning that the user who your are using in the script must have full rights to all the Exchange mailboxes that might be queried by this script. See this for more details on how to achieve this.

To run this script you first need to configure the following four variables at the top of the script

SipURI = "user@domain.com"
Servername = "servername.domain.com"
userName = "domain\username"
Password = "password"

One note if your running the FBA version and you have a fount end server you might want to hardcode the name of the fount end server in the script. This can be important if you have used an alias in your SSL certificate which would normally cause a SSL certificate popup which will cause the script to fail. To hardcode the server name just un-rem the following line of code and configure the variable.

'exservername = "frontend.domain.com" ' If you have a frount end server you can hardcode servername here

After you’ve configured the script all you need to do is start it from the command shell like.

Cscript exwhoislcsrespbot.vbs

The script is very verbose and should return a lot of information back to the console while it’s running.

I’ve put a downloadable copy of the script here The script itself is a little large to post in verbatim

Simple scripted Instant Message response Bot using LCS and Communicator Web Access (Ajax)

Continuing on from the other week I’ve been playing around further with the new Ajax interface for LCS communicator web access. Response bots are not only a lot of fun to play around with but can be a practical way of getting a little bit more out of an Instant messaging system. Like the send message script this script works by using the browser object to post and get data to the LCS Communicator Web Access server. The script is a little crude in some aspects for some reason the XML returned from LCS does seem to parse well so I’ve used a cruder text parse method to interpret the server responses. I have really only tested this in one environment so this logic may fall down in other environments where there are some differing variables.

The first section of the script logs on to the web access server using NTLM over https and most importantly retrieves the latestupdate id which must be posted with every request to the server. The reasons why you need to do this and how the timeouts work is all explained in the AJAX SDK which you can download. Do a search on “latestupdate” if you want more information on this. So after it retrieves the latest update it basically setups up a never ending loop where it continues to poll the server until the script is broken or a error is encounter.

When someone sends a message to the scripts LCS account the next time an update is polled via the script the message itself gets parsed from the message attribute which is part of the response. For this basic script it looks to see if the message is “what day is it”. If there is a match then it uses a few VB functions to get the weekday name and the send a message back to the IM session that initiated the question. To target the right IM session for the response the IM session ID must be parsed out of the response string and used in the LcwSendMessageRequest method. Like the send message script the cookie that is retrieved after logon must be sent with every request.

To run this script you first need to configure the following four variables at the top of the script

SipURI = "user@domain.com"
Servername = "servername.domain.com"
userName = "domain\username"
Password = "password"

After you’ve configured the script all you need to do is start it from the command shell like

Cscript lcsrespbot.vbs

The script is very verbose and should return a lot of information back to the console while its running.

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

SipURI = "user@domain.com"
Servername = "servername.domain.com"
userName = "domain\username"
Password = "password"

set req = createobject("microsoft.xmlhttp")
req.Open "GET", "https://" & Servername & "/iwa/logon.html?uri=" & SipURI &
"&signinas=1&language=en&epid=", False, Username, Password
req.send
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for c = lbound(reqhedrarry) to ubound(reqhedrarry)
if instr(lcase(reqhedrarry(c)),"set-cookie:") then reqsessionID =
right(reqhedrarry(c),len(reqhedrarry(c))-12)
next
wscript.echo reqsessionID
chk = left(reqsessionID,36)
updatestr = "https://" & Servername & "/cwa/AsyncDataChannel.ashx?AckID=0&Ck=" &
chk
req.Open "GET", updatestr, False, Username, Password
req.setRequestHeader "Cookie:", reqsessionID
req.send
latupdate =
mid(req.responsetext,instr(req.responsetext,"latestUpdate=")+14,instr(instr(req.responsetext,"latestUpdate=")+14,req.responsetext,chr(34))-(instr(req.responsetext,"latestUpdate=")+14))

while i <> 1
updatestr = "https://" & Servername & "/cwa/AsyncDataChannel.ashx?AckID=" &
latupdate & "&Ck=" & chk
req.Open "GET", updatestr, False, Username, Password
req.setRequestHeader "Cookie:", reqsessionID
req.send
wscript.echo req.status
if instr(req.responsetext,"div id=""exception""") then i = 1
oldlat = latupdate
latupdate =
mid(req.responsetext,instr(req.responsetext,"latestUpdate=")+14,instr(instr(req.responsetext,"latestUpdate=")+14,req.responsetext,chr(34))-(instr(req.responsetext,"latestUpdate=")+14))
wscript.echo req.responsetext
wscript.echo latupdate
if latupdate = "yTimeout" then latupdate = oldlat
if instr(req.responsetext,"message=""") then
Imid =
mid(req.responsetext,instr(req.responsetext,"imId=""")+6,instr(instr(req.responsetext,"imId=""")+6,req.responsetext,chr(34))-(instr(req.responsetext,"imId=""")+6))
message =
mid(req.responsetext,instr(req.responsetext,"message=""")+9,instr(instr(req.responsetext,"message=""")+9,req.responsetext,chr(34))-(instr(req.responsetext,"message=""")+9))
wscript.echo "************************Message
Recieved****************************"
wscript.echo message
wscript.echo "************************Message
Ends********************************"
wscript.echo Imid
exist = 0
if instr(1,lcase(message),"what day is it") then
SendMessage("Today is " & weekdayname(weekday(now())))
else
SendMessage("Im a little simple and can only answer the question what day is
it")
end if
message = ""
Invite = ""
end if
wend


function SendMessage(message)

' ---Send Message---
Sendmsgcmd = "https://" & Servername & "/cwa/MainCommandHandler.ashx?Ck=" & chk
Messagestr = "cmdPkg=1,LcwAcceptImRequest," & Imid
req.open "POST", Sendmsgcmd, False, Username ,password
req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
req.setRequestHeader "Cookie:", reqsessionID
req.send Messagestr
wscript.echo req.status
Sendmsgcmd = "https://" & Servername & "/cwa/MainCommandHandler.ashx?Ck=" & chk
Messagestr = "cmdPkg=2,LcwSendMessageRequest," & Imid & "," & Message &
",X-MMS-IM-Format: FN=Arial%253B EF=%253B CO=000000%253B CS=1%253B PF=00"
req.open "POST", Sendmsgcmd, False, Username ,password
req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
req.setRequestHeader "Cookie:", reqsessionID
req.send Messagestr
wscript.echo req.status

end function

Wednesday, April 19, 2006

Removing Disabled Users from Distribution lists via a script

One thing that I seem to do with monotonous regularity is disable user accounts as people churn though the companies that I work for. Usually once an account is disabled the chances of it being re-enabled while possible are always slim. One of the things that mostly gets forgotten when disabling an account is to also remove it from any distribution lists that account maybe in. Now with the new hot-fix this is not as much of a problem because a disabled mailbox can now be configured to still receive email. However it is still desirable for these disabled accounts not to be receiving email from distribution lists. Instead of going though each disabled user to work out if it’s a member of any distribution list and then remove it I decided to create a script that would fist let me list all the disable users that are in a distribution lists and also have an option so it will remove these users from that group.

For the script itself I’ve used ADSI and the ADO data shaping provider again. As in the past the ADO data shaping provider is great if you want to create hierarchal datasets which is perfect for what I want to do here. Basically the script first goes though and creates a parent record set with the names of all the mail enabled groups in Active directory. A second ADSI query is then performed which does a bitwise filter to retrieve a recordset of all the disabled users with a mailbox. A child recordset is then created with an entry for each group a user is in. The two recordsets are then related on the Group’s DistinguishedName which then gives me a retrievable hierarchy such as

GroupName-|
|-UserName

The display section of the code then just looks at groups with at least 1 disabled user as a member. Some extra code is added to skip the system mailboxes and some ADSI code also is used to remove the user from the group if the script is being run in remove mode.

To run the script in display mode just run the script without any command-line parameters eg

Cscript.disabusers.vbs

To run the script in remove mode meaning that it will remove the disabled accounts from the group memberships of any mail-enabled groups (this script does not different between group types) run the script with remove as the command-line parameter eg

Cscript disabusers.vbs remove

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


if wscript.arguments.length = 0 then
wscript.echo "Display Mode"
else
if lcase(wscript.arguments(0)) = "remove" then
mode = "remove"
wscript.echo "Remove Mode"
else
wscript.echo "Display Mode"
end if
end if
wscript.echo
set conn = createobject("ADODB.Connection")
set com = createobject("ADODB.Command")
set conn1 = createobject("ADODB.Connection")
strConnString = "Data Provider=NONE; Provider=MSDataShape"
conn1.Open strConnString
Set iAdRootDSE = GetObject("LDAP://RootDSE")
strNameingContext = iAdRootDSE.Get("configurationNamingContext")
strDefaultNamingContext = iAdRootDSE.Get("defaultNamingContext")
set objParentRS = createobject("adodb.recordset")
set objChildRS = createobject("adodb.recordset")
strSQL = "SHAPE APPEND" & _
" NEW adVarChar(255) AS GRPDisplayName, " & _
" NEW adVarChar(255) AS GRPDN, " & _
" ((SHAPE APPEND " & _
" NEW adVarChar(255) AS USDisplayName, " & _
" NEW adVarChar(255) AS USDN, " & _
" NEW adVarChar(255) AS USGRPDisplayName, " & _
" NEW adVarChar(255) AS USGRPDN " & _
")" & _
" RELATE GRPDN TO USGRPDN) AS rsGRPUS "
objParentRS.LockType = 3
objParentRS.Open strSQL, conn1
Conn.Provider = "ADsDSOObject"
Conn.Open "ADs Provider"
GALQueryFilter = "(&(mailnickname=*)(|(objectCategory=group)))"
strQuery = "<LDAP://" & strDefaultNamingContext & ">;" & GALQueryFilter &
";distinguishedName,displayname,legacyExchangeDN,homemdb;subtree"
Com.ActiveConnection = Conn
Com.CommandText = strQuery
Set Rs = Com.Execute
while not rs.eof
objParentRS.addnew
objParentRS("GRPDisplayName") = rs.fields("displayname")
objParentRS("GRPDN") = rs.fields("distinguishedName")
objParentRS.update
rs.movenext
wend
GALQueryFilter =
"(&(&(mailnickname=*)(objectCategory=person)(userAccountControl:1.2.840.113556.1.4.803:=2)))"
strQuery = "<LDAP://" & strDefaultNamingContext & ">;" & GALQueryFilter &
";distinguishedName,displayname,legacyExchangeDN,homemdb;subtree"
Com.ActiveConnection = Conn
Com.CommandText = strQuery
Set Rs1 = Com.Execute
Set objChildRS = objParentRS("rsGRPUS").Value
while not rs1.eof
if instr(rs1.fields("displayname"),"SystemMailbox{") = 0 then
set objuser = getobject("LDAP://" & rs1.fields("distinguishedName"))
For each objgroup in objuser.groups
objChildRS.addnew
objChildRS("USDisplayName") = rs1.fields("displayname")
objChildRS("USDN") = rs1.fields("distinguishedName")
objChildRS("USGRPDisplayName") = objgroup.name
objChildRS("USGRPDN") = objgroup.distinguishedName
objChildRS.update
next
end if
rs1.movenext
wend
objParentRS.MoveFirst
wscript.echo "GroupName,Disabled User's Name"
wscript.echo
Do While Not objParentRS.EOF
Set objChildRS = objParentRS("rsGRPUS").Value
if objChildRS.recordCount <> 0 then
Do While Not objChildRS.EOF
Wscript.echo objParentRS.fields("GRPDisplayName") & "," &
objChildRS.fields("USDisplayName")
if mode = "remove" then
set objgroup = getobject("LDAP://" & objChildRS.fields("USGRPDN"))
Set objUser = getobject("LDAP://" & objChildRS.fields("USDN"))
objGroup.Remove(objUser.AdsPath)
objgroup.setinfo
wscript.echo "User-Removed"
end if
objChildRS.MoveNext
loop
end if
objParentRS.MoveNext
Loop

Thursday, April 06, 2006

Sending an Instant Message via Script using LCS and Communicator Web Access (Ajax)

Its been some time since I had a chance to play around with any LCS code I saw the release of the AJAX SDK so I decided to have a bit of read and see if I could put together a few bits of code. If you’re comfortable writing WebDAV code you’ll feel at home with Ajax its just a matter of posting and getting data via a Http control. Starting real easy I thought I’d try a simple VBS script to send a message from the command line.

The script itself is fairly basic it uses NTLM authentication to first logon to a LCS server using the Logon URL. After it logs on successfully you need to get the cookie for the session which is used when calling any further functions. To send a message the StartIm method is used which basically tries to start an IMSession along with sending a message. The last section of code then calls the logoff URL. Before you run the script you need to configure the hardcoded variables in the config section basically you need to put in the name of the LCS CWA server
Servername = "servername.domain.com"

The Sip address of the user that is going to send the message
LogonSipAddress = user@domain.com
The username and password of a user that has rights to logon on to the sending SIP Adreess
Username = "domain\username"
password = "password"

The script is designed to be run from the command-line with 2 command line parameters the first is the user’s address you want to send to and the second is the message you want to send so to run the script use something like

cscript lcsSendmsg.vbs user@domain.com. "Hello world how are you doing”

I’ve put a download of the script here the script itself look like

' ----Config----
Toaddress = wscript.arguments(0)
Message = wscript.arguments(1)
Servername = "servername.domain.com"
LogonSipAddress = "user@domain.com"
Username = "domain\username"
password = "password"
' ----End Config----
set req = createobject("microsoft.xmlhttp")
' ----Logon
Logonstr = "https://" & Servername & "/iwa/logon.html?uri=" & LogonSipAddress & "&signinas=1&language=en&epid="
req.Open "GET",Logonstr , False, Username ,password
req.send
wscript.echo req.status
reqhedrarry = split(req.GetAllResponseHeaders(), vbCrLf,-1,1)
for c = lbound(reqhedrarry) to ubound(reqhedrarry)
if instr(lcase(reqhedrarry(c)),"set-cookie:") then reqsessionID = right(reqhedrarry(c),len(reqhedrarry(c))-12)
next
chk = left(reqsessionID,36)
' ---Send Message---
Sendmsgcmd = "https://" & Servername & "/cwa/MainCommandHandler.ashx?Ck=" & chk
Messagestr = "cmdPkg=1,LcwStartImRequest,sip:" & ToAddress & "," & Message & ",X-MMS-IM-Format: FN=Arial%253B EF=%253B CO=000000%253B CS=1%253B PF=00"
req.open "POST", Sendmsgcmd, False, Username ,password
req.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
req.setRequestHeader "Cookie:", reqsessionID
req.send Messagestr
wscript.echo req.status
' ---Logoff---
logoffstr = "https://" & Servername &"/cwa/SignoutHandler.ashx?Ck=" & chk
req.Open "GET",logoffstr , False, Username ,password
req.setRequestHeader "Cookie:", reqsessionID
req.send
wscript.echo req.status