Some questions/solutions I found on here were similar but not quite what I needed.
I'm trying to create an installer for a Python application I've created for Windows. The installer calls another installer (openscad_installer.exe) and the user has the choice to install that wherever they like (i.e. I don't know the destination and would need to be able to find it) or not to install it at all.
I essentially need to check if the openscad.exe file exists (i.e. if it is installed) anywhere on the computer (in the C: drive) and if it does not exist then I need to uninstall my software.
The uninstall process seems simple enough but I don't know how to find out if the file exists. Thanks for the help.
Searching the file in C: drive (and possibly any other drive, as an user may choose to install a software anywhere else) is doable, but can take ages.
I'd suggest you instead check for an existence of the SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\OpenSCAD registry key:
const
OpenSCADRegKey = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\OpenSCAD';
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ResultCode: integer;
begin
Exec('OpenSCAD-xxx-Installer.exe', '', '', SW_SHOW, ewWaitUntilTerminated,
ResultCode);
if RegKeyExists(HKEY_CURRENT_USER_32, OpenSCADRegKey) or
RegKeyExists(HKEY_CURRENT_USER_64, OpenSCADRegKey) or
RegKeyExists(HKEY_LOCAL_MACHINE_32, OpenSCADRegKey) or
RegKeyExists(HKEY_LOCAL_MACHINE_64, OpenSCADRegKey) then
begin
Log('OpenSCAD is installed');
end
else
begin
Log('OpenSCAD is not installed');
// Abort installation
Result := 'OpenSCAD is not installed';
Exit;
end;
end;
If you need to know the installation location, read and parse the UninstallString value:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\OpenSCAD]
"UninstallString"="C:\\Program Files\\OpenSCAD\\Uninstall.exe"
If you insist on searching for openscad.exe use:
function FindFile(RootPath: string; FileName: string): string;
var
FindRec: TFindRec;
FilePath: string;
begin
Log(Format('Searching %s for %s', [RootPath, FileName]));
if FindFirst(RootPath + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
FilePath := RootPath + '\' + FindRec.Name;
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
begin
Result := FindFile(FilePath, FileName);
if Result <> '' then Exit;
end
else
if CompareText(FindRec.Name, FileName) = 0 then
begin
Log(Format('Found %s', [FilePath]));
Result := FilePath;
Exit;
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
Log(Format('Failed to list %s', [RootPath]));
end;
end;
Yet another option is looking for the file in search path:
How can I check SQLCMD.EXE if it is installed on client in Inno Setup
Related
Is it possible to get an output of an Exec'ed executable?
I want to show the user an info query page, but show the default value of MAC address in the input box. Is there any other way to achieve this?
Yes, use redirection of the standard output to a file:
[Code]
function NextButtonClick(CurPage: Integer): Boolean;
var
TmpFileName, ExecStdout: string;
ResultCode: integer;
begin
if CurPage = wpWelcome then begin
TmpFileName := ExpandConstant('{tmp}') + '\ipconfig_results.txt';
Exec('cmd.exe', '/C ipconfig /ALL > "' + TmpFileName + '"', '', SW_HIDE,
ewWaitUntilTerminated, ResultCode);
if LoadStringFromFile(TmpFileName, ExecStdout) then begin
MsgBox(ExecStdout, mbInformation, MB_OK);
{ do something with contents of file... }
end;
DeleteFile(TmpFileName);
end;
Result := True;
end;
Note that there may be more than one network adapter, and consequently several MAC addresses to choose from.
I had to do the same (execute command line calls and get the result) and came up with a more general solution.
It also fixes strange bugs if quoted paths are used in the actual calls by using the /S flag for cmd.exe.
// Exec with output stored in result.
// ResultString will only be altered if True is returned.
function ExecWithResult(
Filename, Params, WorkingDir: String; ShowCmd: Integer;
Wait: TExecWait; var ResultCode: Integer; var ResultString: String): Boolean;
var
TempFilename: String;
Command: String;
ResultStringAnsi: AnsiString;
begin
TempFilename := ExpandConstant('{tmp}\~execwithresult.txt');
// Exec via cmd and redirect output to file.
// Must use special string-behavior to work.
Command :=
Format('"%s" /S /C ""%s" %s > "%s""', [
ExpandConstant('{cmd}'), Filename, Params, TempFilename]);
Result :=
Exec(ExpandConstant('{cmd}'), Command, WorkingDir, ShowCmd, Wait, ResultCode);
if not Result then
Exit;
LoadStringFromFile(TempFilename, ResultStringAnsi); // Cannot fail
// See https://stackoverflow.com/q/20912510/850848
ResultString := ResultStringAnsi;
DeleteFile(TempFilename);
// Remove new-line at the end
if (Length(ResultString) >= 2) and
(ResultString[Length(ResultString) - 1] = #13) and
(ResultString[Length(ResultString)] = #10) then
Delete(ResultString, Length(ResultString) - 1, 2);
end;
Usage:
Success :=
ExecWithResult('ipconfig', '/all', '', SW_HIDE, ewWaitUntilTerminated,
ResultCode, ExecStdout) and
(ResultCode = 0);
The result can also be loaded into a TStringList object to get all lines:
Lines := TStringList.Create;
Lines.Text := ExecStdout;
// ... some code ...
Lines.Free;
I want to verify if user has Docker installed in their system.
If it is installed proceed further otherwise display an error message.
Previously I was looking at the registry group in Windows but it's not the correct way.
I want to check if cmd gives correct output for command docker.
function GetHKLM: Integer;
begin
if IsWin64 then
Result := HKLM64
else
Result := HKLM32;
end;
function GetHKU: Integer;
begin
if IsWin64 then
Result := HKCU64
else
Result := HKCU32;
end;
function InitializeSetup: Boolean;
begin
// Opening the setup installer initially
Result := True;
//if the docker is present in the machine registry return True else checking user registry
if not RegKeyExists(GetHKLM, 'SOFTWARE\Docker Inc.') then
begin
if not RegKeyExists(GetHKU, 'Software\Docker Inc.') then
begin
// return False to prevent installation to continue
Result := False;
// Display that you need to install docker.
SuppressibleMsgBox('<Docker not found!>', mbCriticalError, MB_OK, IDOK);
end;
end;
end;
How do I do this with just cmd? Instead of checking registry.. How can I run the command line and verify the output?
for etc:
function checkdocker() :Boolean;
var
dockerfound: string;
begin
Result :=
ShellExecute(application.handle, 'docker', nil, nil, SW_MAXIMIZE)
end;
function InitializeSetup: Boolean;
begin
Result := True;
if not checkdocker then;
SuppressibleMsgBox('<Docker not found!>', mbCriticalError, MB_OK, IDOK);
else
#continue
end;
To answer your literal question: Just use Exec and check the result code:
function CheckDocker: Boolean;
var
ResultCode: Integer;
begin
Result :=
Exec('docker', '', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
(ResultCode = 0);
if Result then Log('Succeeded executing docker')
else Log('Failed to execute docker');
end;
(based on How to get an output of an Exec'ed program in Inno Setup?)
Though there's more efficient way to check if docker.exe executable is in a search path. Use FileSearch. See How can I check SQLCMD.EXE if it is installed on client in Inno Setup.
I cannot find a solution for checking the user selected path to be without any spaces or special characters.
Can you help me?
You can check for space like this:
[Code]
function NextButtonClick(CurPageID: Integer): Boolean;
var
Dir: string;
Msg: string;
begin
Result := True;
if CurPageID = wpSelectDir then
begin
Dir := WizardForm.DirEdit.Text;
if Pos(' ', Dir) > 0 then
begin
Msg := 'The path cannot contain spaces';
if WizardSilent then Log(Msg)
else MsgBox(Msg, mbError, MB_OK);
Result := False;
end;
end;
end;
You may consider using SuppressibleMsgBox function:
What does it mean that message boxes are being suppressed in Inno Setup?
My Inno Setup script is used to install a driver. It runs my InstallDriver.exe after this file was copied during step ssInstall.
I need to ask the user to restart in some cases according to the value returned by InstallDriver.exe.
This means that I cannot put InstallDriver.exe in section [Run] because there's no way to monitor it's return value.
So I put it in function CurStepChanged() as follows:
procedure CurStepChanged(CurStep: TSetupStep);
var
TmpFileName, ExecStdout, msg: string;
ResultCode: Integer;
begin
if (CurStep=ssPostInstall) then
begin
Log('CurStepChanged(ssPostInstall)');
TmpFileName := ExpandConstant('{app}') + '\InstallDriver.exe';
if Exec(TmpFileName, 'I', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then .......
However, I can't find a way to make my script restart at this stage.
I thought of using function NeedRestart() to monitor the output of the driver installer, but it is called earlier in the process.
Does it make sense to call the driver installer from within NeedRestart()?
NeedRestart does not look like the right place to install anything. But it would work, as it's fortunately called only once. You will probably want to present a progress somehow though, as the wizard form is almost empty during a call to NeedRestart.
An alternative is to use AfterInstall parameter of the InstallDriver.exe or the driver binary itself (whichever is installed later).
#define InstallDriverName "InstallDriver.exe"
[Files]
Source: "driver.sys"; DestDir: ".."
Source: "{#InstallDriverName}"; DestDir: "{app}"; AfterInstall: InstallDriver
[Code]
var
NeedRestartFlag: Boolean;
const
NeedRestartResultCode = 1;
procedure InstallDriver();
var
InstallDriverPath: string;
ResultCode: Integer;
begin
Log('Installing driver');
InstallDriverPath := ExpandConstant('{app}') + '\{#InstallDriverName}';
if not Exec(InstallDriverPath, 'I', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
Log('Failed to execute driver installation');
end
else
begin
Log(Format('Driver installation finished with code %d', [ResultCode]))
if ResultCode = NeedRestartResultCode then
begin
Log('Need to restart to finish driver installation');
NeedRestartFlag := True;
end;
end;
end;
function NeedRestart(): Boolean;
begin
if NeedRestartFlag then
begin
Log('Need restart');
Result := True;
end
else
begin
Log('Do not need restart');
Result := False;
end;
end;
Is it possible to get an output of an Exec'ed executable?
I want to show the user an info query page, but show the default value of MAC address in the input box. Is there any other way to achieve this?
Yes, use redirection of the standard output to a file:
[Code]
function NextButtonClick(CurPage: Integer): Boolean;
var
TmpFileName, ExecStdout: string;
ResultCode: integer;
begin
if CurPage = wpWelcome then begin
TmpFileName := ExpandConstant('{tmp}') + '\ipconfig_results.txt';
Exec('cmd.exe', '/C ipconfig /ALL > "' + TmpFileName + '"', '', SW_HIDE,
ewWaitUntilTerminated, ResultCode);
if LoadStringFromFile(TmpFileName, ExecStdout) then begin
MsgBox(ExecStdout, mbInformation, MB_OK);
{ do something with contents of file... }
end;
DeleteFile(TmpFileName);
end;
Result := True;
end;
Note that there may be more than one network adapter, and consequently several MAC addresses to choose from.
I had to do the same (execute command line calls and get the result) and came up with a more general solution.
It also fixes strange bugs if quoted paths are used in the actual calls by using the /S flag for cmd.exe.
// Exec with output stored in result.
// ResultString will only be altered if True is returned.
function ExecWithResult(
Filename, Params, WorkingDir: String; ShowCmd: Integer;
Wait: TExecWait; var ResultCode: Integer; var ResultString: String): Boolean;
var
TempFilename: String;
Command: String;
ResultStringAnsi: AnsiString;
begin
TempFilename := ExpandConstant('{tmp}\~execwithresult.txt');
// Exec via cmd and redirect output to file.
// Must use special string-behavior to work.
Command :=
Format('"%s" /S /C ""%s" %s > "%s""', [
ExpandConstant('{cmd}'), Filename, Params, TempFilename]);
Result :=
Exec(ExpandConstant('{cmd}'), Command, WorkingDir, ShowCmd, Wait, ResultCode);
if not Result then
Exit;
LoadStringFromFile(TempFilename, ResultStringAnsi); // Cannot fail
// See https://stackoverflow.com/q/20912510/850848
ResultString := ResultStringAnsi;
DeleteFile(TempFilename);
// Remove new-line at the end
if (Length(ResultString) >= 2) and
(ResultString[Length(ResultString) - 1] = #13) and
(ResultString[Length(ResultString)] = #10) then
Delete(ResultString, Length(ResultString) - 1, 2);
end;
Usage:
Success :=
ExecWithResult('ipconfig', '/all', '', SW_HIDE, ewWaitUntilTerminated,
ResultCode, ExecStdout) and
(ResultCode = 0);
The result can also be loaded into a TStringList object to get all lines:
Lines := TStringList.Create;
Lines.Text := ExecStdout;
// ... some code ...
Lines.Free;