Occasionally at ActiveState we get a bug report from a Windows user that Komodo thinks the current user’s name is “ReleaseEngineer.MACROVISION”, and is disappointed that it can’t find a directory like “c:/Users/ReleaseEngineer.MACROVISION/AppData/Roaming” on the user’s Vista box. The reporter insists that there’s no one of that name at his company, and we don’t have anyone with that name wandering around our company. If you google for “ReleaseEngineer.MACROVISION”, you’d get the idea that plenty of companies have an account for someone with that name, given the number of hits Google reports.
There was always a workaround — to set the KOMODO_USERDATADIR environment variable, so we didn’t push on this. But it does make for a disappointing initial experience. Because we couldn’t reproduce the bug here, we hadn’t escalated it.
Then yesterday someone not only ran into this bug (a similar one, which complained about a bogus username of “gooemp”), but he was running a registry tracker at the same time as Komodo, and saw that we were reading the registry key “HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders”. The reporter pointed out that there’s a subkey in that folder called “!Do not use this registry key”, with the value of “Use the SHGetFolderPath or SHGetKnownFolderPath function instead”. I’ve never seen a deprecation notice hidden in the registry, and obviously our software was ignoring that key as well every time it went in there, but our customer didn’t.
From there it was relatively straightforward to follow the advice. In fact, there was a comment in the registry-walking code stating that it was using the registry instead of the shell command, with a comment I found cryptic at first stating that “SHGetFolderPath” isn’t always available. Digging around a bit more, I found that SHGetFolderPath function was deprecated as of Vista, in favor of the newer SHGetKnownFolderPath. I bit the bullet, and spent a few hours writing code that made dynamic calls, using LoadLibrary, GetModuleHandle, and GetProcAddress, to make the recommended calls to the shell functions. Hopefully no one else will run into the ReleaseEngineer problem after this.
So what about the registry? Well, my first clue is that Macrovision is the company that acquired InstallShield about seven years ago. The second clue is in the nature of MSI technology. An MSI file is a set of database tables and files, and InstallShield is in some ways nothing more than a tool that helps you fill in these database tables, so that users can install (and uninstall) your products. Many of the fields in these tables can’t be filled in until install time. So it would make sense for those tables to have obvious placeholders in them. And “ReleaseEngineer.Macrovision” sounds more professional than “Joe User”. Is there a bug where the replacement fails to happen at runtime?
My next question is why the registry is getting overwritten. I can’t see why any installer would need to update Shell Folder data. But it’s easy enough to write custom actions in an MSI file, and these custom actions can easily overwrite Registry entries. Is there malice here, or just a common mistake? I can only speculate here (that’s all I’ve been doing in these last couple of paragraphs), but given the frequency we’ve had this bug reported, and the google hits, I’m wondering if InstallShield itself is the victim of a virus. Not necessarily a direct hit, but where some other virus writer is using the name “Macrovision” as a smokescreen.
Okay, enough speculation. Here’s the C code I replaced the registry lookup with:
static int getSetting(
size_t nBufferLength, /* size of buffer */
char* lpBuffer /* path buffer */
) {
HMODULE hShell32;
bool freeHandle = false;
hShell32 = GetModuleHandle(TEXT("shell32.dll"));
int rc = 1;
if (!hShell32) {
hShell32 = LoadLibrary(TEXT("shell32.dll"));
freeHandle = true;
}
fprintf(stderr, "hShell32:%p\n", hShell32);
lpBuffer[0] = 0;
if (hShell32) {
// Try the Vista "SHGetKnownFolderPath" first, and then
// fall back to the Win2K "SHGetFolderPath"
// Don't bother with GetVersionEx(), because the user
// might have installed a service pack for the older OS that
// added "SHGetKnownFolderPath".
//
// This falls in the category of "test for the feature, not the OS
// version".
// Vista, W7, future versions...
const char *funcName = "SHGetKnownFolderPath";
HRESULT hr;
// typedef HRESULT (WINAPI * SHGetKnownFolderPathFn)(REFKNOWNFOLDERID rfid,
typedef HRESULT (WINAPI * SHGetKnownFolderPathFn)(const GUID *rfid,
DWORD dwFlags,
HANDLE hToken,
PWSTR *ppszPath);
SHGetKnownFolderPathFn shGetFolderPathFunc;
shGetFolderPathFunc =
(SHGetKnownFolderPathFn) GetProcAddress(hShell32, funcName);
if (shGetFolderPathFunc) {
PWSTR path;
GUID guid_RoamingAppData = FOLDERID_RoamingAppData;
hr = shGetFolderPathFunc(&guid_RoamingAppData,
0, NULL, &path);
if (hr == S_OK) {
size_t numPrinted = wcstombs(lpBuffer, path, nBufferLength);
lpBuffer[nBufferLength - 1] = 0;
HMODULE hOLE32 = GetModuleHandle(TEXT("ole32.dll"));
bool freeOLEHandle = false;
if (!hOLE32) {
hOLE32 = LoadLibrary(TEXT("ole32.dll"));
freeOLEHandle = true;
}
if (hOLE32) {
typedef void (WINAPI * CoTaskMemFreeFn)(LPVOID pv);
CoTaskMemFreeFn coTaskMemFreeFunc;
coTaskMemFreeFunc = (CoTaskMemFreeFn) GetProcAddress(hOLE32, "CoTaskMemFree");
if (coTaskMemFreeFunc) {
coTaskMemFreeFunc(path);
}
if (freeOLEHandle) {
FreeLibrary(hOLE32);
}
}
}
}
if (!lpBuffer[0]) {
typedef HRESULT (WINAPI* SHGetFolderPathFn)(HWND hwndOwner,
int nFolder,
HANDLE hToken,
DWORD dwFlags,
char *pszPath);
SHGetFolderPathFn shGetFolderPathFunc;
funcName = "SHGetFolderPathA"; // ASCII version
shGetFolderPathFunc =
(SHGetFolderPathFn) GetProcAddress(hShell32, funcName);
if (shGetFolderPathFunc) {
char path[MAX_DATA + 1];
hr = shGetFolderPathFunc(NULL, CSIDL_APPDATA,
NULL, 0, path);
if (hr == S_OK) {
if (strlen(path) >= nBufferLength) {
return 0
} else {
strcpy(lpBuffer, path);
}
} else {
return 0;
}
}
}
if (freeHandle) {
FreeLibrary(hShell32);
}
}
return 1;
}