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

Tools

Automatic Updates for Distributed Applications


Jul00: Automatic Updates for Distributed Applications

Max is a computer consultant and senior research associate with Ultrason image processing. He can be contacted at maxf@ webzone.net.


Over time, the costs of software installation, maintenance, upgrades, and support can exceed those for development. In particular, distributing upgraded applications to users can be time and money consuming, especially when the number of users is large and the process occurs on a regular basis. Of course, software vendors are addressing the issue of software distribution. Microsoft's System Management Server (SMS), Intel's LANdesk, and Novell's Application Launcher (NAL) are among the emerging breed of software-distribution tools. However, you can also build automated update capabilities into your application itself. This technique is especially useful for distributed multiuser applications, particularly when updates roll out frequently. (This is typical of client/server applications undergoing phases of regular revisions/enhancements when new releases must be distributed to users ASAP.)

Since most geographically distant users have access to a corporate LAN or the Internet, you can use a software-distribution server that isn't a dedicated server. It can be sufficient to create a file share for LAN/WAN or FTP accounts for Internet access, and keep the latest versions of application files in appropriate subdirectories. The approach I present here, called "AutomatedUpdate," lets applications transparently check file versions located on specified LAN/WAN shares or FTP servers, and perform a necessary upgrade/install when newer versions are detected. This technique can be incorporated in shared DLLs or static libraries and used by many different applications. (Source code, executables, and related files that implement AutomatedUpdate are available electronically; see "Resource Center," page 5.)

In short, AutomatedUpdate keeps a single copy of the always up-to-date application files on a software-distribution server. Whenever a new release is available, files on the server are simply overwritten or replaced. When remote users launch the application powered by AutomatedUpdate, it logs onto the specified file or FTP server and checks for an upgrade. If an upgrade is available, users are notified and, based on their decision, a new executable or self-extracting installation package is downloaded and executed. If the download fails, the old program is still available (saved with extension .OLD).

Of course, an AutomatedUpdate-enabled program must be installed on user workstations. This can be done manually by downloading/installing from the Web (using, for instance, InstallShield's InstallFromTheWeb software), or by using commercially available tools such as LANdesk, NAL, or SMS. In a constant development environment, most application updates only involve executables. In this case, executable versions can be compared to determine newer versions. However, when an update requires more sophisticated steps (such as the replacement of DLLs or writing information to the system registry), AutomatedUpdate relies on self-extracting single-file installation programs, which can be built using InstallShield or similar software.

Ideally, the application subdirectory on a software-distribution server contains the most recent version of the application executable or self-extracting installation Setup.exe, along with a VERSION.INF file. The VERSION.INF is required for FTP downloads or self-extracting installations. It must contain a line of text describing the current version of the application (for example, 1.0.1). With FTP access, you can't get file-version information without downloading the entire file. Moreover, InstallShield-generated self-extracting installations do not contain version information in the executable file. Thus, to inform AutomatedUpdate about the availability of updates, both program versions must be changed (that is, the version resource modified prior to compilation) and a new VERSION.INF created. AutomatedUpdate includes an AutomatedUpdate Manager that facilitates configuration, tests FTP/LAN/ WAN connections, and uploads a new executable or self-extracting installation to the software-distribution server. In this case, the VERSION.INF file is created automatically.

The AutomatedUpdate DLL

The AutomatedUpdate DLL exports three functions:

  • extern "C" void auInit(LPCSTR Company, LPCSTR AppName, HINSTANCE hInstance, void* pMainFrm = NULL, LPCSTR Server = NULL, LPCSTR UserName = NULL, LPCSTR Password = NULL);, for initialization.
  • extern "C" BOOL auCheck(BOOL bSilent = FALSE);, to check for application updates.

  • extern "C" BOOL auManage();, which displays the AutomatedUpdate manager dialog.

The auInit function, which must be called first, initializes the AutomatedUpdate library (available electronically) and sets up some internal variables. The Company and AppName parameters are required to access the system registry key HKEY_LOCAL_MACHINE\Software\Company\AppName\AutomatedUpdate under which the AutomatedUpdate configuration parameters are stored. Also, AppName specifies the name of the application and application file's subdirectory on the software-distribution server. hInstance, the handle of the application instance, determines the application executable path (installation directory). pMainFrm is an optional CFrameWnd pointer of the MFC-based application window. It is needed for displaying status bar messages. Server, UserName, and Password are optional parameters that specify AutomatedUpdate configuration. These parameters are used only if the AutomatedUpdate configuration does not exist in the system registry. When AutomatedUpdate is used for the first time, it is necessary to provide these parameters so the program knows where to check for updates. Server can be specified in the form \\SERVER\SHARE for LAN/ WAN, or SERVER\PATH for FTP connection. The path should not contain any trailing slashes. Also, you should pay attention to lowercase or uppercase file names. Normally, file shares are case insensitive, but FTP directories and filenames are case sensitive. Thus, UPDTEST.EXE and UpdTest.exe are two different file names when using FTP access. The application executable file and folder name spelling is determined by the AppName parameter. The extensions (.exe) are assumed to be lowercase, while VERSION.INF is spelled in uppercase letters. auCheck, the most important call, logs onto the specified software-distribution server and displays an upgrade notification message. If an update is available and users choose to proceed, the master process will exit without returning from the call. After successfully downloading the new executable or self-extracting file, control is passed to the UPDATE.EXE (executable) or Setup.exe (self-extracting installation) process, and the master process is terminated. The master program must then be terminated so the executable and related DLLs can be overwritten. When UPDATE.EXE terminates, it displays a prompt to launch the new executable for users to continue work. With the self-extracting installation process, the set-up program may execute a new application once the set up is complete. If UPDATE.EXE is not found or an error occurs, an appropriate error message is displayed.

The bSilent parameter specifies whether to display a message indicating the program is up to date. It is convenient to pass the default (True) value of the parameter for silent update checks at program load time so the message won't annoy users. However, when this function is called from the menu, it makes more sense to allow the message to be displayed. The return value of auCheck is True if no errors were encountered and the program appears to be up to date; otherwise it returns False. auManage displays the AutomatedUpdate Manager dialog window. AutomatedUpdate Manager allows specification of configuration parameters, such as software-distribution server name, user name, and password for file-share mapping or FTP access. The Check button tests the connection to the software-distribution server and displays an error message if the attempt fails. The Clear button deletes all files from the specified subdirectory on the software-distribution server. This is useful for stopping automatic updates. Finally, the Upload button allows administrators to select a new program executable or self-extracting installation file and upload it to the server.

For the application executable, VERSION.INF is automatically generated based on version information in the program file. For SETUP.EXE, a dialog box is displayed and users must manually enter the new version number. When the AutomatedUpdate Manager dialog is closed (by clicking the OK button), the new configuration information is written to the system registry. The AutomatedUpdate library uses MFC Internet classes and relies on WinInet for FTP access (needless to say, TCP/IP protocol and WININET.DLL must be installed). The code (available electronically) is written for Microsoft Visual C++ and uses MFC. For the most part, the code is straightforward. Note, however, the auCheck function. All exported functions are defined with extern "C" to avoid decorated names and enable linking with nonC++ programs. Also, remember that the first statement in the exported function in MFC DLLs must be AFX_MANAGE_ STATE(AfxGetStaticModuleState()). It is required to ensure proper MFC library initialization. Next, the WinInet CInternetSession and CFtpConnection class pointers are initialized (see Listing One) and network resource structures for remote file-share access are defined (Listing Two). All subsequent operations appear inside a Try-Catch-All loop to ensure proper exception handling. This routine also throws custom exceptions (CSysException) to display system error messages that result from the error codes returned by WinNetwork API calls. For FTP access, the CInternetSession object is created and an FTP connection is established as in Listing Three.

The file VERSION.INF is read using the CInternetFile class instance. For file-share access networks, the resource structure must first be initialized, as in Listing Four. Then a network connection is established using the WinNetwork WNetAddConnection2 function (Listing Five). It is then possible to treat a remote file version as if it is a local file; see Listing Six. When the UserName parameter is not initialized, there is no need for an explicit file-share mapping. When users are logged onto the Windows NT domain and the software-distribution server is configured to be accessible for everyone, it is sufficient to use standard Win32 API calls such as CopyFile or GetFileVersion (or MFC classes such as CFile) to access remote files. However, if the software-distribution server share requires a password, WNetAddConnection2 must first be called to perform necessary authentication; otherwise CopyFile will fail.

The CFileFind class in Listing Seven is used for finding files on the server share. Listing Eight reads the application version from VERSION.INF (if it exists), compares the executable version with the version in INF file, and selects the most recent one. This is necessary for resolving ambiguities. If the new application version is greater than the current one, a message is displayed; see Listing Nine. It is a good idea to save an old executable in order to recover from AutomatedUpdate failures (Listing Ten). The master process ID must be obtained. It is needed so that UPDATE.EXE will wait for the master process to terminate; see Listing Eleven. Listing Twelve downloads either a new executable or self-extracting installation from the FTP server and executes UPDATE.EXE or SETUP.EXE accordingly. UPDATE.EXE takes three parameters: the downloaded file name (which must be different from the executable name since both files are located in the same directory); the application executable file name; and the master process ID (PID). UPDATE.EXE uses the OpenProcess call to obtain the process handle specified by the PID, and uses WaitForSingleObject to wait for the master process to terminate so it can overwrite its executable module. Then it copies a new executable over the old one and executes it. Listing Thirteen is analogous to the FTP-related code just mentioned and performs the same function except for the fact that no file download occurs. Indeed, there is no need to download files from the remote file server. Once you map the share, you can use it in command-line statements, thus it is sufficient to execute SETUP.EXE by specifying its path or to pass the remote executable path as a parameter for UPDATE.EXE. Another trick is to display error messages after calls to _execl. If the call is successful, the calling process will terminate; otherwise, program flow will continue, indicating a process execution error. Finally, a message is displayed based on the value of the bSilent parameter. WinInet objects are disposed and the network connection is closed; see Listing Fourteen.

It is easy to use the AutomatedUpdate technique with an application. It is sufficient to initialize the DLL by calling auInit, then calling the auCheck. In the UpdTest MFC demo application (available electronically), both calls are inserted at the end of the CUpdTestApp::OnInitInstance handler (Listing Fifteen).

Testing the AutomatedUpdate

After downloading and unzipping the AutomatedUpdate source code and demo application:

1. Create a file share on the same computer or on a network server.

2. Make an UpdTest folder on the created share.

3. Run UpdTest.exe from the V2_0_0 directory for the first time. Since the compiled program contains a dummy file share name (\\MYCOMP\UPDATES), nothing will happen.

4. Click the first toolbar button or select menu Update|Setup to bring up the AutomatedUpdate manager window.

5. Specify the correct server path without a trailing \ corresponding to the created file share, and click the Upload button. In the File Open dialog, select UpdTest.exe from the V2_1_0 directory.

6. Close the AutomatedUpdate manager dialog by clicking OK, and close the application. At this point, the AutomatedUpdate configuration is complete. The new version of the application is uploaded to the file share.

7. Run UpdTest.exe from the V2_0_0 directory for the second time. A newer version of the "program available" message will come up. Click Yes. The "program successfully updated" message will show up. Click the Yes button again. Version 2.0.1 of the test executable will automatically load at this point. These steps are necessary to set up an AutomatedUpdate-enabled application. However, you should embed the correct file share/FTP address into the application executable to make the AutomatedUpdate work right away.

Conclusion

AutomatedUpdate is an efficient, simple, and cost-effective way of delivering application updates to remote users. It requires little support and can be customized in a number of ways. For instance, FTP/WinInet support can be dropped and the software-distribution server configured for public access for all domain users. In such a case, standard file operations allow extensive manipulation of remote files, thereby reducing the size and complexity of the code.

DDJ

Listing One

CInternetSession* pInetSession = NULL;
CFtpConnection* pFtpConnection = NULL;

Back to Article

Listing Two

// WinNetwork resource variable
NETRESOURCE nr;

Back to Article

Listing Three

// Create INet session
pInetSession = new CInternetSession(NULL, 1, PRE_CONFIG_INTERNET_ACCESS,
                                     NULL, NULL, INTERNET_FLAG_DONT_CACHE);
// Create FTP connection
pFtpConnection = pInetSession->GetFtpConnection(FtpSite, UserName, Password);
CFtpFileFind object is used for locating application files on remote FTP
server:
CFtpFileFind FF(pFtpConnection);

// Find Setup.exe
bSetup = FF.FindFile(RemoteSetup);

// Make sure that either application EXE or Setup.exe exists
if ( bSetup || FF.FindFile(RemoteExe) )
{
    // Open and read .INF file
    CInternetFile* pFile = pFtpConnection->OpenFile(RemoteInf);
    if ( pFile )
    {
        pFile->ReadString(Version);
        delete pFile;
    }
}

Back to Article

Listing Four

nr.dwType = RESOURCETYPE_DISK;
nr.lpLocalName = nr.lpProvider = NULL;
nr.lpRemoteName = (LPSTR)(LPCSTR)Server;

Back to Article

Listing Five

if ( UserName != "" && ::WNetAddConnection2(&nr, Password, UserName, 0)
    != NO_ERROR )
    THROW(new CSysException());

Back to Article

Listing Six

// Get application executable version
GetFileVersion(RemoteExe, Version);

Back to Article

Listing Seven

// Find Setup.exe
bSetup = CFileFind().FindFile(RemoteSetup);

Back to Article

Listing Eight

// Get the version number from .INF file
if ( bSetup )
{
    CStdioFile File;
    // Open and read .INF file
    if (  File.Open(RemoteInf, CFile::modeRead|CFile::typeText) )
    {
        CString SetupVersion;
        File.ReadString(SetupVersion);
        if ( SetupVersion > Version )
        {
        bSetup = TRUE;
            Version = SetupVersion;
        }
    }
}

Back to Article

Listing Nine

// Compare versions
if ( Version > AppVersion )
    if ( AfxMessageBox(IDS_UPDATE_PROCEED, MB_YESNO|MB_ICONQUESTION) ==
IDYES )
    {
        ::SetStatus("Writing new executable to disk...");
        ...

Back to Article

Listing Ten

if ( !::CopyFile(AppExe, AppOld, FALSE) )
    THROW(new CSysException());

Back to Article

Listing Eleven

// Obtain process ID
CString PID;
PID.Format("%i", ::GetCurrentProcessId());

Back to Article

Listing Twelve

if ( bUseFTP )
{
    // Download and run Setup.exe
    if ( bSetup )
    if ( pFtpConnection->GetFile(RemoteSetup, SetupExe, FALSE) )
   _execl(SetupExe, SetupExe, NULL);
    else
            THROW(new CSysException());
    else
        // Download new application executable
        if ( pFtpConnection->GetFile(RemoteExe, AppNew, FALSE) )
            _execl(UpdExe, UpdExe, AppNew, AppExe, PID, NULL);
        else
            THROW(new CSysException());
}

Back to Article

Listing Thirteen

if ( bSetup )
{
// Run Setup.exe
    _execl(RemoteSetup, RemoteSetup, NULL);
    AfxMessageBox(IDS_SETUP_FAILED);
}
else
{
// Replacing application executable
    _execl(UpdExe, UpdExe, RemoteExe, AppExe, PID, NULL);
    AfxMessageBox(IDS_UPDATE_FAILED);
}

Back to Article

Listing Fourteen

if ( !bSilent ) AfxMessageBox("Progam is up to date.");
// Clean up
if ( pFtpConnection != NULL )
delete pFtpConnection;
if ( pInetSession != NULL )
    delete pInetSession;
// Close network connection
if ( !bUseFTP )
    ::WNetCancelConnection2(Server, 0, FALSE);

Back to Article

Listing Fifteen

// Initialize AutomatedUpdate
auInit("Helix", "UpdTest", m_hInstance, m_pMainWnd, "\\\\JMYCOMP\\UPDATES");
// Check for update
auCheck(TRUE);

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.