Click here to Skip to main content
15,867,488 members
Articles / Desktop Programming / Win32

An efficient way for automatic updating

Rate me:
Please Sign up or sign in to vote.
5.00/5 (22 votes)
11 Sep 2017CPOL7 min read 68K   1.1K   45   45
A simple way to provide silent automatic updates with no server side code

Commercial Version 

CodeProject Best C++ Article of September 2017 First Prize.

Introduction

Many software developers need to update their software and apply such updates to all current users. This article provides a method we developed that allows fully transparent automatic updates with no action needed from the user (such as starting the new version, etc.). It is also unique because it doesn't require any server-side code. 

The Problems

I have looked for a way to have our software download updates only when the version on our website is newer, and yet, to avoid having to run a server-side component. Our goal was to have our AutoUpdate class determine if the version on the website is indeed newer without downloading it first. We also wanted a clean and smooth solution that won't require installing a new version but will cause a product (i.e. software.exe) to replace itself with a new version (also software.exe) transparently. I learned that there are quite a few steps that have to be taken and scenarios to be addressed but the result is a solid solution and covers all scenarios. 

The Building Blocks

Obtaining the version of a given executable. We use GetFileVersionInfo() to obtain the information. We have developed our own function to convert an executable path into our own version information structure which is:

C++
typedef struct
{
    int Major;
    int Minor;
    int Revision;
    int SubRevision;
} SG_Version;

so SG_GetVersion goes as follow:

C++
BOOL AutoUpdate::SG_GetVersion(LPWSTR ExeFile, SG_Version *ver)
{
    BOOL result = FALSE;
    DWORD dwDummy;
    DWORD dwFVISize = GetFileVersionInfoSize(ExeFile, &dwDummy);
    LPBYTE lpVersionInfo = new BYTE[dwFVISize];
    GetFileVersionInfo(ExeFile, 0, dwFVISize, lpVersionInfo);
    UINT uLen;
    VS_FIXEDFILEINFO *lpFfi;
    VerQueryValue(lpVersionInfo, _T("\\"), (LPVOID *)&lpFfi, &uLen);
    if (lpFfi && uLen)
    {
        DWORD dwFileVersionMS = lpFfi->dwFileVersionMS;
        DWORD dwFileVersionLS = lpFfi->dwFileVersionLS;
        delete[] lpVersionInfo;
        ver->Major = HIWORD(dwFileVersionMS);
        ver->Minor = LOWORD(dwFileVersionMS);
        ver->Revision = HIWORD(dwFileVersionLS);
        ver->SubRevision = LOWORD(dwFileVersionLS);
        result = TRUE;
    }
    ReplaceTempVersion();
    return result;
}

Adding the next version to the file name. Next, we need a function that will generate the file name which will contain the next version. For example, if our software is "program.exe" and the next version is 1.0.0.3, then this function will generate the following name and store it in our class's member variable m_NextVersion "program.1.0.0.3.exe".

C++
void AutoUpdate::AddNextVersionToFileName(CString& ExeFile, SG_Version ver)
{
    CString strVer;
    ver.SubRevision += 1;    // For the time being we just promote the subrevision in one but of course
                            // we should build a mechanism to promote the major, minor and revision
    ExeFile = GetSelfFullPath();
    ExeFile = ExeFile.Left(ExeFile.GetLength() - 4);
    ExeFile += L"."+strVer;
    ExeFile += L".exe";
    m_NextVersion = ExeFile;
}

Downloading the newer version from the web site

After examining several ways to download a file from a URL, here is the best method we have found. Please note that we are using DeleteUrlCacheEntry(). If your software is looking for updates every 2 hours and only after 6 hours, an update is placed, unless you delete the cache, your browser might not download the newer file as it will have the name of an existing file. That is also important during the QA phase when you try different scenarios.

C++
DeleteUrlCacheEntry(URL); // we need to delete the cache so we always download the real file
HRESULT hr = 0;
hr = URLDownloadToFile(
    NULL,   // A pointer to the controlling IUnknown interface (not needed here)
    URL,
    ExeName,0,              // Reserved. Must be set to 0.
    &pCallback);           //  A callback function is used to ensure asynchronous download

Then the callback function goes like that:

We define a class named MyCallback. Before calling URLDownloadToFile() we define a local member of this function:

C++
MyCallback pCallback;

The class is defined as follows:

C++
using namespace std;

class MyCallback : public IBindStatusCallback
{
public:
    MyCallback() {}

    ~MyCallback() { }

    // This one is called by URLDownloadToFile
    STDMETHOD(OnProgress)(/* [in] */ ULONG ulProgress, /* [in] */ ULONG ulProgressMax, /* [in] */ ULONG ulStatusCode, /* [in] */ LPCWSTR wszStatusText)
    {
        // You can use your own logging function here
        // Log(L"Downloaded %d of %d. Status code", ulProgress, ulProgressMax, ulStatusCode);
        return S_OK;
    }

    STDMETHOD(OnStartBinding)(/* [in] */ DWORD dwReserved, /* [in] */ IBinding __RPC_FAR *pib)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(GetPriority)(/* [out] */ LONG __RPC_FAR *pnPriority)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(OnLowResource)(/* [in] */ DWORD reserved)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(OnStopBinding)(/* [in] */ HRESULT hresult, /* [unique][in] */ LPCWSTR szError)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(GetBindInfo)(/* [out] */ DWORD __RPC_FAR *grfBINDF, /* [unique][out][in] */ BINDINFO __RPC_FAR *pbindinfo)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(OnDataAvailable)(/* [in] */ DWORD grfBSCF, /* [in] */ DWORD dwSize, /* [in] */ FORMATETC __RPC_FAR *pformatetc, /* [in] */ STGMEDIUM __RPC_FAR *pstgmed)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(OnObjectAvailable)(/* [in] */ REFIID riid, /* [iid_is][in] */ IUnknown __RPC_FAR *punk)
    {
        return E_NOTIMPL;
    }

    // IUnknown stuff
    STDMETHOD_(ULONG, AddRef)()
    {
        return 0;
    }

    STDMETHOD_(ULONG, Release)()
    {
        return 0;
    }

    STDMETHOD(QueryInterface)(/* [in] */ REFIID riid, /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
    {
        return E_NOTIMPL;
    }
};

The SG_Run function

The SG_Run function is the optimal way we have found to start a new process. There are various ways such as ShellExecute() but that would be the most efficient one:

C++
BOOL SG_Run(LPWSTR FileName)
{
    wprintf(L"Called SG_Run '%s'", FileName);
    PROCESS_INFORMATION ProcessInfo; //This is what we get as an [out] parameter

    STARTUPINFO StartupInfo; //This is an [in] parameter

    ZeroMemory(&StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb = sizeof StartupInfo; //Only compulsory field

    if (CreateProcess(FileName, NULL,
        NULL, NULL, FALSE, 0, NULL,
        NULL, &StartupInfo, &ProcessInfo))
    {
        //WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);
        wprintf(L"Success");
        return TRUE;
    }
    else
    {
        wprintf(L"Failed");
        return FALSE;
    }

}

The Solution

Before you start coding, remember to make sure your project has a version string. To add a version string, right-click the project name in the Solution Explorer and select Add -> Resource. Image 1Image 2

The flow

The solution can be described with the following steps:

Given that the current version of a product (software.exe) is 1.0.0.1, we would like it to download the replace itself with a newer version if this version is 1.0.0.2. We name version 1.0.0.2 of software.exe software.1.0.0.2.exe. We place this version on our website where it can be downloaded freely with no need to interact with the server or to log in. For example: http://www.ourproduct.com/downloads/software.1.0.0.2.exe
  1. Software.exe should fetch its own version and periodically try to download its next version if that is possible. If there is no newer version nothing will be downloaded.
     
  2. When there is a newer version it will be downloaded to a temporary file (for example _software.exe). 
     
  3. _software.exe will be examined to ensure that its version string matches the original file name on the server, (i.e. 1.0.0.2), if not, it will be deleted and ignored.
     
  4. software.exe will now start _software.exe as a new process.
     
  5. software.exe quits.
     
  6. _software.exe copies itself to software.exe, which is possible since software.exe quit.
     
  7. _software.exe starts software.exe as a new process.
     
  8. _sotware.exe quits.
     
  9. software.exe deletes _software.exe.
     

Requirements

We need to link our software with the following libraries:

urlmon.lib and version.lib.

Remember to set the Language of your code snippet using the Language dropdown.

Use the "var" button to to wrap Variable or class names in <code> tags like this.

The Magic

I have placed an identical copy of this program on my server, with one change: the version string in the code that is included in this article is 1.0.0.1 and the one on my server is 1.0.0.2. If you download the source code and compile it, you will get SG_AutoUpdate.exe. If you run it, it should automatically update itself within seconds.

The original executable looks like this:

Image 3

After running it, you will see the same file but in fact, it is a different one. It's the newer version:

Image 4

You can of course use your own website. 

Right now, the download link is composed using this constant:

C++
CString m_DownloadLink = _T("http://forensics.tips/");

AutoUpdate.h line 43.

You can replace it with any other address. Here are the instructions for testing this mechanism: 

  1. Build the source code and keep SG_AutoUpdate.exe somewhere (or rename it temporarily).
  2. Go to the project's resources, find the version string, and change it to 1.0.0.2.
  3. Build the source code again.
  4. You will get the more recent version under the name SG_AutoUpdate.exe. Upload it to your server and rename it SG_AutoUpdate.1.0.0.2.exe.
  5. All names are case-sensitive.
  6. Now, go to the backup you have made, delete the file you have just uploaded, and rename the backup back to SG_AutoUpdate.exe. Delete all other files. You should have one file named SG_AutoUpdate.exe and if you hover the mouse over it, you should see the "BEFORE" image, and the version should show: 1.0.0.1.
  7. Double click it and after a few seconds you will find out it was updated to 1.0.0.2. You can check the version string to verify that. 

But let's make sure the new version isn't downloaded again and won't cause endless "false positive" updates.

Now run the current SG_AutoUpdate.exe again. You will find that it reports that there isn't an update in the server. We are done.

I made a small video showing the entire process. Please take a close look at the "Program Version" column in the File Explorer.

 

Update (Supports UAC)

Following a question by David Zaccanti, this method of automatic updating supports applications that require elevation, i.e. prompting the user to approve Administration Privileges. I have updated the source code and the new version on the server (which the program updates to) so they both require Administration privileges. When you compile the source code and run version 1.0.0.1 it will display the UAC Prompt, however after silently updating to version 1.0.0.2, no elevation prompt will appear, as an elevated process can start another elevated process using CreateProcess() with no elevation prompt.

Left to be done...

There are several enhancements that aren't part of this code but can be developed:

- Adding a Log() function to allow you to redirect all "wprintf" calls into one log file shared by all variants of the program during the auto-update process. 

- Allowing automatic updates to a version that comes after the next one, for example, going from 1.0.0.1 to 1.0.0.5. 

- Adding an automatic version updater to the post-build section of the project, which will allow an automatic promotion of the current version.

Michael Haephrati
Creator of the commercial Auto Update solution
haephrati@gmail.com

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Secured Globe, Inc.
United States United States
Michael Haephrati is a music composer, an inventor and an expert specializes in software development and information security, who has built a unique perspective which combines technology and the end user experience. He is the author of a the book Learning C++ , which teaches C++ 20, and was published in August 2022.

He is the CEO of Secured Globe, Inc., and also active at Stack Overflow.

Read our Corporate blog or read my Personal blog.





Comments and Discussions

 
QuestionAuto Update 64 bit poblem Pin
Emre Kira Sarı31-May-23 3:38
Emre Kira Sarı31-May-23 3:38 
AnswerRe: Auto Update 64 bit poblem Pin
Michael Haephrati31-May-23 6:32
professionalMichael Haephrati31-May-23 6:32 
GeneralRe: Auto Update 64 bit poblem Pin
Emre Kira Sarı31-May-23 6:37
Emre Kira Sarı31-May-23 6:37 
Questionhow about updating not just exe file, include dlls and more ? Pin
Member 143605199-Nov-19 15:06
Member 143605199-Nov-19 15:06 
QuestionSelf renaming reduces two SG_Run calls to one call Pin
ehaerim25-Mar-19 13:42
ehaerim25-Mar-19 13:42 
QuestionBug scenario found when result2 = CopyFile(m_SelfFileName, m_SelfFileName.Mid(3), FALSE); returns FALSE Pin
ehaerim23-Mar-19 23:30
ehaerim23-Mar-19 23:30 
Questionexit is more graceful than _exit Pin
ehaerim22-Mar-19 21:28
ehaerim22-Mar-19 21:28 
QuestionWill this method work when multiple instances running at the same time? Pin
ehaerim21-Mar-19 21:44
ehaerim21-Mar-19 21:44 
QuestionDo we really need to try to DeleteFile the normal or temp version 5 times, not just once and for all? Pin
ehaerim21-Mar-19 21:42
ehaerim21-Mar-19 21:42 
Questiontwo non-critical bugs Pin
ehaerim21-Mar-19 7:44
ehaerim21-Mar-19 7:44 
AnswerRe: two non-critical bugs Pin
Michael Haephrati21-Mar-19 17:12
professionalMichael Haephrati21-Mar-19 17:12 
PraiseThanks you Pin
Huzifa Terkawi26-Oct-17 14:33
Huzifa Terkawi26-Oct-17 14:33 
AnswerRe: Thanks you Pin
Michael Haephrati30-Oct-17 6:36
professionalMichael Haephrati30-Oct-17 6:36 
QuestionNot so efficient Pin
Theo Buys21-Sep-17 22:15
Theo Buys21-Sep-17 22:15 
AnswerRe: Not so efficient Pin
Michael Haephrati21-Sep-17 7:56
professionalMichael Haephrati21-Sep-17 7:56 
GeneralRe: Not so efficient Pin
Theo Buys21-Sep-17 20:55
Theo Buys21-Sep-17 20:55 
AnswerRe: Not so efficient Pin
Michael Haephrati22-Sep-17 7:39
professionalMichael Haephrati22-Sep-17 7:39 
QuestionNice solution! Pin
Theo Buys18-Sep-17 23:43
Theo Buys18-Sep-17 23:43 
AnswerRe: Nice solution! Pin
Michael Haephrati19-Sep-17 0:16
professionalMichael Haephrati19-Sep-17 0:16 
Thank you for your comments. We, at Secured Globe, Inc. tend to create applications with a single file (and in case we need several files, a single file extracts them during the first run). For these reasons it works for us. As for UAC, if you check my update you will see that in general, if your program requires Administration rights, then you assume the user ran it and confirmed the Elevation Prompt and after that point, the automatic updates will run smoothly and with no prompts (silent updates), and the reason is because an elevated process can invoke another elevated process
- Michael Haephrati מיכאל האפרתי

GeneralRe: Nice solution! Pin
Theo Buys19-Sep-17 0:55
Theo Buys19-Sep-17 0:55 
GeneralRe: Nice solution! Pin
Michael Haephrati19-Sep-17 0:59
professionalMichael Haephrati19-Sep-17 0:59 
GeneralRe: Nice solution! Pin
Theo Buys19-Sep-17 1:26
Theo Buys19-Sep-17 1:26 
PraiseRe: Nice solution! Pin
Michael Haephrati19-Sep-17 1:33
professionalMichael Haephrati19-Sep-17 1:33 
GeneralRe: Nice solution! Pin
Theo Buys19-Sep-17 1:55
Theo Buys19-Sep-17 1:55 
GeneralRe: Nice solution! Pin
Michael Haephrati19-Sep-17 2:39
professionalMichael Haephrati19-Sep-17 2:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.