Directly access older MDB files - c++

I have a C++ (MFC) application. My customer needs me to add a feature to read from some data files.
These data files are MDB files. They appear to be for older versions of Microsoft Access (probably versions prior to the year 2007, but I haven't been able to confirm that).
I'm up for reading these files directly if I can find sufficient documentation on their format. I'm also up to hearing about older ODBC or other similar tools that would work with these older files. But a requirement is that we don't need to install a bunch of additional software. Ideally, I can do this all from C++.
Could I get some suggestions on how I might access this data? Where is the format documents and what might be some other options than reading it directly myself?
Note: This article presents the layout of an MDB file; however, it does not seem to match the content of the files I'm working with.

I use CDatabase with the Microsoft Jet driver to connect to MDB databases.
I locate the correct driver like this:
// We now iterate the JET drivers list and locate a valid MDB driver
CString CPTSDatabase::GetJETDriver(bool bAccDbMode)
{
CString strDriver;
CString strName, strNameLower, strValue;
CString strDefaultDriver = _T("Microsoft Access Driver (*.mdb)");
CString strDBType = _T("(*.mdb)");
CStringArray aryStrDrivers;
TCHAR szBuf[2001];
WORD cbBufMax = 2000;
WORD cbBufOut;
TCHAR *pszBuf = szBuf;
if (SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut))
{
#ifdef _WIN64
strDefaultDriver = _T("Microsoft Access Driver (*.mdb, *.accdb)");
strDBType = _T("(*.mdb, *.accdb)");
#else
if (bAccDbMode)
{
strDefaultDriver = _T("Microsoft Access Driver (*.mdb, *.accdb)");
strDBType = _T("(*.mdb, *.accdb)");
}
#endif
do
{
strName = CString(pszBuf);
strNameLower = strName;
strNameLower.MakeLower();
if (strNameLower.Find(strDBType) != -1)
{
aryStrDrivers.Add(strName);
if (strName.CollateNoCase(strDefaultDriver) == 0)
{
strDriver = strName;
break;
}
}
pszBuf = _tcschr(pszBuf, _T('\0')) + 1;
} while (pszBuf[1] != _T('\0'));
if (strDriver.IsEmpty() && aryStrDrivers.GetSize() > 0)
{
// Try and use the first MDB driver we found
strDriver = aryStrDrivers.GetAt(0);
}
}
// Make a note of the driver
AfxGetApp()->WriteProfileString(_T("Options"), _T("JET Connection Driver"), strDriver);
return strDriver;
}
If you know which driver you want to use, the just use that. Then open your database:
// Opens the database (gets closed in destructor)
void CPTSDatabase::OpenDatabase(bool bAccDbMode, CString strPassword)
{
CString strDBConnectString;
CString strDriver;
if (m_dbDatabase.IsOpen())
return;
if (DatabaseExist(m_strDatabasePath))
{
// AJT v10.5.0 Take into account the DB mode
strDriver = GetJETDriver(bAccDbMode);
// Take into account the DB password (decrypted!)
strDBConnectString.Format(_T("Driver={%s};DBQ=%s;Pwd=%s"),
strDriver, m_strDatabasePath,
CPTSTools::DecryptDatabasePassword(strPassword));
m_dbDatabase.OpenEx(strDBConnectString, CDatabase::noOdbcDialog);
}
}
But a word of warning .... And I use my own computer as an example ....
My computer is Windows 10 64 bit ...
It has:
a 64 bit ACCDB Microsoft Database driver
a 32 bit MDB Microsoft Database driver
There is no 64 bit MDB driver. So if you want to work with MDB databases make sure you build your application in 32 bit mode.

All you need to read MS Access .mdb files is MDAC (Microsoft Data Access Components) installed on target PC/server.
All modern OSs like Vista/Windows 7/8/8.1/10 have this component pre-installed for your convinience. If you have to target XP you can download this component from MS site. Also the InstallShield comes with MDAC 2.7 merge module if you need to build installer for your app.
You can simply use the standard set of MFC classes to work with databases:
CDatabase database;
CString sDsn;
CString sFile = _T("D:\\Projects\\DB\\Test.mdb");
// Build ODBC connection string
sDsn.Format(_T("ODBC;DRIVER={%s};DSN='';DBQ=%s"), _T("MICROSOFT ACCESS DRIVER (*.mdb)"), sFile);
try
{
// Open the database
database.Open(NULL, FALSE, FALSE, sDsn);
// Allocate the recordset
CRecordset recset( &database );
// Build the SQL statement
CString sSqlString = _T("SELECT Field1, Field2 from MyTable");
// Execute the query
recset.Open(CRecordset::forwardOnly, sSqlString, CRecordset::readOnly);
// Loop through each record
while( !recset.IsEOF() )
{
// Copy each column into a variable
CString sField1;
CString sField2;
recset.GetFieldValue(_T("Field1"), sField1);
recset.GetFieldValue(_T("Field2"), sField2);
// goto next record
recset.MoveNext();
}
// Close the database
database.Close();
}
catch(CDBException* e)
{
// If a database exception occured, show error msg
AfxMessageBox(_T("Database error: ") + e->m_strError);
}

Related

electron: ui and backend processes accessing the same log file on Windows

Goal
My electron-based app uses a C++ backend, which keeps a log file. I'd love to show the file content on a page of my Electron frontend.
The macOS version works as expected. I simply use node.js fs and readline libraries and to read the file on the fly, and then insert the parsed text into innerHTML.
Problem
However, on Windows, the log file seems to be locked by the backend while the CRT fopen calls use appending mode "a". So node.js keeps getting exception
EBUSY: resource busy or locked open '/path/to/my.log'
To make it worse, I use a thirdparty lib for logging and it's internal is not that easy to hack.
Code
Here is the Electron-side of code
function OnLoad() {
let logFile = Path.join(__dirname, 'logs', platformDirs[process.platform], 'my.log');
let logElem = document.querySelector('.log');
processLineByLine(logFile, logElem);
}
//
// helpers
//
async function processLineByLine(txtFile, outElement) {
const fileStream = fs.createReadStream(txtFile);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
// Note: we use the crlfDelay option to recognize all instances of CR LF
// ('\r\n') in input.txt as a single line break.
for await (const line of rl) {
// Each line in input.txt will be successively available here as `line`.
console.log(`Line from file: ${line}`);
outElement.innerHTML += line + '<br>';
}
}
Here is the backend side of code
inline bool OpenLogFile(FILE** ppLogFile) {
TCHAR logPath[MAX_PATH];
DWORD length = GetModuleFileName(NULL, logPath, MAX_PATH);
bool isPathValid = false;
#if (NTDDI_VERSION >= NTDDI_WIN8)
PathCchRemoveFileSpec(logPath, MAX_PATH);
HRESULT resPath = PathCchCombine(logPath, MAX_PATH, logPath, TEXT("my.log"));
isPathValid = (resPath == S_OK);
#else
PathRemoveFileSpec(logPath);
LPWSTR resPath = PathCombine(logPath, logPath, TEXT("my.log"));
isPathValid = (resPath != NULL)
#endif
if (!isPathValid)
return false;
errno_t res = _wfopen_s(ppLogFile, logPath, L"a");
if (res != 0) {
wprintf(TEXT("Error: Failed to open log file: %s"), GetOSErrStr().c_str());
}
return res == 0;
}
Question
Is this an inherent problem with my architecture?
Should I forget about accessing the log file from frontend/backend processes at the same time?
I thought about using a message queue for sharing logs between the frontend and backend processes, but that'd make logging more complex and bug prone.
Is there an easy way to have the same logging experience as with macOS?
Solved it myself.
I must use another Win32 API _wfsopen that provides more sharing options.
In my case, the following change is sufficient
*ppLogFile = _wfsopen(logPath, L"a+", _SH_DENYWR);
This answer helped.

How come GetDefaultCommConfig fails on windows 10

I use the following code to verify that a serial port name is valid on the computer:
typedef std::pair<StrAsc const, bool> port_pair_type;
typedef std::list<port_pair_type> port_pairs_type;
port_pairs_type pairs;
StrBin config_buffer;
config_buffer.fill(0,sizeof(COMMCONFIG));
while(!pairs.empty())
{
port_pair_type pair(pairs.front());
pairs.pop_front();
if(!pair.second)
{
// we need to get the default configuration for the port. This may
// require some fudging on the buffer size. That is why two calls
// are being made.
uint4 config_size = config_buffer.length();
StrUni temp(pair.first);
COMMCONFIG *config(reinterpret_cast<COMMCONFIG *>(config_buffer.getContents_writable()));
config->dwSize = sizeof(COMMCONFIG);
rcd = GetDefaultCommConfigW(
temp.c_str(), config, &config_size);
if(!rcd && config_buffer.length() < config_size)
{
config_buffer.fill(0, config_size);
config = reinterpret_cast<COMMCONFIG *>(config_buffer.getContents_writable());
config->dwSize = sizeof(COMMCONFIG);
rcd = GetDefaultCommConfigW(
temp.c_str(),
reinterpret_cast<COMMCONFIG *>(config_buffer.getContents_writable()),
&config_size);
}
// if the call succeeded, we can go ahead and look at the
// configuration structure.
if(rcd)
{
COMMCONFIG const *config = reinterpret_cast<COMMCONFIG const *>(
config_buffer.getContents());
if(config->dwProviderSubType == PST_RS232)
port_names.push_back(pair.first);
}
else
{
OsException error("GetDefaultCommConfig Failed");
trace("\"%s\"", error.what());
}
}
else
port_names.push_back(pair.first);
}
On windows 10, when trying to confirm a serial port that uses usbser.sys, the call to GetDefaultCommConfig() is failing and the error code returned by GetLastError() is 87 (invalid parameter). As I am aware, the usbser.sys driver has been rewritten on windows 10 and I suspect that this is a problem with that driver. Does anyone else have an idea of what might be going wrong?
This had been a bug in usbser.sys and was fixed with the Windows 10 Update KB3124262 from 27.01.2016.
The Microsoft employee explained:
The COM port name in the HKLM\HARDWARE\DEVICEMAP\SERIALCOMM registry is not NULL terminated.
Related discussion on MSDN
Because of Windows 10's update policies this issue should not appear in the future anymore.
When you call GetDefaultCommConfigW the second time you probably need to config->dwSize to the new size the structure. Eg:
config->dwSize = config_size;

GetFIleVersionInfoSize() succeeds but returns incorrect size

I am trying to get the version of a file. I want to look at the version number of this file to determine which OS is installed on a non-booted drive (I'll actually be doing this from a Win PE environment and trying to determine if the main drive has Windows XP or Windows 7 installed). Anyway, I have the following
wchar_t *fileName;
fileName = new wchar_t[255];
lstrcpy(fileName, hdds[HardDriveIndexes::SystemDrive].driveLetter.c_str());
lstrcat(fileName, L"Windows\\System32\\winload.exe");
TCHAR *versionInfoBuffer;
DWORD versionDataSize;
if (versionDataSize = GetFileVersionInfoSize(fileName, NULL) != 0)
{
versionInfoBuffer = new TCHAR[versionDataSize];
BOOL versionInfoResult = FALSE;
versionInfoResult = GetFileVersionInfo(fileName, NULL, versionDataSize, versionInfoBuffer);
if (versionInfoResult == FALSE)
{
wprintf(L"The last error associated with getting version info is: %d\n", GetLastError());
}
}
else
{
wprintf(L"The last error associated with gettting version info size is: %d\n", GetLastError());
}
The problem is that GetFileVersionInfoSize() succeeds but always returns 1 as the size. This causes GetFileVersionInfo() to fail with error 122. So far I have only tested this on a Windows 7 system. There is another function GetFileVersionInfoSizeEx() that works as expected, but it is only supported from Vista onwards. I would like to keep XP support if possible (some of our old Win PE images are still based on XP).
Is GetFileVersionInfoSize() deprecated and I somehow can't find that information, am I using it incorrectly, etc.?
The problem isn't with the call, it's with your assignment; you need parens around it:
if ( ( versionDataSize = GetFileVersionInfoSize(fileName, NULL) ) != 0)
What you had written assigns the value of the expression size != 0, which is 1 for true.

How does _stat() under Windows exactly work

In my code I try to get the permissions for a file with _stat(). Currently I want to run it under Windows. The method is as follows:
bool CFile::Private::checkPermissions(std::string sFilename, CFile::EOpenmode iOpenmode)
{
std::string sErrMsg = "";
bool bResult = true;
struct _stat buf;
int iResult = 0;
// Get data associated with "crt_stat.c":
iResult = _stat( sFilename.c_str(), &buf );
// Check if statistics are valid:
if( iResult != 0 )
{
switch (errno)
{
case ENOENT:
sErrMsg = "File: " + sFilename + " not found.";
break;
case EINVAL:
sErrMsg = "Invalid parameter to _stat(filename, &buf).";
break;
default:
/* Should never be reached. */
sErrMsg = "Unexpected error in _stat(filename, &buf).";
}
throw std::runtime_error(sErrMsg);
}
else
{
if((iOpenmode & CFile::Read) && (!(buf.st_mode & S_IREAD)))
{
bResult = false;
}
if((iOpenmode & CFile::Write) && (!(buf.st_mode & S_IWRITE)))
{
bResult = false;
}
}
return bResult;
}
The only way to get 'false' for permission is to set the file's attribute 'read only'. Instead of this, set the security properties of the user (reject writing and reading) will get 'true' for checkPermissions(...). How to check both, the attributes and the user permissions for Windows?
Rumo
_stat is a function that is not native to Windows. It's a helper function to ease the porting of UNIX programs to Windows. But the UNIX file model just doesn't apply to Windows, so not all fields make sense. For instance, Windows has real ACL's, not rwx bits. There's just no way to fit all the ACL information in st_mode.
If you want to test ACL permissions, the proper way is to just try: call CreateFile() and check GetLastError(). Trying to get file permissions up front is not reliable as they can change at any time.
If we're talking about the same _stat() it's pretty clear from this MSDN article exactly what it does. Basically, you supply it a path to a file in question and a pointer to a _stat struct and it will dump the permissions to the struct if it returns 0.
The example C++ code in the article is pretty good.
As for testing user permissions, IsUserAnAdmin() does the job pretty well. You may be able to use this MSDN article for a different approach.
I hope this helps!

How to get Version of the software?

I am working on getting the version of the Software which is installed on the Computer. I have implemented the logic for reading the Uninstall hive of registry, but i have observed that some of the software are not having version entries in the Uninstall hive of the registry. But i want to show the version of those softwares also.
Can some one help me out in this regard?
Supplying a software version to the registry of Windows is voluntary. If the developer of the software you're looking at chose to not display the version there or was simply unaware of such possibility, I am unable to point you to any other location he would choose to use or be aware of. In fact, the software might not even have a version number/name.
Ask yourself this: Where else is the Version detail of the software available if not in the registry? If it is available somewhere else other than registry, ask us if you could get that detail using C++. I guess this would be a better approach to solve your issue.
Added the information below since OP is looking for file version
See if the below code could help you.
CString GetFileVersionInfo(CString strFile, CString strProperty)
{
int rc;
UINT nLen;
DWORD nSize;
DWORD dwHandle = 0;
CString strBuffer;
CString strValue;
CString strBlock;
void *lpPropertyBuffer;
struct LANGANDCODEPAGE
{
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
nSize = GetFileVersionInfoSize(strFile.GetBuffer(strFile.GetLength()), &dwHandle);
::GetFileVersionInfo(strFile.GetBuffer(strFile.GetLength()), 0, nSize, strBuffer.GetBuffer(nSize));
// Read the list of languages and code pages.
if (VerQueryValue(strBuffer.GetBuffer(strBuffer.GetLength()), "\\VarFileInfo\\Translation", (LPVOID *) &lpTranslate, &nLen))
{
strBlock.Format("\\StringFileInfo\\%04x%04x\\%s",
lpTranslate->wLanguage,
lpTranslate->wCodePage,
strProperty);
rc = VerQueryValue(strBuffer.GetBuffer(strBuffer.GetLength()), strBlock.GetBuffer(nSize), &lpPropertyBuffer, &nLen);
if (rc != 0 && nLen > 0)
{
strncpy(strValue.GetBuffer(nLen + 1), (char *) lpPropertyBuffer, nLen);
strValue.ReleaseBuffer(nLen);
}
}
return strValue;
}
user version.lib while linking and you might need winver.h for compilation. You can call the function like this
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
AfxMessageBox(GetFileVersionInfo("shell32.dll", "ProductVersion"));
}
return nRetCode;
}
I'd say look at the file version information. And you might find this article useful on how the Add/Remove Programs dialog gets its information.
If the software developers chose not to add version information into Uninstall information, then there's no reliable way to get it.
You can try to find where application is installed. But even if you have the path, the application can consist of several .exe files which can have different versions and product names. If you add DLLs into the candidate list for getting version information, your results become even less predictable.