Click here to Skip to main content
13,146,315 members (44,755 online)
Click here to Skip to main content
Add your own
alternative version

Stats

4.5K views
114 downloads
20 bookmarked
Posted 11 Sep 2017

An efficient way for automatic updating

, 11 Sep 2017
Rate this:
Please Sign up or sign in to vote.
A simple way to provide silent automatic updates with no server side code

Introduction

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

The Problems

I have looked for a way to have our software download updates only when the version  on our web site 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 web site is indeed newer without downloading it first. We also wanted a clean and smooth solution which 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) transparantly. I learned that there are quite a few steps that has 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:

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

so SG_GetVersion goes as follow:

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".

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.

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:

MyCallback pCallback;

The class is define as follow:

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:

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. 

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 in our web site 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 faetch 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 assure 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 quitted.
     
  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 in 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:

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

You can of course use your own web site. 

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

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

AutoUpdate.h line 43.

You can replace it to any other address. Then 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 hoover the mouse over it, you should see the "BEFORE" image, maning 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 questions 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 running 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 which 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 update 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.

haephrati@gmail.com

License

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

Share

About the Author

Michael Haephrati
CEO Secured Globe, Inc.
United States United States
Michael Haephrati, is an entrepreneur, inventor and a musician. Haephrati worked on many ventures starting from HarmonySoft, designing Rashumon, the first Graphical Multi-lingual word processor for Amiga computer. During 1995-1996 he worked as a Contractor with Apple at Cupertino. Worked at a research institute made the fist steps developing the credit scoring field in Israel. He founded Target Scoring and developed a credit scoring system named ThiS, based on geographical statistical data, participating VISA CAL, Isracard, Bank Leumi and Bank Discount (Target Scoring, being the VP Business Development of a large Israeli institute).

During 2000, he founded Target Eye, and developed the first remote PC surveillance and monitoring system, named Target Eye.


Other ventures included: Data Cleansing (as part of the DataTune system which was implemented in many organizations.



You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionNot so efficient Pin
Theo Buys21-Sep-17 22:15
memberTheo 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
memberTheo Buys21-Sep-17 20:55 
AnswerRe: Not so efficient Pin
Michael Haephrati17hrs 1 min ago
professionalMichael Haephrati17hrs 1 min ago 
QuestionNice solution! Pin
Theo Buys18-Sep-17 23:43
memberTheo Buys18-Sep-17 23:43 
AnswerRe: Nice solution! Pin
Michael Haephrati19-Sep-17 0:16
professionalMichael Haephrati19-Sep-17 0:16 
GeneralRe: Nice solution! Pin
Theo Buys19-Sep-17 0:55
memberTheo 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
memberTheo 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
memberTheo Buys19-Sep-17 1:55 
GeneralRe: Nice solution! Pin
Michael Haephrati19-Sep-17 2:39
professionalMichael Haephrati19-Sep-17 2:39 
GeneralRe: Nice solution! Pin
Theo Buys19-Sep-17 3:08
memberTheo Buys19-Sep-17 3:08 
GeneralRe: Nice solution! Pin
Michael Haephrati19-Sep-17 6:53
professionalMichael Haephrati19-Sep-17 6:53 
GeneralRe: Nice solution! Pin
Theo Buys20-Sep-17 22:54
memberTheo Buys20-Sep-17 22:54 
GeneralRe: Nice solution! Pin
Michael Haephrati21-Sep-17 0:06
professionalMichael Haephrati21-Sep-17 0:06 
PraiseWell done ! Pin
RickZeeland16-Sep-17 8:18
professionalRickZeeland16-Sep-17 8:18 
GeneralRe: Well done ! Pin
Michael Haephrati18-Sep-17 3:28
professionalMichael Haephrati18-Sep-17 3:28 
Questionis it feasible for .NET application? Pin
Southmountain15-Sep-17 8:17
memberSouthmountain15-Sep-17 8:17 
AnswerRe: is it feasible for .NET application? Pin
Michael Haephrati18-Sep-17 3:28
professionalMichael Haephrati18-Sep-17 3:28 
QuestionUser right Pin
Davide Zaccanti13-Sep-17 19:45
memberDavide Zaccanti13-Sep-17 19:45 
AnswerRe: User right Pin
Michael Haephrati14-Sep-17 12:51
professionalMichael Haephrati14-Sep-17 12:51 
AnswerRe: User right Pin
Theo Buys18-Sep-17 5:12
memberTheo Buys18-Sep-17 5:12 
GeneralRe: User right Pin
Michael Haephrati18-Sep-17 23:54
professionalMichael Haephrati18-Sep-17 23:54 
AnswerRe: User right Pin
Michael Haephrati19-Sep-17 0:03
professionalMichael Haephrati19-Sep-17 0:03 
GeneralMy vote of 5 Pin
Kewin Rausch12-Sep-17 20:59
professionalKewin Rausch12-Sep-17 20:59 
GeneralRe: My vote of 5 Pin
Michael Haephrati13-Sep-17 0:14
professionalMichael Haephrati13-Sep-17 0:14 
GeneralMy vote of 5 Pin
Jim Forst11-Sep-17 20:03
memberJim Forst11-Sep-17 20:03 

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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170915.1 | Last Updated 11 Sep 2017
Article Copyright 2017 by Michael Haephrati
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid