How to find broken queries in Access? - ms-access

How do I find broken queries in access.
i.e. Queries that might have broken because the underlying table was deleted or the name of the column in the table changed?
Is there an easy way -- rather than just opening each query running and checking if something has gone wrong?

Here are a few notes that may be of interest, depending on your version of Access.
See: GetDependencyInfo Method [Access 2003 VBA Language Reference]
Do not forget that Track name AutoCorrect info is not a good thing, for the most part, but can be useful in certain circumstances.
Dim dinf As DependencyInfo
For j = 0 To CurrentData.AllQueries.Count - 1
Set dinf = CurrentData.AllQueries(j).GetDependencyInfo
For i = 0 To dinf.Dependencies.Count - 1
''Missing alias, query or table, as far as I can tell
If dinf.Dependencies.Item(i).Name Like "MISSING:*" Then
Debug.Print CurrentData.AllQueries(j).Name _
& " " & dinf.Dependencies.Item(i).Name
End If
Next
Next
You may need to update dependencies:
Application.CurrentProject.UpdateDependencyInfo
This will require a save.

Related

What is a good way to sanitize mysql in an old classic ASP site?

I am the maintainer (but thankfully not the creator) of a very old, very large and very badly written classic ASP site for an electronics manufacturer.
Security is a joke. This is the only thing done to sanitize input before throwing it into the mouth of MySQL:
Function txtval(data)
txtval = replace(data,"'","'")
txtval = trim(txtval)
End Function
productid = txtval(Request.QueryString("id"))
SQL = "SELECT * FROM products WHERE id = " & productid
Set rs = conn.execute(SQL)
Because of that, the site is unfortunately (but perhaps not surprisingly) victim of SQL injection attacks, some of them succesful.
The simple means taken above is not nearly enough. Nor is using Server.HTMLEncode. Escaping slashes doesn't help either as the attacks are quite sophisticated:
product.asp?id=999999.9+UnIoN+AlL+SeLeCt+0x393133353134353632312e39,0x393133353134353632322e39,0x393133353134353632332e39,0x393133353134353632342e39,0x393133353134353632352e39,0x393133353134353632362e39
The url above (an arbitrary attempt taken from the access log) gives the folling response from the site:
Microsoft OLE DB Provider for ODBC Drivers error '80004005'
[MySQL][ODBC 5.3(w) Driver][mysqld-5.1.42-community]
The used SELECT statements have a different number of columns
/product.asp, line 14
This means that the injection made it through but in this case did not succeed in getting any data. Others do, however.
The site consists of hundreds of ASP files with spaghetti code summing up to many thousands of lines without much structure. Because of that it is not an option to go for parameterized queries. The work would be enormous and error prone as well.
One good thing though is that all input parameters in the code are consistently passed through the txtval function, so here is a chance to do it better by augmenting the function. Also, since all SQL calls are done with conn.execute(SQL) it is quite straightforward to search and replace with eg. conn.execute(sanitize(SQL)) so here is a chance to do something about it too.
Given the circumstances, what are my options to prevent or at least minimize the risc of SQL injection?
Any input is much appreciated.
Updates:
1.
I do understand that parameterized queries is the correct way to handle the problem. I use that myself when I create websites. But given the way the site is built and the size of it, it will take 1-2 months to modify, test and debug it. Even if that is what we end up with (which I doubt) I need to do something right now.
2.
The replacement with the html entity is not a typo. It replaces single quote with its html entity. (I didn't make the code!)
3.
In the specific example above, using CInt(id) would solve the problem, but it could be anything, not only numerical inputs.
UPDATE 2:
Ok, I know that I am not asking for the correct solution. I knew that from the start. That's why I wrote "Given the circumstances".
But still, filtering inputs for mysql keywords like select, union etc would at least make it better. Not good, but a little bit better. And this is what I am asking for, ideas to make it a little bit better.
Although I appreciate your comments, telling me that the only good option is to use parameterized queries doesn't really help. Because I know that already :)
I wouldn't give up on parameterized queries. They are the single best tool you can use to protect yourself from SQL Injection. If your plan is to replace all of these calls:
conn.execute(SQL)
to these calls:
conn.execute(sanitize(SQL))
then you're already looking at modifying each interaction with SQL (BTW, don't forget Command.Execute() and Recordset.Open(), which may also be used to run SQL statements). And since you're already planning on changing these calls, consider calling a custom function to run the statement. For example, replace:
set rs = conn.execute(SQL)
with:
set rs = MyExecute(SQL)
and then use your custom function to set up a proper parameterized query using a Command object instead. You'll need to cleverly parse the SQL statement in this custom function. Identify the values in the where clause, determine their type (perhaps you can query the table schema), and add parameters accordingly. But it can be done.
You can also take this opportunity to sanitize the input. Use a RegExp object to quickly strip [^0-9\.] from numeric fields, for example.
But there's still the opportunity that you'll return a recordset from this function that will be used to write values directly to the page without being HTML-encoded first. That's a real concern, especially since it sounds like your site has already been targeted in the past. I wouldn't trust any data coming from your database. The only option I see here (that wouldn't involve touching every page) is to return a "clean", HTML-encoded recordset instead of the default one.
Unfortunately, you're still not out of the woods. XSS attacks can be done via QueryString parameters, cookies, and form controls. How safe are you going to feel after "fixing" the SQL Injection issues knowing that XSS is still a very real possibility?
My advice? Explain to your supervisor the security threats plaguing your site and convince him/her the need for a thorough review or a complete rewrite. It may seem like a lot of resources to throw at an "old, already-working website", but the moment someone defaces your website or truncates your database tables, you'll wish you invested the time.
This attack should only affect numeric values passed in your SQL.
There may or may not be a quick fix depending on whether the same txtval function is used for both numeric and string values (and others like date too).
If txtval is only used for numeric values (probably unlikely) then you could protected by adding single quotes around the value, eg:
Function txtval(data)
txtval = replace(data,"'","'")
txtval = "'" & trim(txtval) & "'"
End Function
If it is used for all value types then your only option might be to search through all the code and either:
1) Add single quotes to all numeric SQL, eg:
SQL = "SELECT * FROM products WHERE id = '" & productid & "'"
2) Create a new function just for sanitizing number values and then change all your queries to use that (not a quick fix), eg:
Function numval(data)
If IsNumeric(data) Then
numvalue = CDbl(data)
Else
numvalue = 0 'or NULL?
End If
End Function
And then change your queries, eg:
productid = numval(Request.QueryString("id"))
SQL = "SELECT * FROM products WHERE id = " & productid
Is there common code (ie. in an include file) that is used to open the database and create the conn variable used in your sample code?
If so, then you could just replace that code and create your own class with Open, Close and Execute functions (at least). You may need other methods too if they are used in your code.
That way you could effectively override the execute in lines like Set rs = conn.execute(SQL).
Eg:
Class MyDatabase
Private m_conn
Public Sub Open(connString)
Set m_conn = Server.CreateObject("ADODB.Connection")
m_conn.Open connString
End Sub
Public Sub Close()
m_conn.Close
Set m_conn = Nothing
End Sub
Public Function Execute(sql)
'Sanitize input here (sql), simple example just for this type of attack
If InStr(sql, "UnIoN AlL SeLeCt") <> 0 Then sql = ""
'return a RecordSet
Set Execute = m_conn.Execute(sql)
End Property
End Class
Then change your common conn declaration from... (eg)
Set conn = Server.CreateObject("ADODB.Connection")
...to...
Set conn = New MyDatabase
If you keep the txtval function I would also update it to escape slashes as well as single quotes, eg:
Function txtval(data)
txtval = Replace(Replace(strValue, "'", "''"), "\", "\\")
txtval = trim(txtval)
End Function
Hopefully something here might be of help.

Writing a single column from a table in MS Access VBA to .txt file

this is my first time posting a question here, but I almost always get good answers from searching this site. I'm trying to find out if there is a better way to speed up this process I have of writing a table column in Access to a .txt file. Here is the code I have, which works, but it's on the slow side. It takes about 45s to write around 7000 items.
lsFileName2 = "E:\DOI_Inventory2\SyncData\EquipUser.txt"
LiFileNumb = FreeFile
SysCmd acSysCmdSetStatus, "Updating User Equipment List"
Open lsFileName2 For Output As LiFileNumb
With rst
Do While Not .EOF
Write #LiFileNumb, ![EqUserFile]
.MoveNext
Loop
End With
dbs.Close
Close LiFileNumb
I'm fairly new to the IT field so any help would be greatly appreciated. Thanks
Just to add a note, the actual query is fine. I already checked that and it's pretty fast.
Create a query, save it with a name, and then use TransferText to export the query's data to your text file.
So assuming you have a SELECT query named qryExportMe like this which returns your table's column data correctly ...
SELECT EqUserFile
FROM YourTable;
... refer to this TransferText example and adapt it to fit your needs.
DoCmd.TransferText TransferType:=acExportDelim, _
Tablename:="qryExportMe", _
FileName:="E:\DOI_Inventory2\SyncData\EquipUser.txt", _
HasFieldNames:=True
Check the TransferText options at that linked page or from Access' built in help system.
Note you are not required to include the option names. I added them to help you keep track of which is which.
If this approach speeds up your export operation adequately, I think it will be because Access handles the task as a single set-based file write. Your recordset approach required Access to process one row at a time ... and one reason such approaches are called RBAR (row by agonizing row) is because they are often painfully slow.

MS Access 2010 Run-Time Error '2467'

I have a MS Access 2010 application that is linked to MS SQL 2008 in the back end.
When I run the application, I encounter a run-time error '2467' - The expression you entered refers to an object that is closed or doesn't exists.
When I select Debug, the line points to:
Forms!FRM_MAIN_HOME!FRM_SUB_EDIT.Form.RecordSource =
I've run the query in the SSMS and works fine.
I've looked for missing references, and there aren't any.
When I run the application with Shift to present the forms, I get a different error: Data Type mismatch in criteria expression. This time I don't have a debug option, but I suspect that this is the real issue.
One more thing: when I run this application on a different database, it loads with no errors.
Any help is much appreciated.
Thanks.
Forms!FRM_MAIN_HOME!FRM_SUB_EDIT.Form.RecordSource = _
"SELECT V.VendorNumber, V.VendorName, T.DocumentNumber, " _
& "T.DocumentDate, T.AmountInc, T.GSTClaim, T.GSTBatch " _
& "FROM TBL_VENDOR_MASTER AS V INNER JOIN TBL_GST_01_TRANSACTIONS AS T " _
& "ON V.AAVendorID = T.AAVendorID WHERE T.GSTBatch =" _
& Me.BATCH_NUM & " ORDER BY V.VendorName"
When I run this code in the SSMS, it works just fine. Also, when I select a different database, it works without any errors.
I am prepared to bet you are running this in the wrong event. You cannot use the Open event to refer to controls on a form. I am not sure why you are dynamically setting the subform recordsource.
The recordsource should be:
SELECT V.VendorNumber,
V.VendorName,
T.DocumentNumber,
T.DocumentDate,
T.AmountInc,
T.GSTClaim,
T.GSTBatch
FROM TBL_VENDOR_MASTER AS V
INNER JOIN TBL_GST_01_TRANSACTIONS AS T
ON V.AAVendorID = T.AAVendorID
ORDER BY V.VendorName
You should set the link fields like so:
Link child field : GSTBatch
Link master field : BATCH_NUM
The issue seems to be related to some memory issues as the tables are quite large (and my machine is not the fastest...), but the error messages that appeared while MS Access is running (every time I click on a field, a tab, or anything really, I'd get the same error, just without the Debug option), is related to a table I have that MS Access didn't like one of the fields I had there.
To troubleshoot this error, I loaded an older backup, and started updating some columns and testing MS Access, until I found the problem. Also, I added some indexes, and changed the default time-out for ODBC to 0 (unlimited) in the registry.
That seemed to fix this problem.

Cannot extract values from dictionary once set in VBA/Access 2003

I'm writing a script which pulls out some patient data and generates an XML export.
Each patient record has an associated doctor - but rather than repeat doctor details in each record, I figured I'd set the doctor ID in the patient record, and then include a list of doctors in a different section at the bottom of the document.
One thing I need to do is included a GUID for the doctor in the patient record, but the actual database relationship is a local non-unique ID. I figured the best way forward was to map the GUIDs in a list of local IDs using a dictionary.
Anyway, long story short, here is the bit that builds the require list:
While Not PatientRec.EOF
Set DoctorRec = MyDB.OpenRecordset("Select Lng_Key, Txt_GUID From Tbl_LU_DoctorDetail Where Lng_Key = " & PatientRec![Lng_Doctor])
While Not DoctorRec.EOF
If (IsNull(DoctorRec![Txt_GUID])) Then
DoctorRec.Edit
DoctorRec![Txt_GUID] = CreateGUID()
DoctorRec.Update
End If
DoctorList.Add DoctorRec![Lng_Key], DoctorRec![Txt_GUID]
' outputs something like '5:{03f50fe1-a0a4-4733-906a-771e22845ea6}
MsgBox (DoctorRec![Lng_Key] & ":" & DoctorList.Items(DoctorRec![Lng_Key]))
DoctorRec.MoveNext
Wend
Wend
' outputs nothing!
MsgBox (DoctorList.Item(5))
' but there is something in there???
MsgBox (DoctorList.count)
I've also tried casting the id to a string using CStr, but get the same result with DoctorList.Item("5")
Worse, when I try:
Dim v As Variant
For Each v In DoctorList.Keys
MsgBox (v & ":" & DoctorList.Item(v))
Next
I get the error:
Run-time error '3420':
Object invalid or no longer set.
Testing (and the helpfile) indicates that the Variant 'v' is not being set to anything from the Keys property, but the For Each is at least attempting on loop...
-- Update
I found a similar question by someone on vbforums: http://www.vbforums.com/showthread.php?t=622933
I tested with a hardcoded key and item:
DoctorList.Add 5, "String"
The For Each loop now runs once successfully, but then fails with the 3420 error on a second loop (even when it should have stopped on the first loop).
Found the problem - it appears that Dictionaries will happily use objects as keys, so when using the Dictionary.Add method you have to explicitly use the Value property of the field from the recordset:
DoctorList.Add DoctorRec![Lng_Key].Value, DoctorRec![Txt_GUID].Value
In regard to GUIDs, Access doesn't like them. Michael Kaplan wrote about this years and years ago. You might want to look into the StringFromGUID() and GUIDToString() functions.
And if there is not an external requirement that you use GUIDs, you should seriously consider getting rid of them entirely. They don't add anything at all that is necessary in 99.99% of Access applications.
Secondly, I've never used the scripting runtime's dictionary, but it really looks to me like it offers nothing you can't already get with a VBA custom collection. Can you outline what you're using it for, and how it is superior to the VBA collection? Also, why are you using early binding and not late binding?

Control Rights to Linked Tables in MS-Access

I'm afraid I already know the answer to this, but I'm checking with the community in case there's something I don't know about....
Is it possible to have the MS Access table linking feature control rights to linked tables? Secifically, can FE.accdb have read/write privileges on BE_A.accdb, but read-only links to BE_B.accdb (without making BE_B.accdb entirely R/O)?
Sort of.
Quoting David Fenton from a post earlier this year on another forum:
One way would be to remove the linked
tables, and create queries to replace
them (you could use the same name for
the query as the corresponding linked
tables), and use a connect string in
the query and set the recordset type
to snapshot, which will be read-only
by default.
I just recently had occasion to do this myself. I asked a related question about setting the querydef recordset type to snapshot via VBA here.
You can adapt the following code to do what you need:
Sub ReadOnlyLink(MDBPath As String, TblName As String, SrcTblName As String)
Dim q As DAO.QueryDef
Set q = CurrentDb.CreateQueryDef(TblName, "SELECT * FROM " & SrcTblName & _
" IN """ & MDBPath & """")
q.Properties.Append q.CreateProperty("RecordsetType", dbByte, 2)
End Sub
This isn't really an answer, but it's too long for comments. Thoughts sparked by the answer I accepted.
It seems that it should be possible to use this for user-level "security":
Set up a BE
Set up a distributable FE with deliberately broken links
in the FE startup, get user name (API calls)
Run user name through a Select Case, re-linking as either real table or querydef as appropriate. Might be even better to do it with a user name -> role lookup before the Select.
Hmmm. Have to think about that some more.... Not bulletproof, but not really intended to be. More a way of dividing responsibility for updates to the users that are the respective domain experts. Don't know if it's really justified.