I'm distributing a freeware product that reads and writes text files with a unique extension. I hoped that double-clicking such a file would automatically start the app.
While developing on Windows 7 Professional, I set up an association to open my files upon double-click, by right-clicking the file->Open With...->Choose Default Program...->Browse... followed by "Always use the selected program to open this type of file." Good. It did just what it needed to. I was going to ship my program with instructions for the users to do the same.
However, when I moved the location of the binary, I see the "Always use" is now grayed out/insensitive, so while I could browse to the new binary I couldn't make it default. Since I thought my users would have trouble with this too, I wanted to see if I could have installation or run of the program take care of the mapping.
I looked at Windows Installer for about 5 minutes before determining it was far more power and complexity than I needed (For my needs, a zip file would be sufficient except for this file mapping.)
So I took a look at having my program, at start-up, set up the mapping itself if it wasn't there already. (I know this would be very bad behavior if we were talking about a common file type such as .html or .jpg, but in this case its some .blahblah extension that surely no-one else uses for anything.)
Based on information at http://www.cplusplus.com/forum/windows/26987/ and http://msdn.microsoft.com/en-us/library/cc144148(v=vs.85).aspx I was able to have my program, at startup, open HKEY_CLASSES_ROOT\.blahblah and confirm (and change if needed) the default text to be an accurate description of the file (replacing some text that may have been created by default when I did the manual association last summer). However, when it came to creating HKEY_CLASSES_ROOT\firm.app.1\shell\open\command, my RegCreateKeyEx() wrapper that works fine to change the value of \.blahblah is now giving return code 5, apparently a lack of permission.
Upon further research it seems that the permissions model may cause all such requests to fail. Can anyone confirm or deny this? If confirm, is there a good reference I should study on the matter?
Otherwise, what are the suggestions? Should I bite the bullet and study Windows Installer? Or is there a way to get the permissions I need to edit the registry when my own software starts the first time?
Note I'm developing with Visual Studio 2008 on Windows 7 Professional, and although still an amateur Windows programmer I've been doing C++ since the '80s on Unix/Linux...
OK, I got it working and I'll share what I learned.
1) Decide a ProgID. It should be vender.app.versionnumber according to docs, but regedit shows that practically no vendor follows that rule. I did though.
2) Most of the MSFT docs on this topic talk about creating entries under HKEY_CLASSES_ROOT, but I found important info on http://msdn.microsoft.com/en-us/library/cc144148(v=vs.85).aspx:
Important considerations about file types include: The
HKEY_CLASSES_ROOT subtree is a view formed by merging
HKEY_CURRENT_USER\Software\Classes and
HKEY_LOCAL_MACHINE\Software\Classes In general, HKEY_CLASSES_ROOT is
intended to be read from but not written to. For more information, see
the HKEY_CLASSES_ROOT article.
3) to have the association show up without rebooting, you must call SHChangeNotify(). (This threw me, because even when I had the right code, the results didn't show up correctly in Explorer.)
Here's the code I ended up with. If I go through with REGEDIT and delete all mention of .moselle (my extention) and MoselleIDE (my application) then run my program once by hand, I get the click-to-open behavior, file icon becomes the same as the app, etc. Note the code uses a logging function, and it also verbosely reports which type of success it has: 1) variable already correct, 2) variable changed, 3) variable didn't exist.
void RegSet( HKEY hkeyHive, const char* pszVar, const char* pszVa
lue ) {
HKEY hkey;
char szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof( szValueCurrent );
int iRC = RegGetValue( hkeyHive, pszVar, NULL, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize );
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if ( iRC != ERROR_SUCCESS && !bDidntExist )
AKS( AKSFatal, "RegGetValue( %s ): %s", pszVar, strerror( iRC ) );
if ( !bDidntExist ) {
if ( dwType != REG_SZ )
AKS( AKSFatal, "RegGetValue( %s ) found type unhandled %d", pszVar, dwType );
if ( strcmp( szValueCurrent, pszValue ) == 0 ) {
AKS( AKSTrace, "RegSet( \"%s\" \"%s\" ): already correct", pszVar, pszValue );
return;
}
}
DWORD dwDisposition;
iRC = RegCreateKeyEx( hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, NULL, &hkey, &dwDisposition );
if ( iRC != ERROR_SUCCESS )
AKS( AKSFatal, "RegCreateKeyEx( %s ): %s", pszVar, strerror( iRC ) );
iRC = RegSetValueEx( hkey, "", 0, REG_SZ, (BYTE*) pszValue, strlen( pszValue ) + 1 );
if ( iRC != ERROR_SUCCESS )
AKS( AKSFatal, "RegSetValueEx( %s ): %s", pszVar, strerror( iRC ) );
if ( bDidntExist )
AKS( AKSTrace, "RegSet( %s ): set to \"%s\"", pszVar, pszValue );
else
AKS( AKSTrace, "RegSet( %s ): changed \"%s\" to \"%s\"", pszVar, szValueCurrent, pszValue );
RegCloseKey(hkey);
}
int SetUpRegistry() {
//app doesn't have permission for this when run as normal user, but may for Admin? Anyway, not needed.
//RegSet( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\MoselleIDE.exe", "C:\\Moselle\\bin\\MoselleIDE.exe" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle", "Moselle.MoselleIDE.1" );
// Not needed.
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\Content Type", "text/plain" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\PerceivedType", "text" );
//Not needed, but may be be a way to have wordpad show up on the default list.
//RegSet( HKEY_CURRENT_USER, "Software\\Classes\\.moselle\\OpenWithProgIds\\Moselle.MoselleIDE.1", "" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\Moselle.MoselleIDE.1", "Moselle IDE" );
RegSet( HKEY_CURRENT_USER, "Software\\Classes\\Moselle.MoselleIDE.1\\Shell\\Open\\Command", "C:\\Moselle\\bin\\MoselleIDE.exe %1" );
SHChangeNotify( SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL );
return 0;
}
Finally, yes, I know I should figure out an installer, but I'm a C++ expert, not a Windows configuration and terminology expert and its a lot easier for me to write the above 50 lines than it is to even start figuring out how to configure an installer. This is for an alpha release and I'll watch this topic for better ideas for future releases.
Registry changes and all actions that require higher permissions should be done at installation stage, not at application startup. You probably want to use installation software. Otherwise your software will create serious security breach.