Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Silent Application Update


November, 2004: Silent Application Update

Zuoliu Ding is a software engineer and adjunct faculty in Southern California. He can be contacted at [email protected].


Online self-updates are among the more useful features of today's software, as most vendors choose downloading from the Internet as an efficient way to patch or upgrade their software. In some instances, "silent" background updates are preferred to those that require user interaction. In such cases, applications monitor the most recent version of itself from the vendor's web server. If updates are available, the application downloads the new components, installs them, and informs (or not) users of changes. The end result is that users find the updated application running the next time they use it.

The Windows Update is a typical automatic update that has "nonsilent" features, such as update notification, scanning, and history. In Windows NT/2000/XP, silent updates can transpire when applications are in use (in runtime) without users rebooting the computer. In Windows 9x/ME, on the other hand, silent updates take effect when you restart Windows.

In this article, I present a technique for updating standalone executables. For illustration purposes, I create a sample program called "sUpdate.exe" to update itself and its configuration and image files. While this program is written to run on XP, with a little work, it will run on Windows 9x/ME and other platforms. The program, source code, and related files are available electronically; see "Resource Center," page 5.

Working with sUpdate.exe

The sample package SUPDEMO.ZIP (available electronically) includes sUpdate.exe and its icon in the Demo directory.

As Figure 1 shows, sUpdate displays the version number 1.0.0.0 and the current connection, with a browser window opened at the bottom for site navigation. Two radio buttons let you either update from a preuploaded site or from a localhost where you have to manually upload the files. For now, choose the test site, and after a few seconds the top line changes to "Some New Update will be Available in Next Session...".

The magic happens when you close—then reopen—sUpdate. Now, the upgrade version 2.0.0.0 is equipped with a URL address box and navigation buttons; see Figure 2. Obviously, sUpdate.exe has been changed. Also notice that the icon is changed to a smiley-face with a new sUpdate.ico. I separated the icon from sUpdate.exe for flexibility, in case you need to update a logo icon only. Moreover, you find two new downloaded files—maplewood.bmp for background and sconfig.xml for UI configurations.

How does this happen? Look first at localhost for the answer. In my package, all relevant files are provided in the myupdate directory. What you need to do is upload it to the localhost, either copying myupdate to a directory such as "C:\Inetpub\wwwroot," or using IIS tools to create a virtual directory under a default web site. The result should look like Listing One.

In the file updateinfo.xml (Listing Two), the server should provide the necessary information for all updates. Consequently, I identify each download file by its tag name, file version number on the server, file size, and download location. A location should correspond to a server URL where that file is uploaded, as compared to Listing One. Once you set up the localhost files, you can test sUpdate locally. Choose the second option in Figure 1, hold until you see the prompt, then close and reopen.

File Download

Example 1 is pseudocode that describes a file download algorithm. Since the update should not block the main process, I create a work thread to deal with a possible lengthy download that may or may not be successful.

In Listing Three, the thread procedure is called ThreadDownloadFiles() where the helper functions reside in hlpfuncs.h/.cpp and the class CAppInfo in appinfo.h/.cpp retrieves data in XML.

In ThreadDownloadFiles(), I first call StreamDownload() to read updateinfo.xml into memory. If it is okay, I use a CAppInfo object to retrieve the information. Next in IsFileNew(), I compare the local file version number with that on the server. If this file needs to update, I call FileDownload() to download it with the .new extension. To check the file integrity, I compare the download file size with that from the server. If equal, I save the new file version and name in the registry as an update flag for the next step.

The file updateinfo.xml plays an important role in my demo. However, in a real application, you need to consider much more. For instance, you may have more files to download and additional fields to identify files. You may need, for example, to add a checksum to verify file integrity or apply some method to encrypt/decrypt XML contents. Additionally, you may need to use a strict version comparison instead of a simple string comparing in my IsFileNew().

When and where is the ThreadDownloadFiles() thread triggered? You can start it minutes after a main process initialization or in an idle loop. But in an application session, you should invoke it only once without a repeated call. So in sUpdate, I create a work thread in the message handler OnMouseMove() (the second function in Listing Three), and to avoid a second call, I set the flag m_nUpdateStatus to _DOWNLOAD_SUCCESS or _DOWNLOAD_ERROR when the thread returns. Considering the slow download in a dial-up connection, I wait five minutes for its return.

File Update

In the file update step, an application must update itself as soon as it detects a new download. If a download is an image or XML, simply copy it to the working directory in idle time. But if a download is an executable, copying is denied if it currently is in use. Fortunately in XP, you can often successfully rename a running .exe file. Hence, the whole procedure is obtained in Example 2.

The function IsExeFileUpdated() (Listing Four) is just such an implementation in sUpdate, where I first check the registry to see whether a new executable is available. If it is, I verify the file existence and call MoveFileEx() to rename the running sUpdate.exe with the ".old" extension. If the renaming works, I copy the downloaded file to sUpdate.exe, do cleanup, and save the new version number. For other files, just copy them, do cleanup, and save.

IsExeFileUpdated() must be called before an application starts a message loop and displays its UI. The startup WinMain() is ideal; see the second function in Listing Four.

For simplicity, I make sUpdate a Singleton, maintaining one instance all the time, which is also required by some real-world products. This is why I first search a previously opened sUpdate and make it visible if it's iconic or hidden. Only when it is opened as a new instance do I call IsExeFileUpdated() to check a new download. If IsExeFileUpdated() returns TRUE, meaning that an updated executable is already in place, I use ShellExecute() to create a new process and terminate the current one. Otherwise, the current process continues, with Run() starting a message loop and creating the UI in CMainDlg (in MainDlg.h/.cpp).

The last issue involves updating in 9x/ME, where MoveFile/MoveFileEx() fails to rename running .exe files. You must modify MoveFileEx() with its if clause in IsExeFileUpdated() and do not call ShellExecute() in WinMain(). Here, I simply describe the implementation outline for your reference. First, programmatically create a batch file like sUpdate.bat, write to it the desired file copying and deletion commands, close it, and then set its full path to the registry key RunOnce (Listing Five). The update uses sUpdate.bat to replace sUpdate.exe. If you open the updated application, it detects itself and saves the new version number locally. (See MSDN at ms-help://MS.VSCC/MS.MSDNVS/ fileio/filesio_9oe0.htm for other implementations.)

Configurable UI Update

You may have noticed a configurable user interface in the updated sUpdate in Figure 2, where the buttons and background are determined by a new downloadable sconfig.xml with a back image maplewood.bmp. This makes user-interface updating flexible and eliminates the need for code rebuilding. For instance, suppose you want to change the background from maplewood to blue sky and reselect/rearrange the navigation buttons. You only need to upload a new sconfig.xml and bluesky.bmp, as well as a new updateinfo.xml with two files—version number and size changed accordingly. Figure 3 is the result, and the browser displays this sconfig.xml telling how to organize the buttons and which the background image is.

You can experiment with this UI update further using the localhost yourself. Although I let you choose update sites in the first sUpdate in Figure 1, in the second sUpdate, I set the update URL to the localhost. Actually, it is named supdate2.exe in updateinfo.xml (Listing Two) as you recall.

Dig deeper and you might find where I changed the download file name to the default (switching supdate2.exe to supdate.exe) or a requirement for the original name (bluesky.bmp as recognized in sconfig.xml). This can all be based on your requirements and I purposely demonstrate some alternatives. For illustration, I control the file-name format in the function CAppInfo::GetFileName(), which can be seen in source code.

Conclusion

To fully understand and make use of the code in this article, an understanding of WinInet, URL downloads, file streams, and browser objects is helpful. In Internet programming with UIs, a lightweight WTL project is often preferable to those of MFC and ATL. My sample sUpdate works with Microsoft Visual C++ 7.0 and WTL 7.0, so you have to install WTL 7.0 in Microsoft Visual Studio IDE before compiling. If you need a reference to WTL, search http:// www.codeproject.com/. My source code includes both sUpdate and sUpdate2 projects, where sUpdate2 shares nearly all functions from sUpdate, a tricky derivation as you might imagine.

DDJ



Listing One

localhost/myupdate/
                   updateinfo.xml
                   supdate2.exe 
                   sconfig.xml
                   image/
                         supdate.ico
                         maplewood.bmp
Back to article


Listing Two
<!-- updateinfo.xml -->
<SilentUpdate>
  <supdate.exe>2.0.0.0|110592|http://localhost/myupdate/supdate2.exe
     </supdate.exe>
  <supdate.ico>1.2.0.0|1078|http://localhost/myupdate/image/supdate.ico
     </supdate.ico>
  <sconfig.xml>1.5.0.0|307|http://localhost/myupdate/sconfig.xml
     </sconfig.xml>
  <background.bmp>1.5.0.0|49206|http://localhost/myupdate/image/  
                                      maplewood.bmp</background.bmp>
</SilentUpdate>
Back to article


Listing Three
/*--------------------------------------------------------------
Function: ThreadDownloadFiles
Description: Monitor new version and download files
Parameter: p: CMainDlg object
Author: Zuoliu Ding
--------------------------------------------------------------*/
DWORD WINAPI ThreadDownloadFiles(void *p)
{
 CMainDlg* pMainDlg = (CMainDlg*)p;
 if (pMainDlg->m_nUpdateStatus!=_DOWNLOAD_NOSTART) return 0;

 CString sInfo = StreamDownload(pMainDlg->m_sUpdateUrl);
 if (sInfo.IsEmpty()) Return(_DOWNLOAD_ERROR);

 CAppInfo AppInfo;
 if (!AppInfo.LoadUpdateXml(sInfo)) Return(_DOWNLOAD_ERROR);

 int nRet =_DOWNLOAD_CURRENT, nDownloadSize, nServerFileSize;
 CString sInfoName, sFile, sTmpReg;
 for (int i=0; i<APPINFO_COUNT; i++)
 {
 sInfoName = szFiles[i];
 if (AppInfo.IsFileNew(sInfoName))
 {
 sFile = GetModulePath() +AppInfo.GetFileName(sInfoName) +_NEW;
 nDownloadSize = FileDownload(AppInfo.GetDLUrl(sInfoName), sFile);
 nServerFileSize = AppInfo.GetSize(sInfoName);
 if (nDownloadSize == nServerFileSize)
 { 
 // e.g.: 2.0.0.0|bluesky.bmp
 sTmpReg = AppInfo.GetVersion(sInfoName) +"|" 
 +AppInfo.GetFileName(sInfoName);
 SaveRegKey(sInfoName +_NEW, sTmpReg);
 nRet =_DOWNLOAD_SUCCESS;
 }
else DeleteFile(sFile);
 }
 }
 Return(nRet);
}

/*--------------------------------------------------------------
Function: OnMouseMove - member function, meggage handler
Description: Check update once in an application session
--------------------------------------------------------------*/
LRESULT CMainDlg::OnMouseMove(UINT, WPARAM, LPARAM, BOOL&)
{
 if (m_nUpdateStatus ==_DOWNLOAD_NOSTART && 
 !m_sUpdateUrl.IsEmpty())
 {
 DWORD dwThreadId;
 HANDLE h = ::CreateThread(NULL, 0, ThreadDownloadFiles, 
 (LPVOID)this, 0, &dwThreadId);
 // for 5 minutes
 if (WaitForSingleObject(h, 300000) == WAIT_OBJECT_0) 
 {
 if (m_nUpdateStatus ==_DOWNLOAD_SUCCESS)
 {
 SetDlgItemText(IDC_STATIC_TEXT, 
 "Some New Update will be Available in Next Session...");
 }
 } 
 else m_nUpdateStatus =_DOWNLOAD_ERROR; 

 CloseHandle(h);
 }

 return 0;
}
Back to article


Listing Four
/*--------------------------------------------------------------
supdate.cpp: Main source file for supdate.exe
Author: Zuoliu Ding
--------------------------------------------------------------*/
#include "stdafx.h"
#include "hlpfuncs.h"

BOOL IsExeFileUpdated()
{
 CString sInfoName, sFile;
 BOOL bRet = FALSE;
 CString sNewVersion = ReadRegKey(_EXE_FILE +_NEW);
 int nPos = sNewVersion.Find('|'); //e.g. 2.0.0.0|bluesky.bmp

 if (!sNewVersion.IsEmpty() && -1 !=nPos)
 {
 sFile = GetModulePath() +sNewVersion.Mid(nPos+1);
 sNewVersion = sNewVersion.Left(nPos);
WIN32_FIND_DATA FindFileData;
 HANDLE hFindFile = FindFirstFile(sFile +_NEW, &FindFileData);

 if (hFindFile != INVALID_HANDLE_VALUE) 
 {
 CString sFileOld = sFile +_OLD;
 DeleteFile(sFileOld); // if any

 // 95/98/ME: MoveFile/MoveFileEx here is Unsupported.
 if (MoveFileEx(sFile, sFileOld, NULL)) 
 { 
 CopyFile(sFile +_NEW, sFile, FALSE);
 DeleteFile(sFile +_NEW); 
 SaveRegKey(_EXE_FILE, sNewVersion);
 DeleteName(_EXE_FILE +_NEW);
 bRet = TRUE;
 }
 } 

 if (hFindFile != INVALID_HANDLE_VALUE) 
         CloseHandle(hFindFile); 
 }

 // Simply copy other files
 for (int i=1; i<APPINFO_COUNT; i++)
 {
 sInfoName = szFiles[i];
 sNewVersion = ReadRegKey(sInfoName +_NEW);
 int nPos = sNewVersion.Find('|'); 
 if (!sNewVersion.IsEmpty() && -1 !=nPos)
 {
 sFile = GetModulePath() +sNewVersion.Mid(nPos+1);
 sNewVersion = sNewVersion.Left(nPos);
 CopyFile(sFile +_NEW, sFile, FALSE);
 DeleteFile(sFile +_NEW); 
 SaveRegKey(sInfoName, sNewVersion);
 DeleteName(sInfoName +_NEW);
 }
 }

 return bRet;
}

// Main Module and WinMain
CAppModule _Module;
static const GUID libId = 
{0x1980abfa,0xaf4f,0x45e8,{0xb7,0xbc,0xf1,0x9e,0x63,0x6e,0x26,0x8e}};

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst, 
 LPTSTR lpstrCmdLine, int nCmdShow)
{
 HRESULT hRes = ::CoInitialize(NULL);
 ATLASSERT(SUCCEEDED(hRes));
 ::DefWindowProc(NULL, 0, 0, 0L);
 AtlInitCommonControls(ICC_BAR_CLASSES); 
 hRes = _Module.Init(NULL, hInstance, &libId);
 ATLASSERT(SUCCEEDED(hRes));
 AtlAxWinInit();
 int nRet = 0;

 HWND hWnd = ::FindWindow(NULL, _APP_TITLE); 
 if (hWnd)
 {
 if (IsIconic(hWnd))
 ShowWindow(hWnd, SW_RESTORE);
  else
 SetForegroundWindow(hWnd);
 }
 else
 {
 if (IsExeFileUpdated())
 ShellExecute(NULL, "open", GetModulePath() +_EXE_FILE, 
 NULL, NULL, SW_SHOWNORMAL);
 else
 nRet = Run(lpstrCmdLine, nCmdShow);
 }

 _Module.Term();
 ::CoUninitialize();
 return nRet;
}
Back to article


Listing Five
HKEY_LOCAL_MACHINE\
  SOFTWARE\
    Microsoft\
      Windows\
        CurrentVersion\
          RunOnce\
            sUpdate c:\...\sUpdate.bat
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.