I am working on an Excel file that connects to a SQL database to update parameters on a piece of production machinery based on an analysis of part quality data. The file has been working in production for some time but a recent hacking attack on my company has caused us to review the security of all of our systems.
The old file version used ADODB with a hard-coded user name a password with narrowly defined database permissions. This meant any quality or engineering employee could run the Excel utility without being explicitly given server/database access. With our new security review, I wanted to switch the file to use windows authentication but I ran into some issues. It seems that using windows authentication through ADODB requires not just a database user, the automation team has permissions to create, but also a server login mapped to the database user which only an IT admin can create. I also worry that adding a large number of server-level users is not a wonderful idea.
The actual piece of production equipment uses a system-level ODBC connection with window authentication. This connection works fine with a database user without a server login. Because ODBC doesn't seem to need a server login it would make the management of the Excel file users much simpler and would allow the team in charge of the equipment and its database to handle it without IT.
Unfortunately, I have been unable to figure out how to execute queries and get results in VBA with an ODBC connection. I have tried Workbook.Connections("ODBCName").CommandText with an ODBC connection stored in the workbook but I don't see a way to directly get the result. The only option I can see is to map the query to cells in a hidden table and read them in VBA but this seems hackish. Also, I'm not sure how this would work for the results of queries other than SELECT like INSERT or UPDATE.
Any help would be greatly appreciated. An example of my old code is here, there are more routines that make similar queries:
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strSQL = "SELECT * FROM Table.dbo.PART_INSPECTION_LOG WHERE PART_NUM = 'PartNo' AND DATA_TIME = " & dataTime
rs.Open strSQL, cn
If (rs.BOF And rs.EOF) = True Then
linear_err = (Sheets("Adjustment").Range("E24").Value)
rotational_err = (Sheets("Adjustment").Range("N24").Value)
strSQL = "INSERT INTO Table.dbo.PART_INSPECTION_LOG (PART_NUM, TOOL_ID, USER_NAME, DATA_TIME, LINEAR_ERROR, ROTATIONAL_ERROR) VALUES ('PartNum', 'ToolNum', "
strSQL = strSQL & "'" & Application.UserName & "', "
strSQL = strSQL & dataTime & ", "
strSQL = strSQL & linear_err & ", "
strSQL = strSQL & rotational_err & ")"
cn.Execute strSQL
End If
cn.Close
Related
In my MS Access FrontEnd connected to an SQL Server, I'm using a combination of pass-through queries and "normal" ODBC connection via file-DSN (for "easy bound forms") for my forms.
Within VBA functions and Subs I use some ADO connections to directly change data in the tables.
I need pass-through queries for some forms as I need to use DISTINCT keywords on tabels with MEMO / NVARCHAR(max) fields.
Currently I'm using the pass-through query to get the data into my forms like that:
strSQL = "SELECT DISTINCT tbl_Changes.Change_Nr, Title, Comment FROM [tbl_Changes] inner JOIN tbl_Parts ON tbl_Changes.Change_Nr = tbl_Parts.Change_Nr " & _
"WHERE '" & strProject & "' IN (" & strAllProjects & ") " & _
"ORDER BY tbl_Changes.Change_Nr DESC"
Dim qdf As DAO.QueryDef, rst As DAO.Recordset
Set qdf = CurrentDb.CreateQueryDef("")
qdf.Connect = Application.TempVars("tempvar_StrCnxn")
qdf.sql = strSQL
qdf.ReturnsRecords = True
'Debug.Print strSQL
Set rst = qdf.OpenRecordset
Set Forms![frm_ChangePartsOverview].Recordset = rst
Forms![frm_ChangePartsOverview].Requery
'No rst.Close to have the data still in the Form!
Set qdf = Nothing
But I think this is not the correct approach to do it, or is it?
Regarding your concerns in the comments:
rst.Close is redundant in VBA, the garbage collector takes care of closing recordsets and only in very rare cases will you need to close them manually.
In theory, you could have a Form_Close or Form_Unload handler to close the recordset, but there's no need.
DAO also caches database connections and reuses them when needed (something which some users use to keep passwords out of connection strings, since if a connection is established with the password, it's cached and can be used in linked tables without specifying it again, but this also causes bugs, for example with SET IDENTITY_INSERT ON which DoCmd.TransferDatabase uses).
That said, there are alternate approaches, but they all come with advantages and disadvantages.
I am currently trying to connect to an Oracle database from VBA. Below is the code I am running in VBA to try to establish the initial connection. Unfortunately, this code produces an “ORA-01017” error mentioning that my username/password is invalid. I have SQL Developer installed and when I try to login with the same credentials/info, I successfully connect. I also tried instead setting UID = userid in the VBA code below and the connection didn’t throw an error, but I can’t query any tables. The same thing happens if I try the same in SQL developer.
After doing some reading, it looks like the info in the brackets is my proxy user id and I need to somehow specify that separately from my UID, but I can’t figure out how I would go about that. Has anyone else had any experience with this or have any guidance? Let me know if there is any additional details I can provide.
Sub Ora_Connection()
Dim con As ADODB.Connection
Dim rs As ADODB.recordset
Set con = New ADODB.Connection
Set rs = New ADODB.recordset
StrCon = "Driver={Microsoft ODBC for Oracle}; Uid=userid[database name];Pwd=UserPWD;" & _
"CONNECTSTRING=(DESCRIPTION=" & _
"(ADDRESS=(PROTOCOL=TCP)" & _
"(HOST=HostName)(PORT=1521))" & _
"(CONNECT_DATA=(SERVICE_NAME=XXXX)));"
con.Open (StrCon)
End Sub
It doesn't look like its possible to connect to proxy user with ADODB .
One another alternative is to use alter session set current_schema = Proxy_user run this after you open the session.
The only trouble with this is that the grants should be there for the actual user, here proxy user just allows us to avoid using identifiers.
I am just starting to move our Access DB to SQL Server and am having trouble.
I have a stored procedure that successfully returns rows to an ado recordset.
When I try to bind the rs containing the results of the stored procedure to the Access form, Access crashes without displaying any error messages. I'm on O365 32b and SQL Server 2019.
Here's the code:
Dim sSQL As String, rs As ADODB.Recordset
1 sSQL = "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
2 ADOConn.ConnectionString = conADO
4 ADOConn.Open
6 Set rs = New ADODB.Recordset
7 rs.CursorLocation = adUseClient
8 rs.Open sSQL, ADOConn
10 Set Me.Recordset = rs ' Access crashes here
. . .
Any help would be greatly appreciated!
tia.
SR
Ok, are you previous using ADO, or are you just introducing this?
In most cases, you are better off to just use a view. (replace the access query with a linked view), and then continue useing client side where clauses or filters (access will ONLY pull down the rows you request). So linked views are often a better choice and much less work (in fact, even existing filter for a open report etc. will work and only critera matching the were clause records are pulled.
And in most cases, i don't introduce ADO.
So for a PT query, I often do this:
dim rs as DAO.RecordSet
with CurrentDb.queryDefs("qryPt")
.SQL = "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
set rs = .OpenRecordSet
end with
So, above assumes you have a pt query called qryPt. This also means that you never deal with or worry about connection strings in code. The pt query has the connection. (and your re-link code now can re-link tables and pt queries).
I ONLY suggest the above as a FYI in case that you introducing ADO for calling store procedures, and the rest of the application was previous DAO. If the application was previous DAO, then leave it alone, and use above approach for your PT queries - even code that needs to call store procedures.
Access tends to try and parse the query text to get filters/sorts/etc to work, and if it isn't a plain syntax error but isn't Access SQL either, strange things tend to happen, mostly crashes.
Try adding a comment up front to make sure Access knows not to parse:
sSQL = "-- Access no parse pls" & vbCrLf & "Exec usp_TaskStatusWidget " & Me.Tag & ",0"
The content of the comment is not relevant, of course, its purpose is to immediately cause a syntax error when Access tries to parse it as Access SQL (which doesn't have comments)
I'm trying to use VBA from Excel 2007 to execute a query in an Access 2007 DB as a stored procedure. The query retrieves data from a few MS SQL tables linked via an ODBC DSN. The authentication for the external tables is done with Windows NT authentication with Trusted_Connection=Yes
I have tried connecting to the linked tables with both a User and System DSN - either way works fine when I run the query from Access 2007.
When I try using VBA in Excel the data that should be coming from the Windows authenticated tables isn't retrieved. I am able to retrieve data from a set of linked MS SQL tables that are using a stored UID.
Here is the connection string I'm using:
strDB = xlWb.Path & "\database\dbQueries.accdb"
conn.Open "Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=" & strDB & ";"
And here is the command that's only partially successful (with data from the UID authenticated tables):
With cmd2
.ActiveConnection = conn
.CommandText = "qryAppendtblOutput"
.CommandType = adCmdStoredProc
.Parameters.Append cmd2.CreateParameter("[Start Date]", adDBDate, adParamInput, , startDate)
End With
cmd2.Execute
Thanks very much in advance!
I have been able to resolve this issue by switching to the Access ODBC driver instead of the OLE DB provider. I changed my connection string to:
strDB = xlWb.Path & "\database\dbQueries.accdb"
conn.Open "Driver={Microsoft Access Driver (*.mdb, *.accdb)};" & _
"Dbq=" & strDB & ";" & _
"Trusted_Connection=Yes;"
Then, all I had to do was adjust the syntax of the underlying queries in Access to meet the requirements of this driver (I had to replace all double quotes with singles).
Thanks very much to all who reviewed my question :)!
I am new to VBScript. Can someone please help me to connect to SQL Server 2005 (OLEDB) using VBScript and update a table in the database.
My server: sql14\qw
My database: fret
User id: admin
Pasword: pass
Table name: lookup
Const DB_CONNECT_STRING = "Provider=SQLOLEDB.1;Data Source=sql14\qw;Initial Catalog=fret;user id ='admin';password='pass'"
Set myConn = CreateObject("ADODB.Connection")
Set myCommand = CreateObject("ADODB.Command" )
myConn.Open DB_CONNECT_STRING
Set myCommand.ActiveConnection = myConn
myCommand.CommandText = "UPDATE lookup SET Col1 = 'Hello'"
myCommand.Execute
myConn.Close
Tested using Integrated Windows Security, did not test with SQL Login.
Easy stuff, actually. First, you have to define the connection and recordset that you'll be using:
Set AdCn = CreateObject("ADODB.Connection")
Set AdRec = CreateObject("ADODB.Recordset")
After that, it's all about the connection string:
connstr="Provider=SQLOLEDB.1;Data Source=" & server & ";Initial Catalog=" & database & ";user id = '" & uid & "';password='" & pwd & "'"
The string consists of a few parts:
Provider: the type of connection you are establishing, in this case SQL Server.
Data Source: The server you are connecting to.
Initial Catalog: The name of the database.
user id: your username.
password: um, your password. ;)
Note that if you want to use your Windows login credentials and are running the script locally then you can substitute the following for the username and password fields:
Integrated Security=SSPI
Of course, this won't work if you're using your script on a website, so you'll have to explicitly use username and password. Then, making sure your connection is open, you just open the recordset, hand over the SQL query, and capture the returned data as an array.
SQL="Select ##version as name"
AdCn.Open connstr
AdRec.Open SQL, AdCn,1,1
queryReturn=Adrec("name")
Just remember that the data is being returned as an array (often two dimensional, where the results you want are actually in the second dimension of the array!) and that you may need to either Trim to kill blank spaces at the end of results or parse the results with string functions like Left. Personally, I always Trim() a result while assigning it to a variable as I've been bitten by hidden blanks more times than I can count.