How to read all attribute values in a VBA LDAP query? - vba

I'm doing some development work and need to see what data is stored in Active Directory in order to figure out which fields are being used and may contain information that will be useful. I have a method of pulling all of the mandatory property field names, and of pulling the data with LDAP if I know what the field names are, but what I can't figure out is how to do it dynamically. Here's my code:
Function LDAPdump()
Dim objSysInfo As Object
Dim objSchema As Object
Dim objRecordset As Object
Dim objConnection As Object
Dim objCommand As Object
Dim objUser As Object
Dim objLUser As Object
Dim dname As String
Dim DLoc As Integer
Dim ADaddress As String
Set objSysInfo = CreateObject("ADSystemInfo")
Set objSchema = GetObject("LDAP://schema/user")
Set objUser = GetObject("LDAP://" & objSysInfo.UserName)
dname = objUser.distinguishedName
DLoc = InStr(dname, "DC=")
ADaddress = Right(dname, Len(dname) - DLoc + 1)
Const ADS_SCOPE_SUBTREE = 2
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = "SELECT * FROM 'LDAP://" & ADaddress & "' WHERE objectCategory='user' AND cn='" & objUser.CN & "'"
Set objRecordset = objCommand.Execute
If objRecordset.RecordCount = 0 Then
MsgBox "no data"
Exit Function
End If
Set objLUser = GetObject(objRecordset.Fields("ADsPath").Value)
'print all mandatory property field names
For Each strAttribute In objSchema.MandatoryProperties
Debug.Print strAttribute & ": " & objLUser(CStr(strAttribute)).Value
Next
End Function
The spot that's specifically giving me issues is pulling the objLUser attribute value. For example, when I run this code the first attribute that comes up is cn. If I specifically write Debug.Print strAttribute & ": " & objLUser.cn I get cn: Chaosbydesign back in the immediate window, but when I try to dynamically get it in the For loop, I get back "The directory property cannot be found in the cache." Looking at objUser or objLUser in the Locals window doesn't provide any hints to a solution because it just says <No Variables> when expanded, even though I can go into the immediate window and type in ? objLUser.cn/mail/displayname and get my common name, email and display name back.
I have a workaround for this that involves a bit of shenanigans with a text file and Excel to basically write out the Debug.Print line for every single attribute, so this problem itself isn't a show stopper, more just an annoyance that I'd like to figure out.

Related

Import Named Lotus123 spreadsheet to MS Access

I'm in the process of moving all the Lotus sheets a company has to a SQL Server and I am using MS Access tables as an intermediary.
This code has been working fine for sheets that are not named but I've come across about 2300 or so sheets where it's named numb. There are too many files for me to manually change them all.
The error I am getting is:
Run-Time error '-2147217865(80040e37)':
The Microsoft Jet Database engine could not find the object "numb:A1..numb:A8000". Make sure the object exists and that you spell its name and the path name correctly.
Found this site but that hasn't provided the answer
I've seen different options for getting the destination right on an ADOB call ("SELECT * FROM [numb:A1..numb:A8000];" or ("SELECT * FROM [numb$:A1..numb$:A8000];") and those haven't worked.
Here is the functioning code when the sheet isn't named:
Dim rst As DAO.Recordset
Dim rs As DAO.Recordset
Dim LotusCn As Object
Dim rsLotus As Object
'Read WK3 Lotus files
repcode = rs![Code]
Directory = rs![Directory]
Directory = Directory & "NUMDATM.WK3"
Set LotusCn = CreateObject("ADODB.Connection")
Set rsLotus = CreateObject("ADODB.Recordset")
'This creates the objects for the lotus connctions
'below the connection string
LotusCn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & Directory & ";" & _
"Extended Properties=Lotus WK3;Persist Security Info=False"
strSQL = "SELECT * FROM [A1..A8000];" 'The SQL to pick the right sections of the lotus file. Picks the Latest Available Date
rsLotus.Open strSQL, LotusCn, adOpenStatic '<<< ***Error occurs here***
If Not (rsLotus.EOF And rsLotus.BOF) Then
FindRecordCount = rsLotus.RecordCount
rsLotus.MoveFirst
Do Until rsLotus.EOF = True
Debug.Print rsLotus.Fields(0).Value
If Len(rsLotus.Fields(0).Value) > 0 Then
rst.AddNew
rst!RegNo = rsLotus.Fields(0).Value
rst.Update
End If
rsLotus.MoveNext
Loop
End If
LotusCn.Close
strSQL = ""
Set rsLotus = Nothing
Set LotusCn = Nothing
Does anyone know how to get named spreadsheets?
I dont know exactly how in Lotus but this was in Excel
Set oRs = oConn.OpenSchema(adSchemaTables) 'get the name of the sheet in Excel
oRs.MoveFirst
With oRs
While Not .EOF
If .fields("TABLE_TYPE") = "TABLE" Then
Debug.Print .fields("TABLE_NAME")
If VBA.Len(.fields("TABLE_NAME")) = 9 Then
WSnameTBL = .fields("TABLE_NAME")
Else
End If
' WSnameTBL = VBA.Replace(WSnameTBL, "$", "", 1, , vbTextCompare)
End If
.MoveNext
Wend
End With

Is it possible to send off info row by row from Access to QuickBooks?

Currently I have the following code that allows me to insert values into specific fields in QuickBooks.
I am trying to add fields from a table into QuickBooks row by row:
See picture ex:
Example:
At the end of each row there is a column for sending off the entries to QuickBooks. How can I modify my code to have this function work?
Public Sub exampleInsert()
Const adOpenStatic = 3
Const adLockOptimistic = 3
Dim oConnection
Dim oRecordset
Dim sMsg
Dim sConnectString
Dim sSQL
sConnectString = "DSN=Quickbooks Data;OLE DB Services=-2;"
sSQL = "Insert into customer (Name, FullName, CompanyName) values ('Testing VB', 'Full Name', 'Test Company Name')"
Set oConnection = CreateObject("ADODB.Connection")
Set oRecordset = CreateObject("ADODB.Recordset")
oConnection.Open sConnectString
oConnection.Execute (sSQL)
sMsg = sMsg & "Record Added"
MsgBox sMsg
Set oRecordset = Nothing
Set oConnection = Nothing
End Sub
UPDATE:
I added:
sConnectString = "DSN=Quickbooks Data;OLE DB Services=-2;"
sSQL = "Insert into customer (Name, CompanyName) Select Num, Description From TestTable"
Set oConnection = CreateObject("ADODB.Connection")
Set oRecordset = CreateObject("ADODB.Recordset")
oConnection.Open sConnectString
oConnection.Execute (sSQL)
sMsg = sMsg & "Record Added"
MsgBox sMsg
But I get the error "Invalid table name: TestTable" how can I get this SQL script to see my Access table?
To add the form's current record values to your queries, you just pull the value (e.g. Me.txtDescription). I would recommend you use the ADODB.Command object, so you can parameterize your SQL and avoid SQL injection:
Option Explicit
Const adOpenStatic As Integer = 3
Const adLockOptimistic As Integer = 3
Const CONNECTION_STRING As String = "DSN=Quickbooks Data;OLE DB Services=-2;"
Private Sub Command10_Click()
Dim cn As ADODB.Connection
Dim cmd As ADODB.Command
Dim param As ADODB.Parameter
Dim sMsg As String
' set up ADODOB connection
Set cn = New ADODB.Connection
cn.Open CONNECTION_STRING
' set up ADODB command object
Set cmd = New ADODB.Command
cmd.ActiveConnection = cn
' note that we're using unnamed parameters,
' with the ? symbol
cmd.CommandText = _
"INSERT INTO customer " & _
"(Name, CompanyName) " & _
"VALUES " & _
"(?, ?)"
' add form values as command parameters
cmd.Parameters.Append cmd.CreateParameter( _
Type:=adVarChar, _
Size:=255, _
value:=Me.txtNumber)
cmd.Parameters.Append cmd.CreateParameter( _
Type:=adVarChar, _
Size:=255, _
value:=Me.txtDescription)
' now that we have the command set up with its params,
' we can just execute it:
cmd.Execute
sMsg = "Record Added"
MsgBox sMsg
Set param = Nothing
Set cmd = Nothing
cn.Close: Set cn = Nothing
End Sub
Of course, you'll have to use the actual names of your textboxes.
Also, please notice a couple of additional modifications I a made to your original code:
I have Option Explicit defined. You may already have this in your code, but if not, you need it. That way, any variables used have to be declared. For more information, see the Microsoft Docs
I moved your ADODB constants outside your sub. Ideally, you'd either use early binding and add the ADODB library reference (so you don't need to define these yourself), or add them in a separate module, so you can use them in any of your forms.
I also added your connection string as a constant outside your sub. Again, this should probably be in a separate module (e.g. modConstants) you can easily refer to from anywhere in your project.
I improved the indentation of your code.
I explicitly added the types for your declarations (Dim sSQL as String rather than just Dim sSQL). Note that if you declare a variable without a type, it defaults to the Variant type, instead of String (which you want). See Microsoft Docs for more information.

How to limit the number of results in an LDAP query

I'm programming an Office 2010 Word template that needs to interact with the Active Directory, to retrieve some user info. The user filling out the template can give some input for the search that is used to retrieve the AD info.
I'm using the ADODB Recordset and Command to setup my query:
Public Function GetActiveDirectoryInfo(searchString As String) As Object
Dim adoCommand, adoConnection, strBase, strFilter, strAttributes
Dim strQuery, adoRecordset
'remove user input asteriks
searchString = Replace(searchString, "*", "")
' Setup ADO objects.
Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Open "Active Directory Provider"
Set adoCommand.ActiveConnection = adoConnection
strBase = "<LDAP://" & GLOBAL_LDAP_USER_OU & ">"
' Filter on user objects.
strFilter = "(&(objectCategory=person)(objectClass=user)(|(sn=" & searchString & "*)(cn=" & searchString & "*)))"
' Comma delimited list of attribute values to retrieve.
strAttributes = ACTIVE_DIRECTORY_FIELDS
' Construct the LDAP syntax query.
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";OneLevel"
adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 10
adoCommand.Properties("Timeout") = 30
adoCommand.Properties("Cache Results") = False
'adoCommand.Properties("Maximum Rows") = 10 'error: read only
On Error GoTo err_NoConnection
' Run the query.
Set adoRecordset = adoCommand.Execute
Set foundItems = adoRecordset
Debug.Print "Found : " & foundItems.RecordCount & " records"
Exit Function
err_NoConnection:
'in case of error: return <nothing>
Debug.Print Err.description
Set GetActiveDirectoryInfo = Nothing
End Function
THis function is part of a User Class in MS Word.
My question: how to prevent the user from retrieving thousands of records? I have been reading about paging, but that seems more like a network-load-thing than actually limiting the search results that will eventually get back to me.
I've tried several things but with no results. For instance setting the MaxRecords or Maximum Rows properties. Both give an error (non existing property for the first, and read only error for the second).
ANy ideas anyone.
MS Office 2010 dutch
ADO Objects 6.0
Thanx!
Unfortunately ADO MaxRecord that limits the number of search results is not implemented for ADsDSObject. The link https://support.microsoft.com/kb/269361 details the workaround to solve the issue.

Display GAL in Excel to get Alias or Email Adress

I am trying to make use of information found in THIS POST.
I have 2 issues:
the following line hangs indefinitly. FIXED--- its just hidden and has no task bar item, simple search told me how to bring to to the front
strAddress = objWordApp.GetAddress(, strCode, False, 1, , , True, True).GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3A00001E")
I need to return something I can use in a TO line, so an ALIAS or a full email address. I have tested this code in WORD and it works perfrectly (remvoing the references to word) except I cannot get the correct information I need. When I pull I get an exchange distinguished name.. I need to convert this somehow to an alias or email address
/o=corperation/ou=administration/cn=my.name
BACKGROUND: the code in the previously mentioned post displayed the OUTLOOK GAL so a user can search/select a contact from it. I want to use the GAL because it can handle the 200,000+ records and includes distrobution lists.
SOFTWARE: This has to function within the OFffice 2010 suite. I don't need any backwords compatibility and future proofing is a minimal concern at the moment.
END RESULT: I basically just want a user to be able to search for a recipient and have that address end up in a cell.
Any hints would be greatly appreciated.
Method One: Using the GetAddress function
Does the following code still hang indefinitely for you?
Set objWordApp = CreateObject("Word.Application")
InputBox "", "", objWordApp.GetAddress(, "<PR_EMAIL_ADDRESS>", False, 1, , , True, True)
Method Two: If you know the username grab it directly
You can maybe use the LDAP directly to get this information:
Public Function GetUserEmail(ByVal userName As String) As String
Const ADS_SCOPE_SUBTREE = 2
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = "SELECT mail FROM 'LDAP://DC=something,DC=co,DC=uk' WHERE objectCategory='user' AND sAMAccountName='" & userName & "'"
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst
If Not objRecordSet.EOF Then
GetUserEmail = objRecordSet.Fields("mail").Value
Else
GetUserEmail = vbNull
End If
End Function
Method Three: Create your own searchable form
You could create your own UserForm to bring back a list of users from the LDAP. You could choose the fields you want to search on and then allow the user to click that item to grab the email address. It's a little messy, but it should load a bit faster, since it'll only search on a name more than 3 characters long.
In this example above I created a query which searches on the givenName or sn field of the LDAP:
Private Sub txtSearch_Change()
If Len(txtSearch) > 3 Then
queryString = txtSearch
Const ADS_SCOPE_SUBTREE = 2
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = "SELECT givenName, sn, mail FROM 'LDAP://DC=something,DC=co,DC=uk' WHERE objectCategory='user' AND (givenName = '*" & queryString & "*' Or sn = '*" & queryString & "*')"
Set objRecordset = objCommand.Execute
lvResults.ListItems.Clear
Do Until objRecordset.EOF
Set li = lvResults.ListItems.Add(, , objRecordset.Fields("givenName").Value)
li.SubItems(1) = objRecordset.Fields("sn").Value
If Not IsNull(objRecordset.Fields("mail")) Then
li.SubItems(2) = objRecordset.Fields("mail").Value
End If
objRecordset.MoveNext
Loop
End If
End Sub
Notes
Something to note, is you will need to change the LDAP string to your company domain controller. For example LDAP://DC=something,DC=co,DC=uk.
If you don't know this you can find it out by doing:
Set sysinfo = CreateObject("ADSystemInfo")
MsgBox sysinfo.userName
Note you only want to take the DC= parts.
A list of all attributes can be found here: http://www.computerperformance.co.uk/Logon/LDAP_attributes_active_directory.htm

Querying Excel by multiple users - Need suggestion

I am seeking one suggestion on how to build an excel macro for below requirement. Request you to provide your valuable comments in EXCEL Only.
Scenario
I have one spreadsheet "Product Master" that contains all the product details.
(i.e. Product ID,Product Name,Product Type,Quantity etc etc)
I am designing a UserForm using excel VBA where anyone can fetch all the details of a product based on its Product ID. Now the product-master sheet where all the product details is present will get updated on a daily basis. And each user should be able to update any details in that sheet based on his requirement.
Questions/Doubts
How do I design my system? I mean where should I put my "Product-Master" spreadsheet so that it can be accessed by multiple users. What I am thinking is to put product-masster on a shared_drive so that all can access that sheet through VBA userform. I will provide excel VBA userform macro to everyone in my office & they will query that sheet present in shared drive. does this seem ok?
Does excel provide facility to Query data from sheet present in shared-drive & update it when required. And I want this to be queried by multiple users at a time.
I know there are other products/technologies that provides better solution than EXCEL. But I want the solution in EXCEL ONLY.
I would appreciate it if anyone can provide his/her valuable comments on this. Let me know in case you need any details.
Thanks you.
Here are some example functions getting data from/posting data to MS Access (took me awhile to dig these up, hah!). This uses a Reference to the Microsoft DAO 3.6 Object Library and will only work with legacy .mdb files, not accdb (because the mdb driver is 100x faster and doesn't have a memory leak.)
Const DBPath As String = "Full\Database\Path"
Function GET_ACCESS_DATA(DBPath, SQL) As Object
Dim dbConn As Object
Dim dbRS As Object
Dim SQL As String
On Error GoTo ErrorHandler
SQL = "Sql Query"
'Set up database connection string
Application.StatusBar = "Connecting to Database..."
'Open database connection
Set dbConn = OpenDatabase(DBPath)
'Run the query
Application.StatusBar = "Running Query..."
Set dbRS = dbConn.OpenRecordset(SQL, DAO.dbOpenForwardOnly, DAO.RecordsetOptionEnum.dbReadOnly)
'If no rows returned, display error message and exit
If dbRS.RecordCount = 0 Then
Application.StatusBar = "Running Query...Error"
MsgBox "There are no records for the selected criteria.", vbInformation, "Refresh Data"
Application.StatusBar = "REFRESHING DATA PLEASE WAIT.."
Exit Function
End If
'returns DAO Recordset with the data
Set GET_ACCESS_DATA = dbRS
'A recordset can either be looped through or pasted to a spreadsheet with the Worksheet.Range.CopyFromRecordset method
'Error trap here
End Function
Function POST_TO_ACCESS() As Boolean
POST_TO_ACCESS = False
errormod = "TRACKING"
On Error GoTo ERROR_TRAP:
'START CONTROLS
Application.StatusBar = "Formatting Data"
St_Timer = Timer 'start connection timer
Dim cn As DAO.Database
Set cn = DAO.OpenDatabase(DBPath)
En_Timer = Timer 'get connection time
'SetKey Parameters
UserNM = Replace(User_Name(), Chr(39), "")
CompNm = Environ("COMPUTERNAME")
Elapsed_Time = En_Timer - St_Timer
SQL = "INSERT INTO TBL_TRACKING " & _
"(UserNM) " & _
" VALUES ('" & UserNM & "')"
cn.Execute SQL
cn.Close
'END CONTROLS
Application.StatusBar = False
POST_TO_ACCESS = True
'error trap here
End Function
Function User_Name()
'This just gets the LDAP username of whoever is logged in. Useful for tracking. Not guarenteed to work for your Active Directory :)
Dim WshNetwork
Dim objAdoCon, objAdoCmd, objAdoRS
Dim objUser, objRootDSE
Dim strDomainDN, strUserName, strUserFullName
strUserFullName = ""
Set WshNetwork = CreateObject("WScript.Network")
strUserName = WshNetwork.UserName
Set objRootDSE = GetObject("LDAP://rootDSE")
strDomainDN = objRootDSE.Get("defaultNamingContext")
Set objAdoCon = CreateObject("ADODB.Connection")
objAdoCon.Open "Provider=ADsDSOObject;"
Set objAdoCmd = CreateObject("ADODB.Command")
Set objAdoCmd.ActiveConnection = objAdoCon
objAdoCmd.CommandText = _
"SELECT ADsPath FROM 'LDAP://" & strDomainDN & "' WHERE " & _
"objectCategory='person' AND objectClass='user' AND " & _
"sAMAccountName='" & strUserName & "'"
Set objAdoRS = objAdoCmd.Execute
If (Not objAdoRS.EOF) Then
Set objUser = GetObject(objAdoRS.Fields("ADsPath").Value)
objUser.GetInfoEx Array("displayName"), 0
strUserFullName = objUser.Get("displayName")
Set objUser = Nothing
User_Name = strUserFullName
Else
End If
Set objAdoRS = Nothing
Set objAdoCmd = Nothing
objAdoCon.Close
Set objAdoCon = Nothing
Set objRootDSE = Nothing
Set WshNetwork = Nothing
End Function