Auto Update mechanism
Buy the 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:
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 defined as follows:
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.exesoftware.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
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.
When there is a newer version it will be downloaded to a temporary file (for example _software.exe).
_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.
software.exe will now start _software.exe as a new process.
software.exe quits.
_software.exe copies itself to software.exe, which is possible since software.exe quit.
_software.exe starts software.exe as a new process.
_sotware.exe quits.
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:
After running it, you will see the same file but in fact, it is a different one. It's the newer version:
You can of course use your own website.
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 with any other address. Here are the instructions for testing this mechanism:
Build the source code and keep SG_AutoUpdate.exe somewhere (or rename it temporarily).
Go to the project's resources, find the version string, and change it to 1.0.0.2.
Build the source code again.
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.
All names are case-sensitive.
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.
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
댓글