Search code examples
c++installshieldshellexecute

C++ Running SHELLEXECUTEINFO and appending feature name


I dont know c++ and im having issues trying to append a string name to lpFile of SHELLEXECUTEINFO. I get a semi-colon delimited string from Installshield and then split it up and loop through it. I am trying to then append each one of the 'features' that were split from the string and send it to dism.exe through shell execute.

Its failing at swprintf_s(buf, _T("Dism.exe /Online /Enable-Feature /FeatureName:%s"), sizeof(buf), wc); If i comment that outh then it will at least fire off the dll and throw a dism.exe error, so i know the call is working correctly.

template<typename Out>
void split(const std::string &s, char delim, Out result) {
    std::stringstream ss;
    ss.str(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        *(result++) = item;
    }
}


HRESULT __stdcall SplitString(IDispatch *pAction) {
    // Create a pointer to the IsSuiteExtension COM interface
    CComQIPtr<ISuiteExtension2> spSuiteExtension2 = pAction;

    // Turn on notifications for both the progress bar(epfProgressValid) and the ui message(epfMessageValid).
    EnumProgressFlags pf = EnumProgressFlags(EnumProgressFlags::epfMessageValid | EnumProgressFlags::epfProgressValid);

    BSTR bstrFeatureList(_T("ENABLE_FEATURES"));  // Property name to get. This should be a semi-colon delimeted list of features to enable for windows.
    BSTR FeatureList = ::SysAllocStringLen(NULL, 2 * 38); // Where to store the property value
    HRESULT hRet = spSuiteExtension2->get_Property(bstrFeatureList, &FeatureList); // Get the property value and store it in the 'FeatureList' variable

    CW2A pszConverted(FeatureList);

    using namespace std;
    string strConverted(pszConverted);
    vector<string> tokens;
    split(strConverted, ';', back_inserter(tokens));
    int numTokens = tokens.size();
    for (int i = 0; i < numTokens; i++)
    {
        string t = tokens.at(i);
        TCHAR buf[1024];

        SHELLEXECUTEINFO ShExecInfo = { 0 };
        ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
        ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
        ShExecInfo.hwnd = NULL;
        ShExecInfo.lpVerb = NULL;
        // Convert ANSI to Unicode
        ATL::CA2W wc(tokens.at(i).c_str());
        swprintf_s(buf, _T("Dism.exe /Online /Enable-Feature /FeatureName:%s"), sizeof(buf), wc);  // HAVING ISSUES HERE. NEED TO APPEND tokens.at(i) AT %S
        ShExecInfo.lpFile = buf;
        ShExecInfo.lpParameters = _T("");
        ShExecInfo.lpDirectory = _T("C:\\Windows\\SysNative");
        ShExecInfo.nShow = SW_SHOW;
        ShExecInfo.hInstApp = NULL;
        ShellExecuteEx(&ShExecInfo);
        WaitForSingleObject(ShExecInfo.hProcess, INFINITE);

    }

    //MessageBoxA(NULL, strConverted.c_str(), "testx", MB_OK);

    return ERROR_SUCCESS;
}

Solution

  • Since your code is using TCHAR strings, you should be using _stprintf_s() instead of swprintf_s(). And as Adrian McCarthy said, you are passing its parameters in the wrong order, so your code shouldn't even compile to begin with.

    But more importantly, there is no need to involve ANSI or TCHAR at all. You have Unicode input, and the STL and ShellExecuteEx() both support Unicode, so just keep everything in Unicode only.

    You are also leaking the BSTR that you allocate for FeatureList, as well as the process HANDLE that ShellExecuteEx() returns.

    Try something more like this instead:

    template <typename Out>
    void split(const std::wstring &s, wchar_t delim, Out result) {
        std::wistringstream iss(s);
        std::wstring item;
        while (std::getline(iss, item, delim)) {
            *(result++) = item;
        }
    }
    
    HRESULT __stdcall SplitString(IDispatch *pAction) {
        // Create a pointer to the IsSuiteExtension COM interface
        CComQIPtr<ISuiteExtension2> spSuiteExtension2 = pAction;
    
        // Turn on notifications for both the progress bar(epfProgressValid) and the ui message(epfMessageValid).
        EnumProgressFlags pf = EnumProgressFlags(EnumProgressFlags::epfMessageValid | EnumProgressFlags::epfProgressValid);
    
        CComBSTR FeatureList;
        HRESULT hRet = spSuiteExtension2->get_Property(CComBSTR(L"ENABLE_FEATURES"), &FeatureList); // Get the property value and store it in the 'FeatureList' variable
    
        std::vector<std::wstring> tokens;
        split(static_cast<BSTR>(FeatureList), L';', std::back_inserter(tokens));
    
        int numTokens = tokens.size();
        for (int i = 0; i < numTokens; i++)
        {
            std::wstring params = L"/Online /Enable-Feature /FeatureName:" + tokens.at(i);
    
            SHELLEXECUTEINFOW ShExecInfo = { 0 };
            ShExecInfo.cbSize = sizeof(ShExecInfo);
            ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
            ShExecInfo.lpFile = L"Dism.exe";
            ShExecInfo.lpParameters = params.c_str();
            ShExecInfo.lpDirectory = L"C:\\Windows\\SysNative";
            ShExecInfo.nShow = SW_SHOW;
    
            if (ShellExecuteExW(&ShExecInfo))
            {
                WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
                CloseHandle(ShExecInfo.hProcess);
            }
        }
    
        //MessageBoxW(NULL, FeatureList, L"testx", MB_OK);
    
        return ERROR_SUCCESS;
    }
    

    That being said, when launching an EXE, you should be using CreateProcess() directly instead of ShellExecuteEx():

    for (int i = 0; i < numTokens; i++)
    {
        std::wstring cmd = L"Dism.exe /Online /Enable-Feature /FeatureName:" + tokens.at(i);
    
        STARTUPINFO si = {0};
        PROCESS_INFORMATION pi = {0};
    
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_SHOW;
    
        if (CreateProcessW(NULL, &cmd[0], NULL, NULL, FALSE, 0, NULL, L"C:\\Windows\\SysNative", &si, &pi))
        {
            CloseHandle(pi.hThread);
            WaitForSingleObject(pi.hProcess, INFINITE);
            CloseHandle(pi.hProcess);
        }
    }