We use Inno Setup for our installer. Recently a user reported the following error during installation:
An error occurred while trying to copy a file: the source file is corrupted
This was due to a setup file that was indeed corrupted somehow.
Ideally the setup EXE would have performed some kind of check upon initialization to see if the entire EXE was valid or not. But apparently it only did this on a file-by-file basis. Is it possible to get InnoSetup to do that?
I looked in Inno Setup documentation for keywords like 'check', 'hash', etc. but didn't see anything - perhaps I missed it.
Quite similar question (from about 10 years ago - though specifically asking about MD5): How to implement MD5 check into Inno Setup installer to get 'like NSIS integrity check'? . That question seemed to state that such a check should already be happening. So perhaps the issue is not whether or not the setup EXE is validated but when this information is used / shown to the user. Also the accepted answer seemed quite manual, ideally I'd like Inno to do this itself.
Similar question with a different error message: "Source file corrupted: SHA-1 hash mismatch" error from Inno Setup
Add a checksum to the installer and verify it when the installer is starting. A standard (and recommended) way to do that is to sign the installer using code signing certificate. It's a must anyway these days.
Easy way to verify the signature is using PowerShell Get-AuthenticodeSignature
. You need PowerShell 5.1 for that. It is bundled with Windows 10 Build 14393 (August 2016) and newer. The following code uses that (and skips the check on older versions of Windows).
function InitializeSetup(): Boolean;
var
WindowsVersion: TWindowsVersion;
S: string;
ResultCode: Integer;
begin
Result := True;
GetWindowsVersionEx(WindowsVersion);
Log(Format('Windows build %d', [WindowsVersion.Build]));
// TODO: Better would be to check PowerShell version
if WindowsVersion.Build < 14393 then
begin
Log('Old version of Windows, skipping certificate check');
end
else
begin
S := ExpandConstant('{srcexe}');
// Possibly this check be might need to be more strict
// (e.g. allowing only letters, digits and dots)
if (Pos('''', S) > 0) or (Pos('"', S) > 0) then
RaiseException('Possible code injection');
S := 'if ((Get-AuthenticodeSignature ''' + S + ''').Status -ne ''Valid'') ' +
'{ exit 1 }';
if ExecAsOriginalUser(
'powershell', '-ExecutionPolicy Bypass -command "' + S + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
(ResultCode = 0) then
begin
Log('Installer signature is valid');
end
else
begin
S := 'Installer signature is not valid. Are you sure you want to continue?';
Result := (MsgBox(S, mbError, MB_YESNO) = IDYES);
end;
end;
end;
If you need to support older versions of Windows, you will have to use more complicated methods, like:
signtool
with the installer (not sure about license here);X509Certificate
class.