Force log file with Inno Setup versions before 5.2.0

From ISXKB

(Difference between revisions)
Jump to: navigation, search
(too many brackets)
Current revision (11:12, 21 September 2009) (view source)
m (bare -> bear)
 
Line 233: Line 233:
</pre>
</pre>
-
Bare in mind that this solution only works on systems with Windows NT or higher.
+
Bear in mind that this solution only works on systems with Windows NT or higher.
== Source Code of StartHelper.exe ==
== Source Code of StartHelper.exe ==

Current revision

Note: This article is for installers created with Inno Setup below version 5.2.0 (2007-09-19). See Inno Setup's revision history for more details. Installers created with Inno Setup 5.2.0 and newer can use [Setup]: SetupLogging and the constant {log} to achieve similar effects.

The article Force log file explains how to force an installation log file with newer versions of Inno Setup.


Contents

Introduction

Installers created with Inno Setup support the command line parameters /LOG and /LOG="filename" to trace issues during the setup process.

Unfortuately users or customers are not always aware of how to enter these parameters. Even after explained on the phone or by email, they finally struggle or fail to send the log file back to the developer.

Solution

There's several ways to make sure that a log file is always created and put into the application's directory or somewhere else.

The first option would be to write a wrapper application for the setup. This wrapper could even be an Inno Setup installer itself. It simply starts itself again with the /LOG parameter, hides its main window, waits until the newly invoked installer finishes, and finally copies the log file to the desired location before it terminates.

The following example uses a different approach. It does in fact invoke itself again with the /LOG parameter but then terminates immediately so that there is no second installer running in the background. Since the new installer can't copy its own log file (because it is locked) it creates a small command line batch script upon termination and invokes it in a hidden window. This script then waits a few seconds and copies the log file.

It either copies the installation log file in the application's folder or, for instance, in a sub folder 'Logs' underneath the application's installation folder. It uses two functions, ForceLogFile () and MoveLogFile () that need to be called from within InitializeSetup () and DeInitializeSetup (), and additionally a helper application to invoke the setup again with /LOG=.

The solution is written so that it can be added to any script as easily as possible. The variables are stored in registry values and are deleted later again, so there's no need to change the tree to reflect your company and application's name, if you want to save some time implementing the solution. Admittingly, many people would suggest to store the log file's name in a global variable, but that would require some more changes to the main script.

Implementation

Here's a suggestion on how to implement it:

Create a file called 'ForceLogFile.pas' or something similiar in the setup working directory and copy and paste both functions into it. This way you can easily reuse ForceLogFile.pas again for other scripts.

In your Inno Setup script, before InitializeSetup () and DeInitializeSetup (), include this line into the [Code] section.

#include "ForceLogFile.pas"


In the variables section of InitializeSetup () the 'Result' variable is required:

var
 Result : Boolean;

As the first operation in InitializeSetup () use this line (where 'MyAppInstall' should be replaced with something more meaningful, but without a file name extension):

Result := ForceLogFile ('MyAppInstall'); if (Result = FALSE) then Abort;

As the last operation in DeInitializeSetup () this line needs to be inserted:

MoveLogFile ('Logs', TRUE);

An empty string here (instead of 'Logs') would not copy the log file into a sub folder, but directly into the application's directory:

MoveLogFile ('', TRUE);

The Boolean parameter tells the function whether a date string should be added to the file name.


In the [Files] section, add the following line:

Source: StartHelper.exe; Flags: dontcopy

You will have to download and extract 'StartHelper.exe' into your setup's working directory first: StartHelper.zip 16KB. The source code for the program, or most of it, can be found further down.

ForceLogFile.pas

Content of 'ForceLogFile.pas' (copy and paste the code into that file):

function ForceLogFile (sMyLogFile : String): Boolean;
// Don't use a file name extension for sMyLogFile here!
//  It will be .log.
var
	Errorcode	: Integer;
	sLanguage	: String;
	sMLogFileName	: String;
	sCMDPars	: String;
begin
	//MsgBox (GetCmdTail, mbInformation, MB_OK);
	if Pos ('/LOG=', Uppercase (GetCmdTail)) = 0 then
	begin
		sLanguage := '';
		// Retrieve the language. We don't want the language
		//	dialog again in our new setup instance.
		if Pos ('/LANG=', Uppercase (GetCmdTail)) = 0 then
		begin
			sLanguage := ' /LANG=' + ActiveLanguage;
		end;
		sMLogFileName := GenerateUniqueName (ExpandConstant ('{%TMP}'), '.log');
		//sBatName := GenerateUniqueName (ExpandConstant ('{%TMP}'), '.cmd')
		ExtractTemporaryFile ('StartHelper.exe');
		
		// Use the registry instead of global variables. That makes the functions
		//  more portable.
		RegWriteStringValue (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars',
			'sMLogFileName', sMLogFileName);
		RegWriteStringValue (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars',
			'sMyLogFile', sMyLogFile);
		sCMDPars := '"' + ExpandConstant ('{srcexe}')+ '"' + ' ' +
			GetCmdTail + ' "/LOG=' + sMLogFileName + '"' + sLanguage;
		//MsgBox (sCMDPars, mbInformation, MB_OK);
		if Exec (Expandconstant ('{tmp}\StartHelper.exe'),
			sCMDPars,                 {The parameters}
			ExpandConstant ('{src}'), {Working dir.}
			SW_HIDE, ewNoWait, Errorcode) then
		begin
		end else
		begin
			if ErrorCode <> 0 then
			begin
				msgbox ('Error: ' + IntToStr (ErrorCode), mbInformation, MB_OK);
			end;
		end;
		Sleep (100);
		Abort;
		Result := FALSE; // If invoked in the wrong place.
	end else
	begin
		Result := TRUE;
	end;
end;


function MoveLogFile (SubDir : String; bDate : Boolean): Boolean;
// Moves the log file to the application's directory.
//	SubDir names a directory below {app}, for instance 'Logs'.
//	Don't append or prepend a '\' to SubDir.
//	bDate specifies whether the final log file will have today's
//	data appended or not (TRUE = append date, FALSE = don't
//	append the data).

var
	sSubF			: String;
	sAppDir			: String;
	sBatName		: String;
	sDate			: String;
	sAppLogFileName	: String;
	sMyLogFile		: String;
	sMLogFileName	: String;
	Errorcode		: Integer;
begin
	try
		sAppDir := ExpandConstant ('{app}');
	except
		sAppDir := '';
	end;
	if sAppDir <> '' then
	begin
		if SubDir <> '' then
		begin
			sSubF := '\' + SubDir;
		end else
		begin
			sSubF := '';
		end;
		ForceDirectories (sAppDir + sSubF);
		RegQueryStringValue (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars',
			'sMLogFileName', sMLogFileName);
		RegDeleteValue (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars',
			'sMLogFileName');
		RegQueryStringValue (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars',
			'sMyLogFile', sMyLogFile);
		RegDeleteValue (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars',
			'sMyLogFile');
		RegDeleteKeyIfEmpty (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\Vars');
		RegDeleteKeyIfEmpty (HKEY_CURRENT_USER, 'Software\MyBrandNewApplication\');
		if DirExists (sAppDir + sSubF) then
		begin
			if bDate then
				sDate := GetDateTimeString ('yyyy-mm-dd', '-', ':')
			else
				sDate := '';
			sAppLogFileName := sAppDir + sSubF + '\' + sMyLogFile + sDate + '.log';
			// Create a batch file that can add the newly created log file.
			sBatName := GenerateUniqueName (ExpandConstant ('{%TMP}'), '.cmd')
			SaveStringToFile (sBatName,
				'PING -n 5 127.0.0.1' +
				Chr (13) + Chr (10),
				FALSE);
			SaveStringToFile (sBatName,
				'TYPE "' + sMLogFileName + '">>"' + sAppLogFileName + '"' +
				Chr (13) + Chr (10),
				TRUE);
			if GetWindowsVersion () >= $05000893 then
			begin
				// Windows 2000 and higher. Let's delete the file.
				SaveStringToFile (sBatName,
					'DEL /F/Q "' + sMLogFileName + '"' +
					Chr (13) + Chr (10),
					TRUE);
				SaveStringToFile (sBatName,
					'DEL /F/Q "' + sBatName + '"' +
					Chr (13) + Chr (10),
					TRUE);
			end;
			SaveStringToFile (sBatName,
				'Exit' +
				Chr (13) + Chr (10),
				TRUE);
			Exec (ExpandConstant ('{sys}\CMD.EXE '),
				' /C ' +
				'"' + sBatName + '"',
				ExpandConstant ('{tmp}'),
				SW_HIDE, ewNoWait, Errorcode);
			if FileOrDirExists (sMLogFileName) then
			begin
				try
					RestartReplace (sMLogFileName, '');
				except
				end;
				try
					RestartReplace (sBatName, '');
				except
				end;
			end;
			Result := TRUE;
		end;
	end else
	begin
		Result := FALSE;
	end;
end;

Bear in mind that this solution only works on systems with Windows NT or higher.

Source Code of StartHelper.exe

Source code of StartHelper.exe (StartHelper.zip 16KB):

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <crtdbg.h>
#include "GetWindowsErrorText.h"

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	STARTUPINFO	si;
	PROCESS_INFORMATION pi;
	TCHAR		tcPars [10000];
	TCHAR		tcError [1000];
	DWORD		dwError;
	int			argc;
	size_t		stlen;

	if (__argc > 1)
	{
		argc = 3;
		tcPars [0] = '\0';
		stlen = 10000;
		while (argc <= __argc)
		{
			if (argc > 3)
			{
				strncat (tcPars, " ", stlen);
				tcPars [10000 - 1] = '\0';
				stlen--;
			}
			if ((strchr (__argv [argc - 1], ' ')) && (!strchr (__argv [argc - 1], '\"')))
			{
				stlen--;
				strncat (tcPars, "\"", stlen);
				tcPars [10000 - 1] = '\0';
			}
			stlen -= strlen (__argv [argc - 1]);
			strncat (tcPars, __argv [argc - 1], stlen);
			tcPars [10000 - 1] = '\0';
			if ((strchr (__argv [argc - 1], ' ')) && (!strchr (__argv [argc - 1], '\"')))
			{
				stlen--;
				strncat (tcPars, "\"", stlen);
				tcPars [10000 - 1] = '\0';
			}
			argc++;
		}
		ZeroMemory (&si, sizeof (STARTUPINFO));
		ZeroMemory (&pi, sizeof (PROCESS_INFORMATION));
		if (CreateProcess (__argv [1],
			tcPars, NULL, NULL, FALSE, 0, NULL, NULL /*DefaultDir*/, &si, &pi) == 0)
		{
			dwError = GetLastError ();
			_snprintf (tcError, 1000, "CreateProcess () failed. Error %d: %s.", dwError, GetWindowsErrorText (dwError));
			tcError [1000 - 1] = '\0';
			MessageBox (NULL, tcError, "Error StartHelper.exe", MB_OK | MB_ICONSTOP);
		};
	} else
		MessageBox (NULL, "Usage:\n\nStartHelper [executable file] [args]", "StartHelper", MB_OK | MB_ICONINFORMATION);
};

See also

Personal tools
Ads: