Only one installer instance

From ISXKB

Jump to: navigation, search

Contents

Introduction

Autostart CDs and DVDs are quite a nice and handy feature of Windows.

The user sticks the media in the drive and the installation program starts automatically. Then he just leans back. But only if he knows that the CD or DVD has an autostart feature, and if he's not impatient. Many times he won't wait until the setup program comes up. He locates it manually, for instance with Windows Explorer, and double clicks on it, while the auto runner in the background just warms up.

Now he's faced with the setup program's GUI, but because the auto runner started the setup as well he ends up with two running setups.

Actually, the installer that was started second could just die silently and not confuse the user.

Solutions

As it is almost always with Windows, the operating system provides several ways to run only one instance of an application, in this case the installation program.

One solution could be to let the installer create a mutex. This could be done in InitializeSetup (). If the mutex already exists the Inno Setup script terminates the installation.

Excerpt from CreateMutex () at MSDN:

If you are using a named mutex to limit your application to a single instance, a malicious user can create this mutex before you do and prevent your application from starting. To prevent this situation, create a randomly named mutex and store the name so that it can only be obtained by an authorized user. Alternatively, you can use a file for this purpose. To limit your application to one instance per user, create a locked file in the user's profile directory.

Another option could be to use a semaphore object.

The solution in this article uses a different attempt. It tries to create a locked file. If it can't, another instance must be running already. The second instance reads the contents of this file which contains the window handle of the previous instance. This instance is then activated. This way the user gets the window back even after he clicked it away or if it is overlapped by some other window.

The code

; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "My Program"
#define MyAppVerName "My Program 1.5"
#define MyAppPublisher "My Company, Inc."
#define MyAppURL "http://www.example.com/"
#define MyAppExeName "MyProg.exe"

; Make this somehow unique, but don't use {tmp}. The second
;  instance would have a different {tmp}.
#define MyLockFile "{userappdata}\" + MyAppName + " Installer.lockfile"

[Setup]
AppName={#MyAppName}
AppVerName={#MyAppVerName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
OutputBaseFilename=OnlyOneInstanceSetup
Compression=lzma
SolidCompression=true

[Languages]
Name: english; MessagesFile: compiler:Default.isl
Name: german; MessagesFile: compiler:Languages\German.isl

[Tasks]
Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked

[Files]
Source: C:\Program Files\Inno Setup 5\Examples\MyProg.exe; DestDir: {app}; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: {group}\{#MyAppName}; Filename: {app}\{#MyAppExeName}
Name: {group}\{cm:UninstallProgram,{#MyAppName}}; Filename: {uninstallexe}
Name: {commondesktop}\{#MyAppName}; Filename: {app}\{#MyAppExeName}; Tasks: desktopicon

[Code]
const
    // The constants for CreateFile ().
    GENERIC_READ        = $80000000;
    GENERIC_WRITE       = $40000000;
    GENERIC_EXECUTE     = $20000000;
    GENERIC_ALL         = $10000000;
    FILE_SHARE_READ     = 1;
    FILE_SHARE_WRITE    = 2;
    FILE_SHARE_DELETE   = 4;
    CREATE_NEW          = 1;
    CREATE_ALWAYS       = 2;
    OPEN_EXISTING       = 3;
    OPEN_ALWAYS         = 4;
    TRUNCATE_EXISTING   = 5;

    // General Win32.
    INVALID_HANDLE_VALUE            = -1;

    // User32.
    SW_RESTORE          = 9;

var
    LockFileName	: String;
    hLockFile		: THandle;

// The required Win32 functions.
function CreateFile (
	lpFileName: String; // Could be PChar, but String is good enough for us here.
	dwDesiredAccess: Cardinal;
	dwShareMode: Cardinal;
	lpSecurityAttributes: Cardinal;
	dwCreationDisposition: Cardinal;
	dwFlagsAndAttributes: Cardinal;
	hTemplateFile:Integer
): Integer;
 external 'CreateFileA@kernel32.dll stdcall';

function WriteFile (
	hFile : THandle;
	lpBuffer : String;
	nNumberOfBytesToWrite : LongInt;
	var lpNumberOfBytesWritten: LongInt;
	lpOverlapped : LongInt
) : Boolean;
 external 'WriteFile@kernel32.dll stdcall';

function ReadFile (
	hFile : THandle;
	lpBuffer : String;
	nNumberOfBytesToRead : LongInt;
	var lpNumberOfBytesRead : LongInt;
	lpOverlapped: LongInt
) : Boolean;
 external 'ReadFile@kernel32.dll stdcall';

function CloseHandle (hHandle: INTEGER): INTEGER;
 external 'CloseHandle@kernel32.dll stdcall';

function IsWindow (hWnd: THandle) : Boolean;
 external 'IsWindow@User32.dll stdcall';

function ShowWindow (hWnd: THandle; nCmdShow : Integer) : Boolean;
 external 'ShowWindow@User32.dll stdcall';

function BringWindowToTop (hWnd: THandle) : Boolean;
 external 'BringWindowToTop@User32.dll stdcall';

function IsPreviousInstanceRunning (): Boolean;
// Returns TRUE, if the previous instance is running.
//  FALSE otherwise.
var
    stringPID   : String;
    BytesWritten: LongInt;
begin
    LockFileName := ExpandConstant ('{#MyLockFile}');
    hLockFile := CreateFile (LockFileName,
      GENERIC_WRITE,      // Desired access.
      FILE_SHARE_READ,    // Share mode - The second instance wants to read later.
      0,                  // Security attributes.
      CREATE_ALWAYS,
      FILE_ATTRIBUTE_TEMPORARY,
      0);
    if (INVALID_HANDLE_VALUE = hLockFile) then
    begin
      // File is still locked, ie. the first instance is still running.
      Result := TRUE;
      Exit;
    end;
    Result := FALSE;
end;

function MakePreviousInstanceActive () : Boolean;
var
    stringWnd : String;
    BytesRead : LongInt;
    hPrevWnd : THandle;
    Mult : THandle;
    i : Integer;
begin
    hLockFile := CreateFile (LockFileName,
      GENERIC_READ,// Desired access.
      FILE_SHARE_READ + FILE_SHARE_WRITE,
      0,                // Security attributes.
      OPEN_EXISTING,
      FILE_ATTRIBUTE_TEMPORARY,
      0);
    if (INVALID_HANDLE_VALUE = hLockFile) then
    begin	// The file's disappeared, ie. we should start up normally.
      Result := FALSE;
      Exit;
    end;
    // Expand the returned string from ReadFile () to avoid an access violation.
    stringWnd := StringOfChar ('#', 101);
    ReadFile (hLockFile, stringWnd, 100, BytesRead, 0);
    stringWnd := Copy (stringWnd, 0, BytesRead);
    CloseHandle (hLockFile);
    hPrevWnd := StrToInt (stringWnd);
    if IsWindow (hPrevWnd) then
    begin
      ShowWindow (hPrevWnd, SW_RESTORE);
      BringWindowToTop (hPrevWnd);
      Result := TRUE;
      Exit;
    end;
end;

procedure RemoveLockFile ();
begin
    CloseHandle (hLockFile);
    DeleteFile (LockFileName);
end;

procedure WriteWizardWindowHandle ();
var
    hMainWnd : THandle;
    stringWnd : String;
    BytesWritten: LongInt;
begin
    hMainWnd := WizardForm.Handle;
    stringWnd := IntToStr (hMainWnd);
    WriteFile (hLockFile, stringWnd, length (stringWnd), BytesWritten, 0);
end;

function InitializeSetup(): Boolean;
begin
    Result := TRUE;
    if IsPreviousInstanceRunning () then
    begin
      if MakePreviousInstanceActive () then
      begin
        RemoveLockFile ();
        Result := FALSE;
        Exit;
      end;
    end;
end;

procedure InitializeWizard;
begin
    // We can't do this before as we don't have a handle yet.
    WriteWizardWindowHandle ();
end;

procedure DeinitializeSetup();
begin
    RemoveLockFile ();
end;

See also

External links

Personal tools
Ads: