For future-proofing, our Internal Phone Directory is being moved onto our AD servers, and obviously we're using LDAP for queries.
To get the phoneboook queries working, I adapted the current classic ASP code to use LDAP, and that works fine. However, we're wanting to move these pages to .NET, and that's where the problem comes in. Using the same basic logic from the Classic code (90% of its unchanged) to produce an LDAP query, I get the error
155 Unknown error (0x80005000)
This is using the same website, and so the same credentials - unless aspx pages run under a different account to asp ones?
![]() |
0 |
![]() |
theoldgoat:
unless aspx pages run under a different account to asp ones?They do. Classic ASP is usually the IUSR/IWAM accounts, ASP.NET is the ASP.NET process account, NETWORK SERVICE on IIS 6 and 7.
Jeff
Please: Don't forget to click "Mark as Answer" on the post that helped you. That way future readers will know which post solved your issue.
![]() |
0 |
![]() |
Am I right in thinking that I need to use the
DirectoryEntry class to do this correctly?
![]() |
0 |
![]() |
Right. I added
On Error Resume Next
Dim oRoot As DirectoryEntry = New DirectoryEntry(sql)
If Err.Number <> 0 Then Trace.Write("139 " & Err.Description)
oRoot.Username = "xxx\xxxx" '' added for authentication
oRoot.Password = "xxxxxx" '' added for authenticationoRoot.AuthenticationType = AuthenticationTypes.Secure
to the code, but still comes back with the 80005000 error
![]() |
0 |
![]() |
Okay, first of all, VBScript and VB.Net are quite different beasts, certainly a lot more different that VBScript and VB6, so you can just expect things not to work. Or not to work the way you'd expect. On the other hand, VB.Net is just so much better than VBScript at pretty much everything that it's worth the pain of learning.
Here's an example: VBScript has On Error Resume Next and Err.Number. Yuck! VB.Net has exceptions and "try catch blocks". You can find loads of help online. I've posted a few addresses at the bottom to get you started. The best bit about them is that you can specify the exception(s) to catch and any others still be thrown and you can specify different code to run for each type of exception that may be thrown. On Error Resume Next is in there for backwards-compatibility - exception-handling is the way forward.
As for the specifics, what's contained in the string 'sql'? I'm surprised you're seeing error 139 because it doesn't usually fetch the directory entry until you actually do something with it (like read a value). I think we need to see a little more of your code.
A DirectoryEntry is required if you need to update an entry in AD. It can also be used to retrieve values for a single object in AD. Are you using DirectorySearcher to search the directory? If so, you can retrive values from the SearchResult objects that you'll get back - they're kind of like a read-only DirectoryEntry but the syntax is slightly different.
If you're still having problems, one thing you can try is to get the basic query running in a console application and then transfer the code to your ASP.NET project.
http://support.microsoft.com/kb/315965
http://www.winnershtriangle.com/w/VBNetHelp_TryCatchErrorHandling.asp
http://www.homeandlearn.co.uk/NET/nets5p4.html
![]() |
0 |
![]() |
I'd tried using Try...Catch but as I cannot see where the error reports go, it was a little pointless. Trace.Write reports that it cannot convert the Catch output to a string for instance.
the sql string is just a prebuilt LDAP query -
<LDAP://cycdc02.xxx.xxx.uk/OU=Users,OU=CYC,DC=xxx,DC=xxx,DC=uk>;(&(objectCategory=person)(objectClass=user)(sn=ba*));sn,givenname,telephoneNumber,mail,department,title,displayname,company
is a fairly average one that returns about 35 entries via the asp page.
I'm only looking to do read queries - no updates from this at all. Since you ask, this is the content of the .vb file:
Imports System.DirectoryServices
Partial Class phoneSearchResultADS
Inherits System.Web.UI.Page
Dim strusername As String, struserfname As String
Dim strusersname As String, strddp As String
Dim strPhone As String, intuserid As Integer
Dim strfrom As String
Dim sql As String, arrusername As Array
Dim optname As String, strFirstName As String, strSurName As String
Dim intsearchwg As String, searchwg As String, strFilterNameOn As String
Dim strFilterOn As String, strFilterOff As String, strquote As String
Dim concatflag As Boolean, search_quote As String
Public Sub Page_load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
GetQueryVariables()
If strusername & struserfname & strusersname & strddp & strPhone = "" Then Response.Redirect("phonebook/phonesearchADS.asp")
PrepareQuery()
GetPhoneList(sql)
End Sub
Public Sub GetQueryVariables()
If Request.ServerVariables("request_method") = "GET" Then
strusername = Trim(Request.QueryString("strusername"))
intuserid = Request.QueryString("intuserid")
struserfname = Trim(Request.QueryString("struserfname"))
strusersname = Trim(Request.QueryString("strusersname"))
strddp = Trim(Request.QueryString("strddp"))
strphone = Trim(Request.QueryString("strphone"))
Else
strfrom = Request.Form("from")
Trace.Write("strFrom[" & strfrom & "]")
strusername = Trim(Request.Form("strusername"))
strusername = Replace(strusername, "'", "''")
If strfrom <> "ff" Then
struserfname = Trim(Request.Form("struserfname"))
struserfname = Replace(struserfname, "'", "''")
strusersname = Trim(Request.Form("strusersname"))
Trace.Write("strusersname[" & strusersname & "]")
strusersname = Replace(strusersname, "'", "''")
intuserid = Int(Request.Form("intuserid"))
strddp = Trim(Request.Form("strddp"))
strPhone = Trim(Request.Form("strphone"))
Else
intuserid = 0
strusername = Replace(Trim(Request.Form("strusername")), "'", "''")
End If
End If
End Sub
Public Sub PrepareQuery()
sql = "<LDAP://cycdc02.xxx.xxx.uk/OU=Users,OU=CYC,DC=xxx,DC=xxx,DC=uk>;(&(objectCategory=person)(objectClass=user)"
If strfrom = "ff" And InStr(strusername, " ") > 0 Then
arrusername = Split(strusername, " ", -1, 1)
struserfname = arrusername(0)
strusersname = arrusername(1)
If UBound(arrusername) > 1 Then
For x = 2 To UBound(arrusername)
strusersname = strusersname & " " & arrusername(x)
Next
End If
ElseIf strfrom = "ff" And InStr(strusername, " ") = 0 Then
struserfname = ""
strusersname = strusername
End If
If (struserfname > "" And strusersname > "") Or strfrom = "ff" Then ' can assume full name
optname = "a"
Trace.Write("strUserfName[" & struserfname & "]")
'arrSearchString = Split(strUsersName," ",-1)
If InStr(strusersname, " ") > 0 Then ' assume double surname
strFirstName = RTrim(struserfname)
'for x = 1 to ubound(arrSearchString)
' strSurName = arrSearchString(x)&" "
'next
Dim strSurName As String = RTrim(strusersname)
Else
Dim strFirstName As String = RTrim(struserfname)
Dim strSurName As String = RTrim(strusersname)
End If
ElseIf RTrim(struserfname) = "" Then 'can assume surname
optname = "s"
strFirstName = ""
strSurName = RTrim(strusersname)
Else ' assume first name
optname = "f"
strSurName = ""
strFirstName = RTrim(struserfname)
End If
intsearchwg = 0
If searchwg = "yes" Then intsearchwg = 1
strddp = Replace(strddp, "'", "''")
strFilterNameOn = "like '"
strFilterOn = "like '%"
strFilterOff = "%' "
strQuote = " like "
concatflag = False
search_quote = ""
If strFirstName > "" Then
sql = sql & "(givenname=" & strFirstName & "*)"
search_quote = "<span style = ""color:#4682B4;"">First Name</span> " & strquote & " " & strFirstName
concatflag = True
End If
If strSurName > "" Then
sql = sql & "(sn=" & strSurName & "*)"
search_quote = search_quote & "<span style = ""color:#4682B4;"">Surname</span> " & strquote & " " & strSurName
concatflag = True
End If
If strddp > "" Then
sql = sql & "(|(company=" & strddp & "*)(department=" & strddp & "*)(Title=" & strddp & "*))"
search_quote = search_quote & "<span style = ""color:#4682B4;"">dept/wgp/job</span> " & strquote & " " & strddp
concatflag = True
End If
If strPhone > "" Then
sql = sql & "(telephoneNumber=" & strPhone & "*)"
'response.write("<!-- strphonecol[" & strPhoneCol & "]strFilterOn[" & strFilterOn & "]strddp[" & strddp & "]strFilterOff[" & strFilterOff & "] -->" & vblf)
search_quote = search_quote & "<span style = ""color:#4682B4;"">phone number</span> " & strquote & " " & strPhone
End If
sql = sql & ");sn,givenname,telephoneNumber,mail,department,title,displayname,company"
' Return
End Sub
Public Function GetPhoneList(ByVal sql As String) As Hashtable
'To retrieve list of all LDAP users
Trace.Write("Query string " & sql)
'This function returns HashTable
'_ldapServerName = ldapServerName
'Dim sServerName As String = "mail"
On Error Resume Next
'Try
Dim oRoot As DirectoryEntry = New DirectoryEntry(sql)
If Err.Number <> 0 Then Trace.Write("139 " & Err.Description)
oRoot.Username = "xxx\xxx"
If Err.Number <> 0 Then Trace.Write("142 " & Err.Description)
oRoot.Password = "xxx"
If Err.Number <> 0 Then Trace.Write("144 " & Err.Description)
'On Error Resume Next
oRoot.AuthenticationType = AuthenticationTypes.Secure
If Err.Number <> 0 Then Trace.Write("146 " & Err.Description)
Dim oSearcher As DirectorySearcher = New DirectorySearcher(oRoot)
If Err.Number <> 0 Then Trace.Write("147 " & Err.Description)
Dim oResults As SearchResultCollection
If Err.Number <> 0 Then Trace.Write("151 " & Err.Description)
Dim oResult As SearchResult
'Dim RetArray As New Hashtable()
' Try
'oSearcher.PropertiesToLoad.Add("uid")
'oSearcher.PropertiesToLoad.Add("givenname")
'oSearcher.PropertiesToLoad.Add("cn")
' On Error Resume Next
oResults = oSearcher.FindAll
If Err.Number <> 0 Then Trace.Write("160 " & Err.Description)
'Catch comEx As System.Runtime.InteropServices.COMException
'Console.WriteLine(comEx)
'Trace.Write("159")
'Catch invOpEx As InvalidOperationException
'Console.WriteLine(invOpEx)
'Trace.Write("162")
' Catch notSuppEx As NotSupportedException
'Console.WriteLine(notSuppEx)
'Trace.Write("164")
'Catch e As Exception
'Trace.Write("Error 163 " & e.Message)
' Return RetArray
' End Try
'For Each oResult In oResults
' Trace.Write("output " & oResult.GetDirectoryEntry().Properties("sn").Value)
' 'If Not oResult.GetDirectoryEntry().Properties("cn").Value = "" Then
' 'RetArray.Add(oResult.GetDirectoryEntry().Properties("uid").Value, oResult.GetDirectoryEntry().Properties("cn").Value)
' 'End If
'Next
'Return RetArray
' End Try
End Function
End Class
![]() |
0 |
![]() |
I switched nack to using the try catch way, after realising that .tostring sorts out the error message!
this is what I get back
Query string <LDAP://cycdc02.york.gov.uk/OU=Users,OU=CYC,DC=york,DC=gov,DC=uk>;(&(objectCategory=person)(objectClass=user)(sn=ba*));sn,givenname,telephoneNumber,mail,department,title,displayname,company 0.669713051859671 0.367406
159 System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindAll()
at phoneSearchResultADS.GetPhoneList(String sql)
error is from Catch comEx As System.Runtime.InteropServices.COMException
for clarity, this query would expect to return 30 + records.
![]() |
0 |
![]() |
I'm guessing, given that you've called it 'sql' that the string was used in an ADO query in your ASP site, is that right?
Things are a bit different in .NET.
DirectoryEntry returns a single object representing an object in the directory, e.g. a user account or a group account, or an organizationalUnit.
DirectorySearcher is used to find one or more objects and returns a collection of SearchResults (which are like read-only DirectoryEnty objects but with slightly different syntax).
You can create a DirectoryEntry and use it as the root of your search, to narrow the search down to a particular set of OUs. Since it looks like that's what you need to do, I'll do that in my code.
Now, since I don't know what your domain is called, I'm going to use the RootDSE object to get hold of the defaultNamingContext, which is the name of the domain that the computer is in, in distinguishedName format. You can remove this bit and hard-code it, if you like.
Then i'm going to use that to build the DirectoryEntry for your searchRoot. You'll see that I haven't specified the computer name, partly because mine isn't called that but mainly because you don't need to - AD will sort it out for you. If you have a reason to, you can add it in exactly the same way you did in your ASP search string.
Then I'm going to build the search object and return a collection of objects for processing. I've used a shorter filter than yours but you can put anything here. At the end of your searchFilter, I'm guessing, is the list of attributed to return. You don't need this in .NET, as by default all non-constructed attributes are returned. However, you can specify it using PropertiesToLoad(), so I have. By default, AD will only return the first 1000 objects but if you specify a PageSize (up to 1000) AD will return > 1000 objects in 'pages' of whatever size you specify.
Once I have a SearchResultCollection, I'm going to iterate through it and write some values to the screen. I'll test to make sure they exist before I attempt to write them to avoid NullReferenceExceptions. The Properites property on the SearchResult is a Name-Value type of collection, so you use the property name as an index to retrieve the property value. E.g. "displayName" might be an index. The type of the property value is ResultPropertyValueCollection, even for attributes which can only ever have one value a collection is returned. This means you need to index the first value, for single-valued attributes, hence the (0) in sr.Properties("displayName")(0). It makes my brain ache, too. As an aid, I've also show how to interate over the results of a multi-valued attribute like 'otherTelephone' which can have > 1 value.
You can use SearchResult.GetDirectoryEntry() but this will slow down your return by quite a bit. The syntax is slightly easier but it's not worth if it, if you want a responsive app and you're only doing reads.
I don't go in for Hungarian naming or any of the similar conventions, since Visual Studio will always tell you what an object is, so you'll have to put up with my naming.
The Using statements avoid me having to remember to call the Close() or Dispose() methods to tidy up the objects - I recommend using them (pardon the pun).
So, you probably want something like this as the core of your GetPhoneList code:
Dim defaultNamingContext As String Using rootDSE As DirectoryEntry = New DirectoryEntry("LDAP://RootDSE") defaultNamingContext = rootDSE.Properties("defaultNamingContext").Value.ToString() End Using Response.Write("defaultNamingContext = " + defaultNamingContext) Using searchRoot As DirectoryEntry = New DirectoryEntry("LDAP://OU=Users,OU=CYC," + defaultNamingContext) Using ds As DirectorySearcher = New DirectorySearcher() ds.SearchRoot = searchRoot ds.SearchScope = SearchScope.Subtree ds.Filter = "(&(objectCategory=person)(objectClass=user))" ds.PropertiesToLoad.AddRange(New String() {"sn", "givenname", "telephoneNumber", "mail", "department", "title", "displayname", "company", "otherTelephone"}) ds.PageSize = 1000 Using src As SearchResultCollection = ds.FindAll() For Each sr As SearchResult In src If sr.Properties("displayName").Count > 0 Then Response.Write("<BR />DisplayName = " + sr.Properties("displayName")(0).ToString()) End If If sr.Properties("telephoneNumber").Count > 0 Then Response.Write("<BR /> telephoneNumber = " + sr.Properties("telephoneNumber")(0).ToString()) End If If sr.Properties("otherTelephone").Count > 0 Then For Each otherTel As Object In sr.Properties("otherTelephone") Response.Write("<BR /> otherTelephone = " + otherTel.ToString()) Next End If Next End Using End Using End UsingGood luck! Let us know how you get on.
![]() |
0 |
![]() |
Exception Details: System.DirectoryServices.DirectoryServicesCOMException: The specified domain either does not exist or could not be contacted.
against
defaultNamingContext = rootDSE.Properties("defaultNamingContext").Value.ToString()
fixed that when I realised what you ment about the rootDSE!
Oh, the joys of learning it all again!
Anyhow, many thanks, it now gives me back the list of names I was expecting
![]() |
0 |
![]() |
Oh, a heads-up for anyone else who's just tried to add the
ds.sort.propertyname = "xxx"
ds.sort.direction = sortdirection.ascending
LDAP is as badly behaved as ever for giving you useless error messages.
If you try and sort a field that you have not already added for the scope of the query, it forces an error further down that makes exactly no sense!
![]() |
0 |
![]() |
Your domain admins would probably prefer that you did the sort locally, anyway - they're not usually happy about tying up DC resources like that.
![]() |
0 |
![]() |