Any time i try to compile this code, a get an error msg -> Type mismatch. Why?
The first variant is called with the same "nil" and the compiler has no problem with it, but with the last?
[Setup]
AppName=MyApp
AppVerName=MyApp
DefaultDirName={pf}\MyApp
DefaultGroupName=MyApp
OutputDir=.
[Code]
function URLDownloadToCacheFile(const lpUnkcaller: variant; const szURL: PAnsiChar; var szFileName: PAnsiChar; const cchFileName: DWORD; const dwReserved: DWORD; IBindStatusCallback: variant): HRESULT; external 'URLDownloadToFileW#Urlmon.dll stdcall';
function InitializeSetup(): Boolean;
var
szFileName, szURL : PAnsiChar;
ErrorCode : HRESULT;
MAX_PATH : DWORD;
begin
szURL := 'http://fs2.directupload.net/images/user/150607/x3sugvzy.jpg';
MAX_PATH := 512;
SetLength(szFileName, MAX_PATH + 1);
ErrorCode := URLDownloadToCacheFile(null, szURL, szFileName, MAX_PATH, 0, null);
end;
changed code that way:
[Setup]
AppName=MyApp
AppVerName=MyApp
DefaultDirName={pf}\MyApp
DefaultGroupName=MyApp
OutputDir=.
[Code]
function URLDownloadToCacheFile(const lpUnkcaller: variant; const szURL: AnsiString; var szFileName: String; const cchFileName: DWORD; const dwReserved: DWORD; IBindStatusCallback: variant): HRESULT; external 'URLDownloadToFileW#Urlmon.dll stdcall';
function InitializeSetup(): Boolean;
var
szFileName : String;
szURL : AnsiString;
ErrorCode : HRESULT;
MAX_PATH : DWORD;
begin
szURL := 'http://fs2.directupload.net/images/user/150607/x3sugvzy.jpg';
MAX_PATH := 512;
SetLength(szFileName, 513);
ErrorCode := URLDownloadToCacheFile(null, szURL, szFileName, MAX_PATH, 0, null);
MsgBox(szFileName,mbConfirmation, MB_OK);
end;
The original problem was that you were passing nil to a parameter of type Variant. Null value for the Variant type is NULL in Pascal Script.
But, your prototype needs to be changed. You need to use Unicode data types when you are using Unicode variant of a function, so you cannot use PAnsiChar types there. The next problem is with the Variant types you used for interface type parameters. That won't work. You can use IUnknown types there, because the lpUnkcaller parameter is of a pointer to this type and pBSC is of type IBindStatusCallback which from IUnknown inherits. The last problem is that MSDN is wrong here. The szURL is according to the header files of type LPCTSTR, not LPCSTR. The Unicode variant prototype looks like this in SDK 8.1 urlmon.h header file (formatted and commented by me):
STDAPI URLDownloadToCacheFileW(
_In_opt_ LPUNKNOWN,
_In_ LPCWSTR, // <- this is wrong on MSDN; this type would have to be LPCSTR here
_Out_writes_(cchFileName) LPWSTR,
DWORD cchFileName,
DWORD,
_In_opt_ LPBINDSTATUSCALLBACK
);
So, your script might be written like this (it should support also ANSI version of Inno Setup, but I haven't tested it there):
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
MAX_PATH = 260;
S_OK = $00000000;
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function URLDownloadToCacheFile(lpUnkcaller: IUnknown; szURL: string; szFileName: string;
cchFileName: DWORD; dwReserved: DWORD; pBSC: IUnknown): HRESULT;
external 'URLDownloadToCacheFile{#AW}#urlmon.dll stdcall';
function TryDownloadToCache(const URL: string; out FileName: string): Boolean;
begin
SetLength(FileName, MAX_PATH);
Result := URLDownloadToCacheFile(nil, URL, FileName, Length(FileName), 0, nil) = S_OK;
end;
function InitializeSetup: Boolean;
var
FileName: string;
ErrorCode: Integer;
begin
Result := True;
if TryDownloadToCache('http://i.imgur.com/wKCsei6.png', FileName) then
ShellExec('', FileName, '', '', SW_SHOW, ewNoWait, ErrorCode)
else
MsgBox('Downloading failed.', mbError, MB_OK);
end;
Related
I would like to have in my installer:
an infinite music loop playback during installation
a window on the background (like the old installations that used to fill the screen with an image and only show the installation window), with a slideshow on that background window
How to do this in InnoSetup ?
If you want to have an installer with a background image slideshow with an infinite music track playback, you can do e.g. the following:
get the recent version of the InnoCallback library for slideshow timer implementation
for music playback get e.g. most recent copy of the Inno Media Player for Unicode Inno Setup
Write a script similar to following, or download the complete project, which includes all necessary files used in the next script code. So the only thing you'd need to do, is to build it in the recent version of Unicode Inno Setup.
Please note, that Inno Media Player is a Unicode library, and so you can use it only with Unicode versions of Inno Setup, not with ANSI ones! There is no support for ANSI versions of Inno Setup...!
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
BackColor=clLime
BackColor2=clYellow
WindowVisible=yes
[Files]
Source: "Image1.bmp"; Flags: dontcopy
Source: "Image2.bmp"; Flags: dontcopy
Source: "AudioFile.mp3"; Flags: dontcopy
Source: "MediaPlayer.dll"; Flags: dontcopy
Source: "InnoCallback.dll"; Flags: dontcopy
[Code]
var
TimerID: Integer;
SlideID: Integer;
BackImage: TBitmapImage;
const
EC_COMPLETE = $01;
type
TTimerProc = procedure(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR;
SysTime: DWORD);
TDirectShowEventProc = procedure(EventCode, Param1, Param2: Integer);
function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
external 'wrapcallback#files:InnoCallback.dll stdcall';
function SetTimer(hWnd: HWND; nIDEvent, uElapse: UINT;
lpTimerFunc: UINT): UINT; external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL;
external 'KillTimer#user32.dll stdcall';
function DSGetLastError(var ErrorText: WideString): HRESULT;
external 'DSGetLastError#files:mediaplayer.dll stdcall';
function DSPlayMediaFile: Boolean;
external 'DSPlayMediaFile#files:mediaplayer.dll stdcall';
function DSStopMediaPlay: Boolean;
external 'DSStopMediaPlay#files:mediaplayer.dll stdcall';
function DSSetVolume(Value: LongInt): Boolean;
external 'DSSetVolume#files:mediaplayer.dll stdcall';
function DSInitializeAudioFile(FileName: WideString;
CallbackProc: TDirectShowEventProc): Boolean;
external 'DSInitializeAudioFile#files:mediaplayer.dll stdcall';
procedure OnMediaPlayerEvent(EventCode, Param1, Param2: Integer);
begin
if EventCode = EC_COMPLETE then
begin
if DSInitializeAudioFile(ExpandConstant('{tmp}\AudioFile.mp3'),
#OnMediaPlayerEvent) then
begin
DSSetVolume(-2500);
DSPlayMediaFile;
end;
end;
end;
procedure OnSlideTimer(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR;
SysTime: DWORD);
begin
case SlideID of
0: SlideID := 1;
1: SlideID := 0;
end;
BackImage.Bitmap.LoadFromFile(
ExpandConstant('{tmp}\Image' + IntToStr(SlideID + 1) + '.bmp'));
end;
procedure StartSlideTimer;
var
TimerCallback: LongWord;
begin
TimerCallback := WrapTimerProc(#OnSlideTimer, 4);
{ third parameter here is the timer's timeout value in milliseconds }
TimerID := SetTimer(0, 0, 5000, TimerCallback);
end;
procedure KillSlideTimer;
begin
if TimerID <> 0 then
begin
if KillTimer(0, TimerID) then
TimerID := 0;
end;
end;
procedure InitializeWizard;
var
ErrorCode: HRESULT;
ErrorText: WideString;
begin
TimerID := 0;
SlideID := 0;
ExtractTemporaryFile('Image1.bmp');
ExtractTemporaryFile('Image2.bmp');
BackImage := TBitmapImage.Create(MainForm);
BackImage.Parent := MainForm;
BackImage.Top := 70;
BackImage.Left := 10;
BackImage.AutoSize := True;
BackImage.Bitmap.LoadFromFile(ExpandConstant('{tmp}\Image1.bmp'));
StartSlideTimer;
ExtractTemporaryFile('AudioFile.mp3');
if DSInitializeAudioFile(ExpandConstant('{tmp}\AudioFile.mp3'),
#OnMediaPlayerEvent) then
begin
DSSetVolume(-2500);
DSPlayMediaFile;
end
else
begin
ErrorCode := DSGetLastError(ErrorText);
MsgBox('TDirectShowPlayer error: ' + IntToStr(ErrorCode) + '; ' +
ErrorText, mbError, MB_OK);
end;
end;
procedure DeinitializeSetup;
begin
KillSlideTimer;
DSStopMediaPlay;
end;
Further resources:
How to make a slideshow in InnoSetup ?
How to play a sound during InnoSetup installation process ?
Creating backgrounds feature:
Did you try Graphical Installer (http://www.graphical-installer.com) ?
It is a professional Inno Setup extension specially for this (creating cool looking installers with background) so creating such installer is matter of few minutes (no coding is required).
I am trying to query the version details of a file that the installer installs and compare it against the version details of the same file present in the installer being executed. The details are not in the FileVersion or ProductVersion field but can be in other fields like InternalName etc.
I see Win32 APIs for solving this and also some sample code like :
http://delphidabbler.com/articles?article=20
How can I read details of file?
However, some of the data types used in those code samples do not work with Inno Setup. Further, some samples and description seem to indicate that the language and codepage itself will be an array but some samples use it assuming only one entry for language and codepage.
I was stuck at trying to find the language and codepage and based on comments below, I hard coded it for en-us.
I do see this answer which has a code sample for Inno Setup Pascal but the language and codepage calculation not being based on the lplpBufferCP variable makes me doubt its correctness.
Is it possible to read generic version info properties from Inno Setup Pascal script ? If so, please help around how to find the language and code page values.
The code I have written based on the aforesaid solutions is listed below with in-line comments for the problematic portions.
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function GetFileVersionInfoSize(lptstrFilename: String; lpdwHandle: Integer): Integer;
external 'GetFileVersionInfoSize{#AW}#version.dll stdcall delayload';
function GetFileVersionInfo(lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external 'GetFileVersionInfo{#AW}#version.dll stdcall delayload';
function VerQueryValue(var pBlock: Byte; lpSubBlock: String; var lplpBuffer: Byte; var puLen: Integer): Boolean;
external 'VerQueryValue{#AW}#version.dll stdcall delayload';
function GetFileVersionProperty(const FileName, PropertyName: String): String;
var
VerSize: Integer;
VerInfo: array of Byte;
Dummy: Integer;
InternalNameArr: array of Byte;
begin
Result := '';
if not FileExists(FileName) then
begin
Log('File ' + FileName + ' does not exist');
Exit;
end;
VerSize := GetFileVersionInfoSize(FileName, 0);
if not VerSize > 0 then
begin
Log('File ' + FileName + ' has no version information');
Exit;
end;
SetArrayLength(VerInfo, VerSize);
if not GetFileVersionInfo(FileName, 0, VerSize, VerInfo[0]) then
begin
Log('Failed to get version info for ' + FileName);
Exit;
end;
if not GetFileVersionInfo(FileName, 0, VerSize, VerInfo[0]) then
begin
Log('Failed to get version info for ' + FileName);
Exit;
end;
{ Getting 'Version size = 2156' }
Log(Format('Version size = %d', [VerSize]));
{ Hard coded value just for testing }
SetArrayLength(InternalNameArr, 512);
{ 040904E4 hard coded for en-us }
{ Is this the correct way of querying the details ? }
{ If not, what needs to be done here }
{ TODO : InternalName hard coded. Use parameter PropertyName }
if VerQueryValue(VerInfo[0], '\StringFileInfo\040904E4\InternalName', InternalNameArr[0], Dummy) then
begin
Log('Failed to query internal name of ' + FileName);
Exit;
end
else
begin
{ What needs to be done here to convert an array of byte to string ? }
{ Do I need to iterate over the array and do the conversion ?}
{ The following does not work because of SetString() being unavailable : }
{ InternalName = SetString(AnsiStr, PAnsiChar(#InternalNameArr[0]), Len);}
{ Getting 'ProductName = 0000' and 'Dummy = 0' }
Log(Format('ProductName = %d%d', [InternalNameArr[0], InternalNameArr[1], InternalNameArr[2], InternalNameArr[3]]));
Log(Format('Dummy = %d', [Dummy]));
end;
{ TODO : Populate Result with appropriate value }
end;
An alternate approach could be to save the file properties of the installed file in registry (I am interested in 1 property of 1 of the files) and have the property available in the installer statically for the new file.
The correct code to retrieve a string from the first language of a file version info is below. The code builds on an answer by #Jens A. Koch to How to write data to an installer on the server?
The code requires Unicode version of Inno Setup.
function GetFileVersionInfoSize(
lptstrFilename: String; lpdwHandle: Integer): Integer;
external 'GetFileVersionInfoSizeW#version.dll stdcall delayload';
function GetFileVersionInfo(
lptstrFilename: String; dwHandle, dwLen: Integer; var lpData: Byte): Boolean;
external 'GetFileVersionInfoW#version.dll stdcall delayload';
function VerQueryValue(
var pBlock: Byte; lpSubBlock: String; var lplpBuffer: DWord;
var Len: Integer): Boolean;
external 'VerQueryValueW#version.dll stdcall delayload';
procedure RtlMoveMemoryAsString(Dest: string; Source: DWord; Len: Integer);
external 'RtlMoveMemory#kernel32.dll stdcall';
procedure RtlMoveMemoryAsBytes(Dest: array of Byte; Source: DWord; Len: Integer);
external 'RtlMoveMemory#kernel32.dll stdcall';
function GetFileVerInfo(FileName, VerName: String): String;
var
Len: Integer;
FileVerInfo: array of Byte;
Lang: array of Byte;
Buffer: DWord;
LangCodepage: string;
SubBlock: string;
begin
Result := '';
if FileExists(FileName) then
begin
Len := GetFileVersionInfoSize(FileName, 0);
if Len > 0 then
begin
SetArrayLength(FileVerInfo, Len);
if GetFileVersionInfo(FileName, 0, Len, FileVerInfo[0]) then
begin
if VerQueryValue(
FileVerInfo[0], '\VarFileInfo\Translation', Buffer, Len) then
begin
if Len >= 4 then
begin
SetArrayLength(Lang, 4);
RtlMoveMemoryAsBytes(Lang, Buffer, 4);
LangCodepage :=
Format('%.2x%.2x%.2x%.2x', [Lang[1], Lang[0], Lang[3], Lang[2]]);
SubBlock :=
Format('\%s\%s\%s', ['StringFileInfo', LangCodepage, VerName]);
if VerQueryValue(FileVerInfo[0], SubBlock, Buffer, Len) then
begin
SetLength(Result, Len - 1);
RtlMoveMemoryAsString(Result, Buffer, (Len - 1) * 2);
end;
end;
end;
end;
end;
end;
end;
How does one write to a binary file in Inno Setup script? It's a configuration file I want to edit in the PrepareToInstall step. The problem is that I'm looking at the support functions:
TStream = class(TObject)
function Read(Buffer: String; Count: Longint): Longint;
function Write(Buffer: String; Count: Longint): Longint;
function Seek(Offset: Longint; Origin: Word): Longint;
procedure ReadBuffer(Buffer: String; Count: Longint);
procedure WriteBuffer(Buffer: String; Count: Longint);
function CopyFrom(Source: TStream; Count: Longint): Longint;
property Position: Longint; read write;
property Size: Longint; read write;
end;
And it seems even the most basic write function writes strings. Should I just do it in a batch script?
Consider the string in the TStream interface as a buffer of chars/bytes.
It's bit more complicated with Unicode version of Inno Setup, where the string is an array of 2-byte chars (comparing to legacy Ansi version, as there one byte equals one char – Though as of Inno Setup 6, Unicode is actually the only version available anyway).
See also Read bytes from file at desired position with Inno Setup.
To convert a hex string to the actual binary data, you can use the CryptStringToBinary Windows API function.
The following code works both in Ansi and Unicode version of Inno Setup.
#ifndef Unicode
const CharSize = 1;
#define AW "A"
#else
const CharSize = 2;
#define AW "W"
#endif
function CryptStringToBinary(
sz: string; cch: LongWord; flags: LongWord; binary: string; var size: LongWord;
skip: LongWord; flagsused: LongWord): Integer;
external 'CryptStringToBinary{#AW}#crypt32.dll stdcall';
const
CRYPT_STRING_HEX = $04;
procedure WriteHexToFile(Hex: string; FileName: string);
var
Stream: TFileStream;
Buffer: string;
Size: LongWord;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
SetLength(Buffer, (Length(Hex) div 2*CharSize) + CharSize - 1);
Size := Length(Hex) div 2;
if (CryptStringToBinary(
Hex, Length(Hex), CRYPT_STRING_HEX, Buffer, Size, 0, 0) = 0) or
(Size <> Length(Hex) div 2) then
begin
RaiseException('Error decoding hex string');
end;
Stream.WriteBuffer(Buffer, Size);
finally
Stream.Free;
end;
end;
Use it like:
procedure WriteHexToFileTest;
var
Hex: string;
begin
Hex :=
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f' +
'202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f' +
'404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f' +
'606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f' +
'808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f' +
'a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf' +
'c0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' +
'e0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff';
WriteHexToFile(Hex, 'my_binary_file.dat');
end;
I'm having an issue here with the following:
function InitializeSetup(): Boolean;
var
ResultCode:Integer;
begin
Result := true;
if MsgBox('Wanna help?',mbConfirmation, MB_YESNO )= IDYES then
begin
CreateBatch();
Exec('cmd.exe', '/c "' +ExpandConstant('{tmp}\batch.bat'),'',SW_HIDE, ewWaitUntilTerminated, ResultCode);
Result:= false;
end;
end;
in the batch file i got the following :
#ECHO OFF
D:
cd D:\_INSTALLER\Output
"installer.exe" /SAVEINF="opt.txt"
So it basically re-open the installer , over and over again ... ( infinite loop )
Is there any way to ask : Wanna help? only for the 1st time ? and if the user clicked yes, the batch should be executed , else if the user clicked no it should continue the install normally.
Thanks in advance for the support ,
BeGiN.
With the help of TLama and his post i achieved my goal by using the following script:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HINSTANCE = THandle;
procedure ExitProcess(exitCode:integer);external 'ExitProcess#kernel32.dll stdcall';
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
external 'ShellExecute{#AW}#shell32.dll stdcall';
var
withINF: Boolean;
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Break;
end;
end;
//Initialize setup
function InitializeSetup(): Boolean;
var
ResultCode:Integer;
Params: string;
RetVal: HINSTANCE;
begin
Result := true;
withINF := CmdLineParamExists('/SAVEINF=opt.txt');
if not withINF then
begin
Params := '/SAVEINF=opt.txt';
ShellExecute(0, 'open',ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
ExitProcess(0);
end;
end;
L.E: A shorter code for doing the same thing also made by TLama(multilingual support added):
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
ShowLanguageDialog = yes
[Languages]
Name: "en"; MessagesFile: "compiler:Default.isl"
Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl"
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HINSTANCE = THandle;
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
external 'ShellExecute{#AW}#shell32.dll stdcall';
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Break;
end;
end;
function InitializeSetup: Boolean;
var
Params: string;
begin
// prepare Params variable for reusing
if ExpandConstant('{language}') = 'en' then begin
Params := '/SAVEINF=opt.txt /LANG=en';
end;
if ExpandConstant('{language}') = 'nl' then begin
Params := '/SAVEINF=opt.txt /LANG=nl';
end;
// allow this setup to run if the expected parameter is specified; or, if it is not, allow to run it
// when ShellExecute fails; it works like this - first evaluates the CmdLineParamExists function and
// if that returns True, the second part of the statement (ShellExecute) won't run (evaluate); when
// the parameter is not found, the CmdLineParamExists returns False and statement evaluation goes on,
// ShellExecute attempts to run the setup and to the Result returns True (allow this instance to run)
// when the function fails for some reason (the returned value <= 32); in other words, you will allow
// this setup instance to run if executing of the new setup instance fails
Result := CmdLineParamExists('/SAVEINF=opt.txt') or (ShellExecute(0, '', ExpandConstant('{srcexe}'), Params, '', SW_SHOW) <= 32);
end;
Regards,
BeGiN
I see not reason for this but you can simply solve this by creating temp file.
If user clicks Yes (first time) then create some file (any file with some random content) just before CreateBatch() is called.
Then simply check for existence of this file - if it exists user already clicked Yes (once) and do what ever you want.
How can I "touch" a file, i.e. update its' last modified time to the current time, from within an InnoSetup (Pascal) script?
Here's the code snippet for the TouchFile function:
[Code]
function CreateFile(
lpFileName : String;
dwDesiredAccess : Cardinal;
dwShareMode : Cardinal;
lpSecurityAttributes : Cardinal;
dwCreationDisposition : Cardinal;
dwFlagsAndAttributes : Cardinal;
hTemplateFile : Integer
): THandle;
#ifdef UNICODE
external 'CreateFileW#kernel32.dll stdcall';
#else
external 'CreateFileA#kernel32.dll stdcall';
#endif
procedure GetSystemTimeAsFileTime(var lpSystemTimeAsFileTime: TFileTime);
external 'GetSystemTimeAsFileTime#kernel32.dll';
function SetFileModifyTime(hFile:THandle; CreationTimeNil:Cardinal; LastAccessTimeNil:Cardinal; LastWriteTime:TFileTime): BOOL;
external 'SetFileTime#kernel32.dll';
function CloseHandle(hHandle: THandle): BOOL;
external 'CloseHandle#kernel32.dll stdcall';
function TouchFile(FileName: String): Boolean;
const
{ Win32 constants }
GENERIC_WRITE = $40000000;
OPEN_EXISTING = 3;
INVALID_HANDLE_VALUE = -1;
var
FileTime: TFileTime;
FileHandle: THandle;
begin
Result := False;
FileHandle := CreateFile(FileName, GENERIC_WRITE, 0, 0, OPEN_EXISTING, $80, 0);
if FileHandle <> INVALID_HANDLE_VALUE then
try
GetSystemTimeAsFileTime(FileTime);
Result := SetFileModifyTime(FileHandle, 0, 0, FileTime);
finally
CloseHandle(FileHandle);
end;
end;