C++ Program to install & uninstall EXE silently

In this article I have explained how we can create a program to install & uninstall EXE/MSI file silently in C++ without any requirement of UI integration by user/client. A package/tool can be created which can be used by admin/IT team of MNCs to install/uninstall bundle of applications (EXE/MSI) silently on their employee’s machines.

Let's do it with an example. We will create a package which will uninstall all installed java updates and then will install new version.

These are the steps to follow for creating package:

  1. Write program to install/uninstall required application.
  2. Generate EXE of program.
  3. Create package which needs to install on client’s machine. This bundle will contain following files:
    • Program EXE - Contains actual code to run for installation & uninstallation.
    • MSI/EXE file - Application which needs to install on client’s machine.
    • Configuration file – Contains configurations used in code like WMI Query defining list of programs those need to install, MSI file name, install command, uninstall command

Program to install/uninstall required application

We will be developing a console application using C++ to implement required functionality.

I have used Microsoft Visual Studio 2017 Community edition to develop the program which is free to use and can be downloaded using following link. You can use any other IDE with C++ compiler if you don’t want to use Visual Studio.

Create C++ Console Application Project in Visual Studio

Follow these steps to create new C++ console application project in visual studio:

  1. Start Visual Studio 2017 Community Edition.
  2. File > New > Project or Press Ctrl + Shift + N.
  3. In New project dialog box, Installed > Visual C++ > Windows Console Application
  4. Type project name “InstallPackage” in Name text box.
  5. Select project location to save project files in Location fields.
  6. Click on OK button.

A new solution with the name InstallPackage will be created and InstallPackage.cpp file will be opened by default. This file will contain your actual required implementation. You will find main function in this file.

Solution Explore window (View > Solution Explorer) will be opened by default and you can find all supporting files and header file for this program.

Let’s write program now to implement required functionality.

These are the main functionalities needs to implement in C++ program:

Hide Console Window

We need to hide console window whenever program starts so that client will not be distracted when package is running in background.

//Step 1: Hide Console Window when start programme
::ShowWindow(::GetConsoleWindow(), SW_HIDE);

Get Directory Path

We need to get directory’s complete path where our program’s exe, MSI file & configuration file will be extracted (Ex. Temp folder). A function will contain code to get directory path:

string GetDirectoryPath()
{
    wchar_t wfilePath[MAX_PATH];
    GetModuleFileName(NULL, wfilePath, MAX_PATH);
    wstring ws(wfilePath);
    _bstr_t filePath = ws.c_str();

    string fileName(ws.begin(), ws.end());
    string directoryPath;
    const size_t last_slash_idx = fileName.rfind('\\');
    if (std::string::npos != last_slash_idx)
    {
        directoryPath = fileName.substr(0, last_slash_idx) + '\\';
    }
    return directoryPath;
}

This function will be called from main function:

// Step 2: Get Current Director's Path
string directory = GetDirectoryPath();
_bstr_t sdirectory = directory.c_str();

Get Configuration from config.txt file.

We need to get all configurations from config file which will contain following configuration/settings in form of key/value pair:

  1. WMI Query – Query which will be used to fetch list of installed application those need to uninstall.
  2. MSI File Name – MSI/EXE file name which needs to install.
  3. Install Command – Command which will be used to install required file.
  4. Uninstall Command – Command which will be used to uninstall applications.
InstallPackageConfig.txt

Key Value
{WMIQuery}{select IdentifyingNumber,Name from win32_product where name like 'Java 8 Update%' and Version Like '8.0%'}
{MSIFileName}{jre-8u191-windows-x64.exe}
{InstallCommand}{start /wait "title" "#Path##MSIFileName#" /s /l*v "#Path#installation.log}
{UninstallCommand}{start /wait msiexec.exe /x #ProductId# /qn /norestart /l*v "#Path##ProductId#.log}

There are following place holders in configuration values those will be replaced through code:

#Path# - Will replace directory full path.
#MSIFileName# - Will replace MSI file name required to implement.
#ProductId# - Product Id of application which need to uninstall. It will be fetched through WMI query.

We will write a function which will be respobsible to fetch configuration settings and return.


void GetConfigurations(string directory, _bstr_t &wmiQuery, _bstr_t &msiFileName, _bstr_t &installCommand, string &uninstallCommand)
{
    string lineText, strMsiFileName;

    ifstream myReadFile;
    myReadFile.open(directory + "InstallPackageConfig.txt");
    char output[100];
    if (myReadFile.is_open()) 
    {
        while (!myReadFile.eof()) 
        {
            getline(myReadFile, lineText);
            std::size_t pos = lineText.find("WMIQuery");
            if (lineText.find("WMIQuery") == 1)
            {
                lineText = lineText.substr(lineText.find(":{"));
                lineText = lineText.replace(0, 2, "");
                lineText = lineText.replace(lineText.end() - 1, lineText.end(), "");
                wmiQuery = lineText.c_str();
            }
            else if (lineText.find("MSIFileName") == 1)
            {
                lineText = lineText.substr(lineText.find(":{"));
                lineText = lineText.replace(0, 2, "");
                lineText = lineText.replace(lineText.end() - 1, lineText.end(), "");
                strMsiFileName = lineText;
                msiFileName = lineText.c_str();
            }
            else if (lineText.find("InstallCommand") == 1)
            {
                lineText = lineText.substr(lineText.find(":{"));
                lineText = lineText.replace(0, 2, "");
                lineText = lineText.replace(lineText.end() - 1, lineText.end(), "");
                findAndReplaceAll(lineText, "#MSIFileName#", strMsiFileName);
                findAndReplaceAll(lineText, "#Path#", directory);
                installCommand = lineText.c_str();
            }
            else if (lineText.find("UninstallCommand") == 1)
            {
                lineText = lineText.substr(lineText.find(":{"));
                lineText = lineText.replace(0, 2, "");
                lineText = lineText.replace(lineText.end() - 1, lineText.end(), "");
                findAndReplaceAll(lineText, "#Path#", directory);
                uninstallCommand = lineText;
            }
        }
    }
    myReadFile.close();
}

This function will be called in main function:

// Step 3: Get Cofigurations from cofig.txt
_bstr_t wmiQuery, msiFileName, installCommand;
string uninstallCommand;
GetConfigurations(directory, wmiQuery, msiFileName, installCommand, uninstallCommand);

Run WMI Query to fetch application’s list those need to Uninstall

HRESULT hres;

// Initialize COM. ------------------------------------------

hres = CoInitializeEx(0, COINIT_MULTITHREADED);
if (FAILED(hres))
{
    cout << "Failed to initialize COM library. Error code = 0x"
    << hex << hres << endl;
    return 1;                  // Program has failed.
}

// Set general COM security levels --------------------------
hres = CoInitializeSecurity(
NULL,
-1,                          // COM authentication
NULL,                        // Authentication services
NULL,                        // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL,                        // Authentication info
EOAC_NONE,                   // Additional capabilities
NULL                         // Reserved
);

if (FAILED(hres))
{
    cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
    CoUninitialize();
    return 1;                    // Program has failed.
}

// Obtain the initial locator to WMI -------------------------

IWbemLocator *pLoc = NULL;
hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID *)&pLoc);
if (FAILED(hres))
    {
    cout << "Failed to create IWbemLocator object." << " Err code = 0x" << hex << hres << endl;
    CoUninitialize();
    return 1;                 // Program has failed.
}

// Connect to WMI through the IWbemLocator::ConnectServer method

IWbemServices *pSvc = NULL;

// Connect to the root\cimv2 namespace with the current user and obtain pointer pSvc to make IWbemServices calls.
hres = pLoc->ConnectServer(
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
NULL,                    // User name. NULL = current user
NULL,                    // User password. NULL = current
0,                       // Locale. NULL indicates current
NULL,                    // Security flags.
0,                       // Authority (for example, Kerberos)
0,                       // Context object
&pSvc                    // pointer to IWbemServices proxy
);

if (FAILED(hres))
{
    cout << "Could not connect. Error code = 0x" << hex << hres << endl;
    pLoc->Release();
    CoUninitialize();
    return 1;                // Program has failed.
}

// Set security levels on the proxy -------------------------

hres = CoSetProxyBlanket(
pSvc,                        // Indicates the proxy to set
RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
NULL,                        // Server principal name
RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL,                        // client identity
EOAC_NONE                    // proxy capabilities
);

if (FAILED(hres))
{
    cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
    pSvc->Release();
    pLoc->Release();
    CoUninitialize();
    return 1;               // Program has failed.
}

// Use the IWbemServices pointer to make requests of WMI ----

// For example, get the name of the operating system
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
bstr_t("WQL"),
bstr_t(wmiQuery),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);

if (FAILED(hres))
{
    cout << "Query for operating system name failed." << " Error code = 0x" << hex << hres << endl;
    pSvc->Release();
    pLoc->Release();
    CoUninitialize();
    return 1;               // Program has failed.
}

Uninstall Applications & Cleanup Objects

Now we will iterate over list of applications and uninstall them one by one:

// Step 5: -------------------------------------------------
// Iterate over product ids and uninstall

IWbemClassObject *pclsObj = NULL;
ULONG uReturn = 0;

while (pEnumerator)
{
    HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
    &pclsObj, &uReturn);

    if (0 == uReturn)
    {
        break;
    }

    VARIANT vtProductId;
    VARIANT vtName;

    hr = pclsObj->Get(L"IdentifyingNumber", 0, &vtProductId, 0, 0);
    hr = pclsObj->Get(L"Name", 0, &vtName, 0, 0);

    _bstr_t id = _bstr_t(vtProductId.bstrVal, false);
    _bstr_t name = _bstr_t(vtName.bstrVal, false);

    string newCommand = uninstallCommand;
    findAndReplaceAll(newCommand, "#ProductId#", (const char*)id);
    system(newCommand.c_str());

    VariantClear(&vtProductId);
    pclsObj->Release();
}

// Cleanup
// ========

pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();

Install Application

// Step 10: -------------------------------------------------
// Install required MSI file
system(installCommand);

return 0;

I have attached complete code for reference to download.

Create EXE of written Program

Now as we have completed implementation of C++ code, we need to generate EXE for it. To create EXE follow following steps:

  1. Select Release mode from Solution Configuration dropdown in tool bar.
  2. Select x86 from Solution Platform dropdown in tool bar.
  3. Build your project. InstallPackage .exe file will be generated under Release folder at project location.

We have selected x86 in Solution Platform because it will generate exe for 32 bit machine. But this exe will be applicable for both 32 bit & 64 bit machine.

Create Installer Package using 7-Zip

Now as we have exe of program so now we need to bundle our exe with required MSI file and configuration file. We will need 7-Zip to bundle our package. Download from here if you don’t have it on your machine. These steps required to package exe and required MSI file:

Step 1 - Setup your installation folder

To make this easy create a folder C:\Install. This is where we will copy all the required files.

Step 2 - 7Zip your installers
  1. Go to the folder that has your .msi/.exe (latest version of java exe in case of our example), InstallPackageConfig.txt and your InstallPackage.exe (Copied from Release folder).
  2. Select both the .msi and the InstallPackage.exe.
  3. Right-Click and choose 7Zip --> "Add to Archive".
  4. Name your archive "Installer.7z" (or a name of your choice).
  5. Click Ok.
  6. You should now have "Installer.7z".
  7. Copy this .7z file to your C:\Install directory.
Step 3 - Get the 7z-Extra sfx extension module

You need to download the 7z-Extra.

  1. Follow this link to go to download 7Zip.
  2. You need to download the latest version.
  3. Extract the 7zip extra files.
  4. Copy the file "7zS.sfx" to C:\Install.
Step 4 - Setup your 7-zip config.txt

NotePad++ is recommended to edit this text file as you will need to encode in UTF-8, the following instructions are using notepad++.

  1. Using windows explorer go to C:\Install.
  2. Right-click and choose "New Text File" and name it config.txt.
  3. Right-click and choose "Edit with NotePad++.
  4. Click the "Encoding Menu" and choose "Encode in UTF-8".
  5. Enter something like this: ;!@Install@!UTF-8! RunProgram=" InstallPackage.exe" ;!@InstallEnd@!
CheckPoint

You should now have a folder "C:\Install" with the following 3 files:

  1. Installer.7z
  2. 7zS.sfx
  3. config.txt
Step 5 - Create the archive
  1. Open a cmd window, Window + R --> cmd --> press enter
  2. In the command window type the following cd cd Install copy /b 7zS.sfx + config.txt + Installer.7z <name_of_package>.exe
  3. Look in c:\Install and you will now see a <name_of_package>.exe

Share this exe to run on required machines. This exe will install attached file and uninstall all applications those are selected through WMI Query.