1

I can use this code in InitInstance to confirm which executable is associated with a given extension:

TCHAR szRegisteredEXE[_MAX_PATH];
DWORD dwBufferLen = _MAX_PATH;

HRESULT  hRes = AssocQueryString(ASSOCF_NONE, ASSOCSTR_EXECUTABLE, 
                    _T("MeetSchedAssist.MWB"), NULL, szRegisteredEXE, &dwBufferLen);
if (hRes == S_OK)
{
    // TODO
}

It works fine.

My software installs a 32 bit version and a 64 bit version of the executable. So what I would like the code to do is prompt to update the associations if the registered exe is not the active exe.

I know how to get my active exe path and how to confirm if it matches szRegisteredEXE but how to I then deal with updating the file associations (assuming the user says yes to the prompt to associate)?

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • 2
    It's complicated, but can be done. You'll need to write/replace registry entries in `HKEY_CLASSES_ROOT`, but there will be a number of such that need to be modified **and** interlinked. If you have an installed program that handles the MWB extension, look at `HKEY_CLASSES_ROOT\.MWB` and also at the `HKEY_CLASSES_ROOT\MyDescription` key (where `MyDescription` is given in the first key). – Adrian Mole May 09 '20 at 15:08
  • 2
    You'll also need to be running with elevated privileges, otherwise I think the HKCR key will be virtualized, and changes won't persist after the program exits. – Adrian Mole May 09 '20 at 15:11
  • @AdrianMole Yes, I have installed the program. I understand what you are saying. – Andrew Truckle May 09 '20 at 15:18
  • 1
    So, if you're comfortable with the registry access API calls (as I imagine you are), you can dive in and try! I think it's a bit too much to try to post a code answer, at this stage, but please feel free to come back and ask if you run into issues. I'd be happy to (try to) help. – Adrian Mole May 09 '20 at 15:21
  • @AdrianMole Suppose that I add a menu item to perform the associations. I assume it will have to be an external tool that I need to invoke with admin privilieges? Or can we temporarily elevate our running executable? – Andrew Truckle May 09 '20 at 15:22
  • 2
    I think the executable would have to be "Run as Administrator" (set that requirement in the manifest file), which would then (annoyingly) keep asking for an Admin password each time it starts. But I'm not 100% certain - they *may* be a way to elevate after starting (would still need the admin password prompt, though -otherwise it would be an easy O/S hack). – Adrian Mole May 09 '20 at 15:24
  • @AdrianMole So then we need a tool that the software will run which we can raise with elevated privileges. I will think about it. See if it is worth it. – Andrew Truckle May 09 '20 at 15:25
  • 1
    You may be able to build a 'fake' MSI installer that you call from your program, that sets the required file association. – Adrian Mole May 09 '20 at 15:29
  • @AdrianMole Now that is an idea! It will all be done under the hood then. Just run the exe file with the appropriate path as a parameter. I will look into it. Thank you. – Andrew Truckle May 09 '20 at 15:33
  • @AdrianMole I made progress (see my answer). My only issue is a cosmetic one concerning the Shield icon (see: https://stackoverflow.com/questions/61700399/shield-mfc-image-resource-for-cmfcbutton). – Andrew Truckle May 09 '20 at 16:59

1 Answers1

1

Step one is to create a Inno Setup installer that is going to manage the tweaking of the registry for us:

; SignTool parameters
#define SignedDesc "$qMeeting Schedule Assistant File Associations Tool$q"
#define SignedPfx "$q~~~~~$q"
#define SignedTimeStamp "$qhttp://timestamp.verisign.com/scripts/timestamp.dll$q"
#define SignedPw "$q~~~~~$q"

#define AppURL "https://www.publictalksoftware.co.uk"
#define AppPublisher "~~~~~"

#define AppVerText() \
   ParseVersion('..\Meeting Schedule Assistant\Release\Meeting Schedule Assistant.exe', \
       Local[0], Local[1], Local[2], Local[3]), \
   Str(Local[0]) + "." + Str(Local[1]) + "." + Str(Local[2])

[Setup]
DisableReadyPage=True
DisableReadyMemo=True
DisableFinishedPage=True
UsePreviousSetupType=False
UsePreviousTasks=False
UsePreviousLanguage=False
FlatComponentsList=False
AlwaysShowComponentsList=False
ShowComponentSizes=False
AppName=Meeting Schedule Assistant File Associations Tool
AppVersion={#AppVerText}
CreateAppDir=False
Uninstallable=no
OutputBaseFilename=MSATweakFileAssociations
SignTool=SignTool /d {#SignedDesc} /du $q{#AppURL}$q /f {#SignedPfx} /p {#SignedPw} /t {#SignedTimeStamp} /v $f
AppId={{~~~~~}

[registry]
Root: HKCR; SubKey: "MeetSchedAssist.MWB\Shell\Open\Command"; ValueType: string; ValueData: """{param:ExePath}"" ""%1""";
Root: HKCR; SubKey: "MeetSchedAssist.SRR\Shell\Open\Command"; ValueType: string; ValueData: """{param:ExePath}"" ""%1""";

Then, in MFC, we run our tool, like this:

void COptionsDlg::OnBnClickedMfcbuttonFileAssociations()
{
    // Try to run the help installer
    CString strSetupExe = _T("~~~~~.exe");
    CString strProgramFolder = theApp.GetProgramPath();
    CString strParams = _T("");

    strParams.Format(_T("/SP- /VERYSILENT /ExePath=\"%s\""), (LPCTSTR)theApp.GetProgramEXE());
    if (!theApp.ExecuteProgram(strProgramFolder + strSetupExe, strParams))
    {
        // Problem running the installer
        AfxMessageBox(_T("Unable to change the file associations"), MB_OK | MB_ICONERROR);
        return;
    }
}

BOOL CMeetingScheduleAssistantApp::ExecuteProgram(CString strProgram, CString strArguments)
{
    SHELLEXECUTEINFO    se = { 0 };
    MSG                 sMessage;
    DWORD               dwResult;

    theApp.SetProgramExecuting(true);

    se.cbSize = sizeof(se);
    se.lpFile = strProgram;
    se.lpParameters = strArguments;
    se.nShow = SW_SHOWDEFAULT;
    se.fMask = SEE_MASK_NOCLOSEPROCESS;
    ShellExecuteEx(&se);

    if (se.hProcess != nullptr)
    {
        do
        {
            dwResult = ::MsgWaitForMultipleObjects(1, &(se.hProcess), FALSE,
                INFINITE, QS_ALLINPUT);
            if (dwResult != WAIT_OBJECT_0)
            {
                while (PeekMessage(&sMessage, nullptr, NULL, NULL, PM_REMOVE))
                {
                    TranslateMessage(&sMessage);
                    DispatchMessage(&sMessage);
                }
            }
        } while ((dwResult != WAIT_OBJECT_0) && (dwResult != WAIT_FAILED));

        CloseHandle(se.hProcess);
    }

    theApp.SetProgramExecuting(false);

    if ((DWORD_PTR)(se.hInstApp) < 33)
    {
        // Throw error
        AfxThrowUserException();
        return FALSE;
    }

    SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);

    return TRUE;
}

Voila! It updates the registry.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164