Subscriber Information Dump
Similar to the call handler information dump in the previous section, this example will walk through the process of dumping out the basic information about any subscriber in the Unity server you're connected to. Of course a healthy portion of the information about a subscriber is stored in their primary call handler and you can use the previous example to mine that information if you wish. You just have to keep in mind that the alternate contact rule is always enabled for primary call handlers and you're good to go. We'll be sticking to subscriber specific data here.
As with the call handler information dump I'm not going to go into too much detail here as this is mostly a process of knowing what tables the subscriber information you're interested in is stored and what the values mean. We'll cover the high points in here but you'll need to make sure you've familiarized yourself with the object model and spend some time wandering around in CUDLE. You can get a full code listing for the Subscriber Information Dump on the code samples page of www.CiscoUnityTools.com. Here's some of the highlights for subscriber related data.
You can use the same routine discussed in the previous section for constructing action string for the exit destination that will take effect when the subscriber uses * to exit out of their subscriber conversation. By default this goes to the opening greeting but as of 4.0(1) it can be customzied on a per user basis.
To fetch the primary and alternate extensions for this subscriber you need to make a simple query against the DTMFAccessID table:
"SELECT * FROM vw_DTMFAccessID WHERE ParentObjectID=SubscriberObjectID"
This will get both primary and alternate Ids. If you want to filter against just the primary or just the alternates you can add a WHERE clause condition against the IsPrimaryID column being '0' or not.
To get the MWI devices for a subscriber is a very similar affair:
SELECT * FROM vw_NotificationMWI WHERE ParentObjectID=SubscriberObjectID
A subscriber can have up to 10 MWI devices defined for their mailbox.
To get your notification devices and rules is a little trickier since the devices and rules are stored in separate tables and don't reference one another explicitly. They are associated only by their alias matching. As we discussed in the data object model chapter the device table contains the notification device information itself such as the pager number to deliver to or the SMTP address for the text pager and the like. The rules table contains the schedule and the triggering critieria that will activate its device counterpart.
To get information about, say, the Home Phone delivery for a subscriber you'd need to get information from both tables using a query like this:
SELECT vw_NotificationRule.*, vw_NotificationDevice.* FROM vw_NotificationRule INNER JOIN vw_NotificationDevice ON vw_NotificationRule.DlvRuleDevice=vw_NotificationDevice.Alias WHERE vw_NotificationDevice.Alias='Home Phone Rule'
Most of the information associated with devices is pretty straight forward but I'll tell you now messing with schedules is going to hurt your hair. CUDLE includes the logic for how the schedules for notification rules are stored which is a scheme that lives still as a result of them being crammed into Exchange 5.5's directory back in the 2.x days. I'm not a big fan of the format, let's just say that much.
Remember that the HomeServer column in the subscriber table is the server name of the mailstore this subscriber's mailbox is homed on. A lot of folks confuse this and the HomeServer on the location object this subscriber is associated with which is the server name of the Unity server they are associated with.
The language value found on the Subscriber table indicates which language they hear when they call into the system to check messages. The language callers hear when they get that subscriber's greeting is set on the subscriber's primary call handler. The same language code handling discussed in the call handler dump example applies here.
As of Unity 4.0(1) each subscriber can dictate the order in which messages are presented to them in the subscriber message retrieval conversation for both the new message stack and the old (read) message stack. This is a feature called FlexStack and the settings for it are stored in the StackOrder column of the subscriber table. By default this column is NULL which means the default stack order is used. Here's a chunk of code form the Subscriber Information Dump example that shows how to parse this string out for your users:
AddText "Active Stack message order for new messages"
If IsNull(rsSubscribers("StackOrder")) Then
'The default for new the new message stack is FIFO
AddText " Stack Order= FIFO"
' The default order for new the new message stack items 1 through 7 in the order laid out in this Choose function (which is just a short hand case statement in VB by the way). If the user has specified a specific order the StackOrder column will not be NULL and we'll parse it out in the Else statement next.
For iCounter = 1 To 7
AddText " " + Choose(iCounter, "Urgent Voice Mail", "Voice Mail", "Urgent Fax", "Fax", "Urgent Email", "Email", "Receipts")
Next iCounter
Else
'The user has customized the stack order, we need to pull out the string for new messages. The string looks something like this "NN:F1532467:0:L1234567". In this case we want the 7 characters after the "N:" which stands for new messages. Check the description of this field in CUDLE for more details. The string is always the same length if it's not null so we can simply do this with a MID and Choose function here in just a few lines of code.
'The first character indicates if we're going to play messages for the stack in question (new messages here) in LIFO or FIFO order – L or F respectively.
If StrComp(Mid(rsSubscribers("StackOrder"), 4, 1), "L", vbTextCompare) = 0 Then
AddText " Stack Order= LIFO"
Else
AddText " Stack Order= FIFO"
End If
'Now snag the 7 digits after the first character (position 5 in the string) and we'll parse it out to determine the order the user is palying messages for this stack.
strTemp = Mid(rsSubscribers("StackOrder"), 5, 7)
For iCounter = 1 To 7
'Pull the numbers out of the string of 7 characters one after another and print the human readable representation of which message type plays in that spot.
AddText " " + Choose(Val(Mid(strTemp, iCounter, 1)), "Urgent Voice Mail", "Voice Mail", "Urgent Fax", "Fax", "Urgent Email", "Email", "Receipts")
Next iCounter
End If
'The read message stack info is stored in the same string as the new message stack order, just after the "O:" near the end. We'll run through the same process here.
AddText "Active Stack message order for read messages"
If IsNull(rsSubscribers("StackOrder")) Then
'The default for read messages is LIFO
AddText " Stack Order= LIFO"
' The default order is message items 1 through 7 laid out in the "choose" function below
For iCounter = 1 To 7
AddText " " + Choose(iCounter, "Urgent Voice Mail", "Voice Mail", "Urgent Fax", "Fax", "Urgent Email", "Email", "Receipts")
Next iCounter
Else
'The user has customized the stack order, we need to pull out the string for new messages. The string looks something like this "NN:F1532467:0:L1234567". In this case we want the 7 characters after the "O:" which stands for old messages. The string is always the same length if it's not null so we can simply do this with a MID function
If StrComp(Mid(rsSubscribers("StackOrder"), 15, 1), "L", vbTextCompare) = 0 Then
AddText " Stack Order= LIFO"
Else
AddText " Stack Order= FIFO"
End If
'Grab the next 7 digits starting at string position 16.
strTemp = Mid(rsSubscribers("StackOrder"), 16, 7)
For iCounter = 1 To 7
'Pull the numbers out of the string of 7 characters one after another and print the human readable representation of which message type plays in that spot.
AddText " " + Choose(Val(Mid(strTemp, iCounter, 1)), "Urgent Voice Mail", "Voice Mail", "Urgent Fax", "Fax", "Urgent Email", "Email", "Receipts")
Next iCounter
End If
Armed with the information here you should be able to generate a pretty robust call handler and/or subscriber report and, with a little generalized application you should be able to apply that to other objects in the directory as well.
Create New Subscriber
Here's where things start getting interesting. Creating new subscribers requires we dance with the directory the Unity server is hooked up with (Exchange 5.5, Active Directory or Domino) to create objects and mailboxes external to Unity. In the case of Domino this is currently not possible. The DUCS interface provided by the Domino team does not account for the ability to dynamically create accounts on the fly as we can in Exchange 5.5 and Active Directory. As such this section only applies if your Unity servers are connected to Exchange 5.5, 2000 or 2003 at this point.
With the release of Unity 4.0(1) much of the really ugly legwork for adding directory and mail accounts has now been nicely wrapped up in the SQLSyncSvr functionality which is a huge win for folks wanting to do their own admin interfaces to Unity and is the primary reason I've waited until the release of 4.0 before supporting any external administration work. Prior to this the work to deal with this from off box was nothing short of a nightmare. Now it's all handled for you on the back end through stored procedures.
The key to understanding adding, importing, editing and deleting subscribers in Unity is understanding how this synchronization process works. If you haven't already now would be a real good time to go review the Architectural Overview chapter since much of the synchronization concept is covered in there. Lets review the interaction with the directory process as it pertains to creating new users.
The first step is to ensure you have licenses on the Unity server you want to create the subscriber on to accommodate a new user or the group of users you wish to add. Getting the licensing information off the Unity box is covered in the "Getting version and licensing information" section above. There are a couple routines defined in the Create Subscriber and Import Subscriber examples on the code samples page of www.CiscoUnityTools.com that allow you to simply pass in if the COS associated with the subscriber template is using TTS and/or VMI licenses and it'll return to you if you can or can't create a new subscriber using that COS on the box. In this case you'll need to go find the COS referenced by the subscriber template you're using to create the subscriber. This is easy enough to do if you have the alias or ObjectID of the subscriber template using this code:
StrSQL="SELECT AccessTTS, AccessVMI FROM vw_COS INNER JOIN vw_SubscriberTemplate ON vw_SubscriberTemplate.COSOBjectID=vw_COS.CosObjectID WHERE vw_SubscriberTemplate.Alias='" + strAlias + "'"
RsCOS.Open strSQL, strConnectionString, adOpenKeyset, adLockReadOnly
'You can check out the source for this routine in either the Create Subscriber or Import Subscriber code samples.
If checkForAvailableLicense(strUnityServerName, rsCOS("AccessTTS"), rsCOS("AccessVMI")) = False Then
<no licenses available to add new user to that COS>
Else
<go for it>
EndIf
If you decide to get cute and do an end run around the licensing system here, eventually your evil ways will catch up with you. In the short term it'll let you do it and the only thing you'll notice as you rocket past your licensed limits is that the SA will no longer allow you to add/import users directly and you'll sing and dance around your office with the joy of an amazing cost saving device you've discovered. Then clouds will roll in and you'll start noticing errors being logged daily to the application event log warning you that you're out of compliance and you need to purchase more licenses or delete users. Eventually Unity will shut itself down entirely. Then brawny, but well dressed fellows with snappy "Cisco" logos on their very large polo shirts will show up at your door with folded newspapers over their hands. OK, maybe not but there are no free lunches, people, so make sure you do your license checks up front and keep things on the up and up.
Once you know your licensing is OK, the next step is adding the information about the subscriber into the local SQL database in the Subscriber table using the sp_CreateSubscriber stored procedure. The stored procedure requires you pass in a primary extension number a reference for a subscriber template to use when creating the subscriber and the new subscriber's alias, first and last names. There are a number of other items you can optionally pass in such as the display name, SMTP Address, AddressByExtension value, language etc. Not all properties associated with a subscriber can be customized in this call to create the subscriber, it you wish to fiddle with things such as alternate extensions, greeting rules, contact rule configuration and the like you'll have to do that after the user has been created. This stored procedure will use the subscriber template you pass in and create the subscriber's primary call handler and all the associated objects such as contact rules, messaging rules, user input keys, notification devices and so on. The subscriber will be complete in the database with one big exception: they will not have a mailbox or a directory account.
The next step is to create the aforementioned directory and mailbox accounts. You can do this by setting the DirectorySync and DirectorySysncFlags values into the stored procedure such that Unity will attempt to create these accounts for you automatically when you add the subscriber to SQL. This process is asynchronous which means the stored procedure is going to return to you right away and if you want to see if the user was successfully added to the directory you need to pass in a DirectorySyncTicket and watch the DirectorySyncTicket table and wait for your ticket to show up there with a "pass or fail" grade. You definitely just don't want to assume the user has been added to the directory and move on here since there are any number of reasons this can fail all of which are external to Unity and out of our control. If the user failed to get a directory and mailstore object created you need to report that error back to the user somehow and back that user out of SQL by deleting their subscriber record. Triggers in SQL will automatically remove all their associated information in other tables as needed.
You can also do this by adding a bunch of subscribers to SQL using the same sp_CreateSubscriber stored procedure, however not passing in any DirectorySync flags. Once you're done adding users to the directory you can call the sp_DirectorySync stored procedure and have it synchronize all users in the Subscriber table that do not have a DirectoryId value yet. The lack of a DirectoryID value indicates the user does not have an object in the directory associated with them and, as such, they are not a valid subscriber. We'll cover this method of adding and importing users in bulk in the Batch Subscriber Operations section a little later.
The directory creation function is done using the rights of the domain account you have associated with your "directory facing" accounts in Unity. Specifically the account associated with the directory facing account such as AvDSAD service in the case of Exchange 2000/2003 or AvDSEx55 in the case of Exchange 5.5. If the permissions on that account do not have rights to create new user objects in the container specified (which defaults to the container you selected during Unity configuration setup if you don't over ride it when calling the sp_CreateSubscriber stored procedure) then the create will obviously fail. You need to be sure that account has all the juice you need for creating users where you want.
The high level details here are actually pretty straight forward. Lets actually work through the code needed to do this and discuss some of the options you have along the way. This is part of the code you'll find in the Create Subscriber example found on the source code samples page of www.CiscoUnityTools.com:
'This function returns true if the subscriber has their directory object and mailbox created OK and false otherwise. This function is specifically setup to only add a new user to the directory, it will not allow the synchronization process to bind to an existing user in the directory. It's a real good idea for you to follow the same convention to avoid messy accidents in your directory. For binding to existing users you should use a separate import function as discussed in the next section.
This function returns true of the subscriber was properly created and false otherwise.
Public Function CreateSubscriber() As Boolean
Dim rsCreate As ADODB.Recordset
Dim oCommand As ADODB.Command
Dim strSyncTicket As String
Dim iCounter As Integer
Dim strTemp As String
Set rsCreate = New ADODB.Recordset
Set oCommand = New ADODB.Command
oCommand.ActiveConnection = strConnectionString
oCommand.CommandType = adCmdStoredProc
'I assume at this point that you've already gathered the alias, extension number, the alias of the subscriber template you want to use for creating this user and their first and last names. If you're getting this from the user directly or slurping it in from a CSV file or whatever is not really important to the creation process here.
oCommand.CommandText = "sp_CreateSubscriber"
'It's a good idea to use the CreateParameter function provided off the Command object in ADO here to add parameters to your stored procedure since it takes care of expunging stray single quotes and the like automatically such that you don't get malicious folks slipping in ugly stuff into your SQL commands. You can build a straight string with the parameters yourself but all my code uses this method.
'In this example the alias, extension number (DTMFAccessID), first name, last name and display name are being pulled off of text controls on the main form that the user has populated manually. You can, of course, snag this information any way you like.
oCommand.Parameters.Item("@Alias") = oCommand.CreateParameter("Alias", adVarChar, adParamInput, , txtAlias.Text)
oCommand.Parameters.Item("@DtmfAccessId") = oCommand.CreateParameter("DtmfAccessId", adVarChar, adParamInput, , txtExtension.Text)
oCommand.Parameters.Item("@FirstName") = oCommand.CreateParameter("FirstName", adVarChar, adParamInput, , txtFirstName.Text)
oCommand.Parameters.Item("@LastName") = oCommand.CreateParameter("LastName", adVarChar, adParamInput, , txtLastName.Text)
oCommand.Parameters.Item("@DisplayName") = oCommand.CreateParameter("DisplayName", adVarChar, adParamInput, , txtDisplayName.Text)
'For this example I assume we're creating a full Exchange subscriber here, not an internet subscriber so we hard code the SubscriberType parameter to "1". You can, as always, check CUDLE for the different subscriber type values allowed here, however creating remote users is a bit of a different bag than creating full subscribers so be sure you know what you're doing.
oCommand.Parameters.Item("@SubscriberType") = oCommand.CreateParameter("SubscriberType", adInteger, adParamInput, , 1)
'Only the default Example Administrator account should be marked undeletable however in special cases you may wish to add your own accounts that can't be deleted from the SA interface as well. Here I assume the normal case of deletable.
oCommand.Parameters.Item("@Undeletable") = oCommand.CreateParameter("Undeletable", adInteger, adParamInput, , 0)
'You can pass either the alias of a subscriber template here as I do or, perhaps better style, the SubscriberTemplateObjectID value into the @templateOid parameter instead. Either way will work fine, I use the alias here since it was easier to list it in a combo box and have the user select one.
oCommand.Parameters.Item("@templateAlias") = oCommand.CreateParameter("templateAlias", adVarChar, adParamInput, , cmbTemplates.Text)
'The UID and the alias should almost always match - the UID is actually what maps to the mail alias in the directory - the alias field above is used internally by Unity - if you don't pass a UID value it'll use the Alias string but it's a good idea to always pass it explicitly to avoid any confusion.
oCommand.Parameters.Item("@Uid") = oCommand.CreateParameter("Uid", , adParamInput, adVarChar, txtAlias.Text)
'The phone password is passed in as clear text here - it gets crunched and encrypted into MD5 by the stored procedure during the subscriber create process. This parameter is optional, the subscriber template has a default phone password that will be applied if this is not included.
oCommand.Parameters.Item("@PWDTMF") = oCommand.CreateParameter("PWDTMF", adVarChar, adParamInput, , txtPhonePassword.Text)
'Another optional value is to list the user in the directory based on a checkbox value on the main form. Again, the subscriber template will fill in a default value for this if it's not passed in.
oCommand.Parameters.Item("@ListInDirectory") = oCommand.CreateParameter("ListInDirectory", adBoolean, adParamInput, , chkListUsersInDirectory.Value = vbChecked)
'The mailstore you create the user on can be left up to the back end if you like - it'll create the user in the Organization Unit (E2K) or user container (E55) you selected during the mailstore configuration setup portion of the Unity install. If you'd like to dictate a particular mailstore you need to include the MailboxStoreObjectID value from the MailboxStore table for the store you want. We have this information stored on the form in two drop down controls on the main form in my example here. The MailboxStore table will include ALL mailstores visible in the forest or oranization. Remember, it's up to you to make sure the domain account you have associated with the Unity directory services has rights to create users in this container. The Permissions Wizard will only check the one container you selected during initial setup.
'On my form I have a readable list of mailstore names in the cmbMailstores combo box and a list of MailstoreObjectID values in the hidden cmbMailstoreObjectIDs combo box. The two are tied together using the list ID values such that when a user selects a readable mailstore name we can fish out it's objectID easily. With a decent 3rd party grid control at your disposal this would be a little nicer but we're trying to keep the sample code generic.
oCommand.Parameters.Item("@MailboxStoreObjectID") = oCommand.CreateParameter("MailboxStoreObjectID", adGUID, adParamInput, , cmbMailstoreObjectIDs.List(cmbMailStores.ListIndex)
'You usually want to tell the stored procedure to synchronize SQL to the directory for this subscriber after adding them so you should pass a 1 for the DirectorySync flag here. In rare cases you may want to add users without synching and then do it "all at once" after, say, a big batch operation. We'll deal with that scenario in the Batch Subscriber Operations section later.
oCommand.Parameters.Item("@DirectorySync") = oCommand.CreateParameter("DirectorySync", adInteger, adParamInput, , 1)
'We want to pass in a resynch mask that synchs the subscriber, the DLs they were added to and to only try create a new object in AD, not to bind to an existing one if the mail alias happens to match something already there. The synchronization process accepts a number of flags to control what it is going to write to the directory from the SQL database. Here's a list of the flags of interest to us:
'0x00000001 - synch subscribers
'0x00000002 - synch distribution lists
'0x00000008 - synch locations. For synching new users this flag does not apply. You'd only use this if you were creating or editing delivery location objects.
'0x0000FFFF - synch all types
'0x01000000 - only synch new guys without directory ID values - used for batch processing. In this case where we're synching an individual user this flag does not apply.
'0x02000000 - don't create anything, just try to associate to existing object - prevents accidental object creation in AD.
'0x04000000 - only try to create new, don't attempt to associate
'0x08000000 - Create NT account - for use with Ex55 systems. You have the option of only creating a mailbox in Exchange 5.5 and not an NT account to go with it. This would prevent the user from getting access to the SA or PCA interfaces but may be desirable in some scenarios. This example is written against Exchange 2000 so I don't pass this in.
'The values we're interested in for this operation are to create only new subscribers and to sync distribution lists that user may have been added via their subscriber template definition. These flags are 0x04000000 + 0x00000001 + 0x00000002 = 67108867 decimal. With this flag if there is a conflicting alias in the directory in the domain the user is being added to, the sync will be considered a failure and the user will not be added.
oCommand.Parameters.Item("@DirectorySyncFlags") = oCommand.CreateParameter("DirectorySyncFlags", adInteger, adParamInput, , 67108867)
'We need to create a GUID that we can pass into the stored procedure such that we can look for it in the DirectorySyncTicket table in SQL which is used to pass back the results of this sync - we have to use an asynchronous process here since the synch with AD can take a while and doing a blocking call for such things is not a good idea. See below where we wait for the ticket to get inserted into the table and let us know how things went. If you don't pass a synch ticket here, no record is created in the DirectorySyncTicket table. We show another method of dealing with this issue in the Subscriber Batch Operations section later. The generateDirectorySyncTicket is routine that calls the "GUID" function in the Scriptlet.TypeLib library. This just generates a random GUID we can use as a unique identifier to watch for – you can get the code behind this in the full listing for this example on www.CiscoUnityTools.com.
strSyncTicket = generateDirectorySyncTicket
oCommand.Parameters.Item("@DirectorySyncTicket") = oCommand.CreateParameter("DirectorySyncTicket", adGUID, adParamInput, , strSyncTicket)
'Let the stored procedure call rip. Remember, this will return right away even if you've asked it to sync the user to the directory.
rsCreate.CursorLocation = adUseClient
rsCreate.Open oCommand, , adOpenDynamic, adLockOptimistic
'The SP will return a recordset populated with one row for the subscriber you added. The row returned is from the subscriber table in UnityDb so if you want to customize your users' properties with items that are not included in the stored procedure's parameters, now's your chance. If something went wrong, the recordset will be empty.
If rsCreate.RecordCount = 0 Then
MsgBox "SubscriberCreate stored procedure failed: " + Err.Description
CreateSubscriber = False
'Since the subscriber creation stored procedure failed to create anything, there's nothing to clean up here. We can just exit out after cleaning up the local recordsets
GoTo BailOut
End If
'The stored procedure itself kicked off the synchronization process with the directory using the flags we used above. You need to wait for the sync to complete here and check for errors before moving on unless you're doing some batch operation or you have a note from your mother stating otherwise. The WaitForSyncToFinish function will wait for the syncher to finish and will also take care of cleaning up the synch ticket row for you as well as rolling back the subscriber add if it fails for whatever reason. The code listing for this function follows shortly.
If WaitforSyncToFinish (strSyncTicket) = False Then
'We remove the subscriber using the sp_DeleteSubscriber stored procedure here since leaving a subscriber that has not properly been tied to a directory object in SQL is a bad idea all around. The only required parameter for this stored procedure is the SubscriberObjectId value which we have off the rsCreate recordset returned from the subscriber creation stored procuedure earlier.
oCommand.CommandText = "sp_DeleteSubscriber"
oCommand.Parameters.Item("@SubscriberObjectID") = oCommand.CreateParameter("SubscriberObjectID", adGUID, adParamInput, , rsCreate("SubscriberObjectID")
'There's no reason to pass in any flags to force a directory synch here since this guy was not tied to a directory object to begin with. As such, just execute the stored procedure here. Triggers in SQL will take care of getting rid of all the related objects in the database associated with this subscriber such as it's primary call handler, MWI entires, notification devices etc…
oCommand.Execute
CreateSubscriber=False
MsgBox "The syncronization to the directory failed for this subscriber add. Subscriber removed from SQL. You can check the SQLSyncSvr logs in the \commserver\logs directory for more details."
Else
CreateSubscriber=True
MsgBox "Subscriber Added"
End If
BailOut:
'clean up after ourselves – not strictly necessary with the VB garbage collection stuff but not a bad thing to do in general
On Error Resume Next
rsCreate.Close
Set rsCreate = Nothing
Set oCommand = Nothing
End Function
If the sync process went through then the subscriber is ready to roll. The licensing service will automatically update the used license counts and, if you're in a pooled license scenario, push that information out to the directory for the other Unity servers to know about. You shouldn't need to do anything other than checking for available license counts up front before adding a new user.
I've consolidated the logic for waiting for a syncronization operation to complete and cleaning up after myself into one function since it's something that needs to be done so often. Whenever you add, delete or edit a subscriber or add, delete or edit a distribution list you'll have to syncronize that information to the directory. It's a good idea to get such a function in place in your own applications early on since you'll need to use it often.
'The WaitForSyncToFinish function returns TRUE of the syncronization succeeds and FALSE if it fails. In either case it will clean up the SyncTicket table when it's done.
Public Function WaitForSyncToFinish(strSyncTicket As String) As Boolean
Dim rsSyncTicket As ADODB.Recordset
Dim iCounter As Integer
Dim oCommand As ADODB.Command
Set rsSyncTicket = New ADODB.Recordset
Set oCommand = New ADODB.Command
oCommand.ActiveConnection = strConnectionString
oCommand.CommandType = adCmdStoredProc
'I have this hard coded to check every 2 seconds up to 20 times (40 seconds). You can change this or make it a parameter you pass in or whatever. If it takes more than 40 seconds to sync something to the directory, however, you have some latency issues outside of Unity that you need to take a close look at.
For iCounter = 1 To 20
Sleep 2000
rsSyncTicket.Open "SELECT * FROM vw_DirectorySyncTicket WHERE Ticket='" + strSyncTicket + "'", strConnectionString, adOpenKeyset, adLockReadOnly
If rsSyncTicket.RecordCount > 0 Then
'the record is created and then the result value is populated in it- the timing can bit you here so after we open the recordset and find a match on the DirectorySyncTicket check the result value - it should never be NULL so if it is, fall through and close/reopen the record set in another second. A little awkward, but hey...
If IsNull(rsSyncTicket("result")) = False Then
GoTo TicketFound
End If
End If
rsSyncTicket.Close
Next iCounter
'We timed out waiting for the syncronization to complete. There's no row to remove so we can just pull the rip cord here.
WaitForSyncToFinish = False
GoTo BailOut
TicketFound:
rsSyncTicket.MoveFirst
If rsSyncTicket("result") = 0 Then
'you're golden, everything finished OK.
WaitForSyncToFinish = True
Else
'You have not been living right and the synch failed for some reason.
WaitForSyncToFinish = False
End If
'Since we explicitly requested a synch ticket above we need to clean up that row in the DirectorySyncTicket table
oCommand.CommandText = "sp_DeleteDirectorySyncTicket"
oCommand.Parameters.Item("@Ticket") = oCommand.CreateParameter("Ticket", adGUID, adParamInput, , rsSyncTicket("Ticket"))
oCommand.Execute
BailOut:
On Error Resume Next
Set oCommand = Nothing
rsSyncTicket.Close
Set rsSyncTicket = Nothing
End Function
We'll reference the WaitForSyncToFinish function in other routines that require syncronization to the directory later in this chapter as well.
ไม่มีความคิดเห็น:
แสดงความคิดเห็น