Remove strange characters from .mdb field names - python-3.x

I have a large number of .mdb-files (as in Microsoft Access db-files). The first field (or column) is supposed be named say MyField1. However the files are corrupted so that the actual field name is \ufeffMyField1 or in other words there is 0xFEFFprepended to the actual field name.
I'm trying to copy the field in question from \ufeffMyField1 to NewField using the pyodbc-command
cursor.execute("UPDATE MyTable SET NewField=" + colname + ";")
where colnameis the errouneous field name (assume that NewFieldalready exists)
The value of colname is fetched with pyodbc using something like
rows = cur.columns(table='MyTable')
for row in rows:
if("MyField1" in row.column_name):
colname=row.column_name
Executing the UPDATE... command yields a driver error that the MaxLocksPerFile Ms Access parameter is too low, as described here https://support.microsoft.com/en-us/help/815281/-file-sharing-lock-count-exceeded-error-message-during-large-transacti.
However I've increased the MaxLocksPerFile parameter with several orders of magnitude while opening only a single file in the program, so it seems it is not the actual problem.
Note that without problem I can open the file in MS Access and rename the field in the gui. I have not found a way to see in the gui that the field is incorrectly named, supposedly since the extra bits doesn't match common encodings.
Finally this leads me to my question: how can I pass raw bytes as pyodbc-commands?
Alternatively please suggest in comments if you have other ways to solve the real-world problem of removing the extra characters? (as in clean out all non-ASCII characters from the field names)

If you want to use DAO, then it is pretty simple. You can modify the following code to find and loop thru a folder of all databases.
Sub Rename_First_Field()
Dim dbs As DAO.Database
Dim tdf As DAO.TableDef
Dim fld As DAO.Field
Set dbs = OpenDatabase("C:\....\SomeDB.mdb")
For Each tdf In dbs.TableDefs ' Spin thru all tables in this database
Set fld = tdf.Fields(0) ' Grab the first field
Debug.Print tdf.Name & vbTab & "|" & vbTab & fld.Name
fld.Name = "MyField1" ' Rename to 'MyField1'
Next tdf 'Move to next table
Set tdf = Nothing
Set dbs = Nothing
End Sub

Related

Extract file names from a File Explorer search into Excel

This has been bugging me for while as I feel I have few pieces of the puzzle but I cant put them all together
So my goal is to be able to search all .pdfs in a given location for a keyword or phrase within the content of the files, not the filename, and then use the results of the search to populate an excel spreadsheet.
Before we start, I know that this easy to do using the Acrobat Pro API, but my company are not going to pay for licences for everyone so that this one macro will work.
The windows file explorer search accepts advanced query syntax and will search inside the contents of files assuming that the correct ifilters are enabled. E.g. if you have a word document called doc1.docx and the text inside the document reads "blahblahblah", and you search for "blah" doc1.docx will appear as the result.
As far as I know, this cannot be acheived using the FileSystemObject, but if someone could confirm either way that would be really useful?
I have a simple code that opens an explorer window and searches for a string within the contents of all files in the given location. Once the search has completed I have an explorer window with all the files required listed. How do I take this list and populate an excel with the filenames of these files?
dim eSearch As String
eSearch = "explorer " & Chr$(34) & "search-ms://query=System.Generic.String:" & [search term here] & "&crumb=location:" & [Directory Here] & Chr$(34)
Call Shell (eSearch)
Assuming the location is indexed you can access the catalog directly with ADO (add a reference to Microsoft ActiveX Data Objects 2.x):
Dim cn As New ADODB.Connection
Dim rs As New ADODB.Recordset
Dim sql As String
cn.Open "Provider=Search.CollatorDSO;Extended Properties='Application=Windows'"
sql = "SELECT System.ItemNameDisplay, System.ItemPathDisplay FROM SystemIndex WHERE SCOPE='file:C:\look\here' AND System.Kind <> 'folder' AND CONTAINS(System.FileName, '""*.PDF""') AND CONTAINS ('""find this text""')"
rs.Open sql, cn, adOpenForwardOnly, adLockReadOnly
If Not rs.EOF Then
Do While Not rs.EOF
Debug.Print "File: "; rs.Collect(0)
Debug.Print "Path: "; rs.Collect(1)
rs.MoveNext
Loop
End If
Try using the next function, please:
Function GetFilteredFiles(foldPath As String) As Collection
'If using a reference to `Microsoft Internet Controls (ShDocVW.dll)_____________________
'uncomment the next 2 lines and comment the following three (without any reference part)
'Dim ExpWin As SHDocVw.ShellWindows, CurrWin As SHDocVw.InternetExplorer
'Set ExpWin = New SHDocVw.ShellWindows
'_______________________________________________________________________________________
'Without any reference:_____________________________________
Dim ExpWin As Object, CurrWin As Object, objshell As Object
Set objshell = CreateObject("Shell.Application")
Set ExpWin = objshell.Windows
'___________________________________________________________
Dim Result As New Collection, oFolderItems As Object, i As Long
Dim CurrSelFile As String
For Each CurrWin In ExpWin
If Not CurrWin.Document Is Nothing Then
If Not CurrWin.Document.FocusedItem Is Nothing Then
If left(CurrWin.Document.FocusedItem.Path, _
InStrRev(CurrWin.Document.FocusedItem.Path, "\")) = foldPath Then
Set oFolderItems = CurrWin.Document.folder.Items
For i = 0 To oFolderItems.count
On Error Resume Next
If Err.Number <> 0 Then
Err.Clear: On Error GoTo 0
Else
Result.Add oFolderItems.item(CLng(i)).Name
On Error GoTo 0
End If
Next
End If
End If
End If
Next CurrWin
Set GetFilteredFiles = Result
End Function
Like it is, the function works without any reference...
The above function must be called after you executed the search query in your existing code. It can be called in the next (testing) way:
Sub testGetFilteredFiles()
Dim C As Collection, El As Variant
Set C = GetFilteredFiles("C:\Teste VBA Excel\")'use here the folder path you used for searching
For Each El In C
Debug.Print El
Next
End Sub
The above solution iterates between all IExplorer windows and return what is visible there (after filtering) for the folder you initially used to search.
You can manually test it, searching for something in a specific folder and then call the function with that specific folder path as argument ("\" backslash at the end...).
I've forgotten everything I ever knew about VBA, but recently stumbled across an easy way to execute Explorer searches using the Shell.Application COM object. My code is PowerShell, but the COM objects & methods are what's critical. Surely someone here can translate.
This has what I think are several advantages:
The query text is identical to what you wouold type in the Search Bar in Explorer, e.g.'Ext:pdf Content:compressor'
It's easily launched from code and results are easily extracted with code, but SearchResults window is available for visual inspection/review.
With looping & pauses, you can execute a series of searches in the same window.
I think this ability has been sitting there forever, but the MS documentation of the Document object & FilterView method make no mention of how they apply to File Explorer.
I hope others find this useful.
$FolderToSearch = 'c:\Path\To\Folder'
$SearchBoxText = 'ext:pdf Content:compressor'
$Shell = New-Object -ComObject shell.application
### Get handles of currenlty open Explorer Windows
$CurrentWindows = ( $Shell.Windows() | Where FullName -match 'explorer.exe$' ).HWND
$WinCount = $Shell.Windows().Count
$Shell.Open( $FolderToSearch )
Do { Sleep -m 50 } Until ( $Shell.Windows().Count -gt $WinCount )
$WindowToSerch = ( $Shell.Windows() | Where FullName -match 'explorer.exe$' ) | Where { $_.HWND -notIn $CurrentWindows }
$WindowToSearch.Document.FilterView( $SearchBoxText )
Do { Sleep -m 50 } Until ( $WindowToSearch.ReadyState -eq 4 )
### Fully-qualified name:
$FoundFiles = ( $WindowToSearch.Document.Folder.Items() ).Path
### or just the filename:
$FoundFiles = ( $WindowToSearch.Document.Folder.Items() ).Name
### $FoundFIles is an array of strings containing the names.
### The Excel portion I leave to you! :D

VBA multiple users to update same table in a shared MS-Access database using DAO

Excel crashes, VBA raises Error 3218 “Could Not Update” Record Locking Errors when multiple users try to update same table in a shared MS-Access database using DAO.
I have a special configuration like this: a MS-Access database located in shared network folder, multiple user connect to update that database using VBA DAO build on Excel file. The VBA code in each Excel file is the same. The problem happens when there are 2 users click on update button at the same time. User Excel file turn hanging, or showing error 3218 "Could not update".
Sub ExportToAccess()
Dim oSelect As Range, i As Long, j As Integer, sPath As String
'tblSuppliers.Active
Set oSelect = Application.InputBox("Range", , Range("A1").CurrentRegion.Address, , , , , 8)
Dim oDAO As DAO.DBEngine, oDB As DAO.Database, oRS As DAO.Recordset
sPath = "\\sharedfolder\Database.accdb"
Set oDAO = New DAO.DBEngine
Set oDB = oDAO.OpenDatabase(sPath)
Set oRS = oDB.OpenRecordset("tblSuppliers")
For i = 2 To oSelect.Rows.Count 'skip label row
oRS.AddNew
For j = 1 To oSelect.Columns.Count 'Field(0) is Auto#
oRS.Fields(j) = oSelect.Cells(i, j)
Next j
oRS.Update
Next i
oDB.Close
MsgBox ("Updated Done!")
End Sub
I know my configuration is not good for database application, however I have to stick with this for a while. Could you please advise any solutions to avoid error when multiple users update Access database in this case ? Is there a way to detect if database is being updating by others and script to wait until that process to finish first. Any technical solution for this issue is welcome!
Thank you!
You need some type of flag to tell if anyone is updating the table or not. Examples of what this flag can be:
An Excel file cell (that is probably the easiest in your case; if multiple excel files are used, just link to the one cell)
A field in an Access table (even a table with a single field and a single record dedicated just for that)
A (text) file in your shared drive (the flag can be the content of the file or even whether the file exists or not)
Then your update process would be:
- Check the flag, if set, loop until flag is cleared
- Set the flag
- Update the table
- Clear the flag
You will probably also need some way for the users (or just you) to clear the flag manually, in case something else goes wrong while updating the table and the flag gets stuck raised.
Well , this is not probably the most elegant solution
but you may create a field in a table , and ask for it before working with the table
something like this :
Set LockedStatus= oDB.OpenRecordset("mycontroltable")
if LockedStatus("lockedSuppiers")=False then
oDB.Execute"update mycontroltable set lockedSuppiers=true"
Set oRS = oDB.OpenRecordset("tblSuppliers")
For i = 2 To oSelect.Rows.Count 'skip label row
oRS.AddNew
For j = 1 To oSelect.Columns.Count 'Field(0) is Auto#
oRS.Fields(j) = oSelect.Cells(i, j)
........
......
oDB.Execute"update mycontroltable set lockedSuppiers=false"
end if

CopyFromRecordset from Access to Excel is out of Order

Very frustrating results from:
ws.Range(myExcelTable).CopyFromRecordset rs
' myExcelTable is a named table with assigned range
The pull for rs (the recordset, is a select all, doesn't matter if it is done this way or with field names, it is the same result)
cmdSQL2 = "SELECT * FROM " & myAccessTable
rs.Open cmdSQL2, conn
For an unknown reason it copies records into excel in this order (using pID to show ordering):
3-8
0-2
9-number of records
I have tried several range methods and the result is the same.
I will "hand bomb" them here to cut out the extra work I performed in building range strings.
ws.Range("S4").CopyFromRecordset rs
ws.Range("S4:BP258").CopyFromRecordset rs
This behavior does not appear if the table you are copying from Access has only one Field (column) and the Table in Excel has a single header (column), but it is appearing for me now in this multi-field table.
I feel this is happening when the recordset is actually created somehow and not on the "paste" or assignment side (Excel).
'*************************************************************************
Problem Solved by this SO Contributor (will leave here until it is placed as an answer):
https://stackoverflow.com/users/7296893/erik-a
Using ORDER BY in the sql sommand:
cmdSQL2 = "SELECT * FROM " & myAccessTable & " ORDER BY " & primaryKey
From immediate window (removes the variables so you can see the command better for those new to such things:
SELECT * FROM County_Directory ORDER BY [pID]
'*************************************************************************

Lotus Notes - Export emails to plain text file

I am setting up a Lotus Notes account to accept emails from a client, and automatically save each email as a plain text file to be processed by another application.
So, I'm trying to create my very first Agent in Lotus to automatically export the emails to text.
Is there a standard, best practices way to do this?
I've created a LotusScript Agent that pretty much works. However, there is a bug - once the Body of the memo exceeds 32K characters, it starts inserting extra CR/LF pairs.
I am using Lotus Notes 7.0.3.
Here is my script:
Sub Initialize
On Error Goto ErrorCleanup
Dim session As New NotesSession
Dim db As NotesDatabase
Dim doc As NotesDocument
Dim uniqueID As Variant
Dim curView As NotesView
Dim docCount As Integer
Dim notesInputFolder As String
Dim notesValidOutputFolder As String
Dim notesErrorOutputFolder As String
Dim outputFolder As String
Dim fileNum As Integer
Dim bodyRichText As NotesRichTextItem
Dim bodyUnformattedText As String
Dim subjectText As NotesItem
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
'INPUT OUTPUT LOCATIONS
outputFolder = "\\PASCRIA\CignaDFS\CUser1\Home\mikebec\MyDocuments\"
notesInputFolder = "IBEmails"
notesValidOutputFolder = "IBEmailsDone"
notesErrorOutputFolder="IBEmailsError"
'''''''''''''''''''''''''''''''''''''''''''''''''''''''
Set db = session.CurrentDatabase
Set curview = db.GetView(notesInputFolder )
docCount = curview.EntryCount
Print "NUMBER OF DOCS " & docCount
fileNum = 1
While (docCount > 0)
'set current doc to
Set doc = curview.GetNthDocument(docCount)
Set bodyRichText = doc.GetFirstItem( "Body" )
bodyUnformattedText = bodyRichText.GetUnformattedText()
Set subjectText = doc.GetFirstItem("Subject")
If subjectText.Text = "LotusAgentTest" Then
uniqueID = Evaluate("#Unique")
Open "\\PASCRIA\CignaDFS\CUser1\Home\mikebec\MyDocuments\email_" & uniqueID(0) & ".txt" For Output As fileNum
Print #fileNum, "Subject:" & subjectText.Text
Print #fileNum, "Date:" & Now
Print #fileNum, bodyUnformattedText
Close fileNum
fileNum = fileNum + 1
Call doc.PutInFolder(notesValidOutputFolder)
Call doc.RemoveFromFolder(notesInputFolder)
End If
doccount = doccount-1
Wend
Exit Sub
ErrorCleanup:
Call sendErrorEmail(db,doc.GetItemValue("From")(0))
Call doc.PutInFolder(notesErrorOutputFolder)
Call doc.RemoveFromFolder(notesInputFolder)
End Sub
Update
Apparently the 32KB issue isn't consistent - so far, it's just one document that starts getting extra carriage returns after 32K.
With regards the 32Kb thing, instead of this:
Set bodyRichText = doc.GetFirstItem( "Body" )
... you might want to consider iterating all "Body" fields in the email document. When dealing with large amounts of rich text, Domino "chunks" said content into multiple rich text fields. Check some documents you're processing: you may well see multiple instances of the "Body" field when you look at document properties.
I'm not sure what is causing the 32K bug, but I know there are lots of limitations in the order of 32K or 64K within Lotus Notes, so perhaps you're running into one of those. I can't imagine what would add extra CR/LFs. Perhaps you could try using the GetFormattedText method on the NotesRichTextItem class and see if it fares better?
It's more complicated, but you might also be able to use the NotesRichTextNavigator class to iterate through all the paragraphs in the memo, outputting them one at a time. Breaking up the output that way might eliminate the CR/LF problem.
Lastly I always suggest Midas' LSX for dealing with rich text in Lotus Notes. They sell an add-on that gives you much more control over rich text fields.
As for best practices, one that comes to mind when I read your code is the looping construct. It is more efficient to get the first document in a view, process it, and then get the next doc and check whether it is equal to Nothing. That sets the loop to run through the view in index order, and eliminates the need to search through the index to find the Nth document each time. It also saves you from maintaining a counter. The gist is as follows:
Set doc = curview.GetFirstDocument()
While Not (doc Is Nothing)
'Do processing here...
Set doc = curview.GetNextDocument(doc)
Wend
The external eMail most likely comes in as MIME. So you could check the document.hasMime and then use the mime classes to get to the content. Then you don't have a 64k limit. Samples are in the help - or reply if you want code.

MS Access & Excel: Turning a query with dynamic parameters into something useful

I got stuck in the problem beneath, because I don´t use Access or Excel much and I have some basic programming language. So here's the deal:
I just made a fairly simple database in MS Access (2007) with a nice query to retrieve data, depending on which parameters you pass. In Excel (2007), I have this big 'template' which basically has parameters for the query. These parameters change per column & per row!
Perhaps superfluously, e.g.
column A contains paramA (10 different options)
column B contains paramB (8 different options)
column C contains paramC (2 different options)
What I'd like to do is to fill this template with dynamic data from Access, minding the continously changing parameters.
e.g.
column D contains Query (ParamA, ParamB, ParamC)
Best way to go I think is to make a (inline?) function that retrieves results from the query, also passing the parameters depending on the relative cell position. And this function is then copied as a normal inline excel function (like: SUM()).
I just don't know how to call /execute an MS Access query from inside an Excel Macro function.
Could someone help me with it? Thank you very much in advance!
A few notes.
Dim cn As Object
Dim rs As Object
''See: http://www.connectionstrings.com/access
strFile = "C:\Docs\AccessDB.mdb"
strCon = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & strFile _
& ";User Id=admin;Password=;"
Set cn = CreateObject("ADODB.Connection")
Set rs = CreateObject("ADODB.Recordset")
cn.Open strCon
strSQL = "SELECT SomeField, OtherField FROM SomeTable " _
& "WHERE SomeText='" & Range("A1") & "'"
rs.Open strSQL, cn
s = rs.GetString
MsgBox s
'' Or
Sheets("Sheet2").Cells(2, 1).CopyFromRecordset rs
To add to Remou's answer also see
Modules: Sample Excel Automation - cell by cell which is slow and
Modules: Transferring Records to Excel with Automation
Late binding means you can safely remove the reference and only have an error when the app executes lines of code in question. Rather than erroring out while starting up the app and not allowing the users in the app at all. Or when hitting a mid, left or trim function call.
This also is very useful when you don't know version of the external application will reside on the target system. Or if your organization is in the middle of moving from one version to another.
For more information including additional text and some detailed links see the "Late Binding in Microsoft Access" page

Resources