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;
Related
Thanks to this article posted at Add audio when Splash Screen starts on Inno Setup
the code work like a charm, I just wanted to know what to do so that the music of my installer is only played once.
I donĀ“t want it to be repeated continuously.
Thanks in advance...
Code im using
[Code]
const
BASS_SAMPLE_LOOP = 4;
BASS_UNICODE = $80000000;
BASS_CONFIG_GVOL_STREAM = 5;
const
#ifndef UNICODE
EncodingFlag = 0;
#else
EncodingFlag = BASS_UNICODE;
#endif
type
HSTREAM = DWORD;
function BASS_Init(device: LongInt; freq, flags: DWORD;
win: HWND; clsid: Cardinal): BOOL;
external 'BASS_Init#files:bass.dll stdcall';
function BASS_StreamCreateFile(mem: BOOL; f: string; offset1: DWORD;
offset2: DWORD; length1: DWORD; length2: DWORD; flags: DWORD): HSTREAM;
external 'BASS_StreamCreateFile#files:bass.dll stdcall';
function BASS_ChannelPlay(handle: DWORD; restart: BOOL): BOOL;
external 'BASS_ChannelPlay#files:bass.dll stdcall';
function BASS_SetConfig(option: DWORD; value: DWORD ): BOOL;
external 'BASS_SetConfig#files:bass.dll stdcall';
function BASS_Free: BOOL;
external 'BASS_Free#files:bass.dll stdcall';
procedure InitializeWizard;
var
StreamHandle: HSTREAM;
begin
ExtractTemporaryFile('AudioFile.mp3');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
StreamHandle := BASS_StreamCreateFile(False,
ExpandConstant('{tmp}\AudioFile.mp3'), 0, 0, 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 2500);
BASS_ChannelPlay(StreamHandle, False);
end;
end;
procedure DeinitializeSetup;
begin
BASS_Free;
end;
When I choose .mp3 file, it will play when launching setup.exe but when I change it to .xm or .s3m, it doesn't play
[Setup]
AppName=Bass Audio Project
AppVersion=1.0
DefaultDirName={pf}\Bass Audio Project
[Files]
Source: "Bass.dll"; Flags: dontcopy
Source: "tune.xm"; Flags: dontcopy
[CustomMessages]
SoundCtrlButtonCaptionSoundOn=Play
SoundCtrlButtonCaptionSoundOff=Mute
[Code]
const
BASS_SAMPLE_LOOP = 4;
BASS_ACTIVE_STOPPED = 0;
BASS_ACTIVE_PLAYING = 1;
BASS_ACTIVE_STALLED = 2;
BASS_ACTIVE_PAUSED = 3;
BASS_UNICODE = $80000000;
BASS_CONFIG_GVOL_STREAM = 5;
const
#ifndef UNICODE
EncodingFlag = 0;
#else
EncodingFlag = BASS_UNICODE;
#endif
type
HSTREAM = DWORD;
function BASS_Init(device: LongInt; freq, flags: DWORD;
win: HWND; clsid: Cardinal): BOOL;
external 'BASS_Init#files:bass.dll stdcall';
function BASS_StreamCreateFile(mem: BOOL; f: string; offset1: DWORD;
offset2: DWORD; length1: DWORD; length2: DWORD; flags: DWORD): HSTREAM;
external 'BASS_StreamCreateFile#files:bass.dll stdcall';
function BASS_Start: BOOL;
external 'BASS_Start#files:bass.dll stdcall';
function BASS_Pause: BOOL;
external 'BASS_Pause#files:bass.dll stdcall';
function BASS_ChannelPlay(handle: DWORD; restart: BOOL): BOOL;
external 'BASS_ChannelPlay#files:bass.dll stdcall';
function BASS_SetConfig(option: DWORD; value: DWORD ): BOOL;
external 'BASS_SetConfig#files:bass.dll stdcall';
function BASS_ChannelIsActive(handle: DWORD): DWORD;
external 'BASS_ChannelIsActive#files:bass.dll stdcall';
function BASS_Free: BOOL;
external 'BASS_Free#files:bass.dll stdcall';
var
SoundStream: HSTREAM;
SoundCtrlButton: TNewButton;
Muted: Boolean;
procedure SoundCtrlButtonClick(Sender: TObject);
begin
if not Muted then
begin
if BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 0) then
begin
SoundCtrlButton.Caption := 'Play';
Muted := True;
end;
end
else
begin
if BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 2500) then
begin
SoundCtrlButton.Caption := 'Mute';
Muted := False;
end;
end;
end;
procedure InitializeWizard;
begin
ExtractTemporaryFile('tune.xm');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
SoundStream := BASS_StreamCreateFile(False,
ExpandConstant('{tmp}\tune.xm'), 0, 0, 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 2500);
BASS_ChannelPlay(SoundStream, False);
SoundCtrlButton := TNewButton.Create(WizardForm);
SoundCtrlButton.Parent := WizardForm;
SoundCtrlButton.Left := 8;
SoundCtrlButton.Top := WizardForm.ClientHeight -
SoundCtrlButton.Height - 8;
SoundCtrlButton.Width := 40;
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
SoundCtrlButton.OnClick := #SoundCtrlButtonClick;
end;
end;
procedure DeinitializeSetup;
begin
BASS_Free;
end;
What should I do? I want to use to original file which is .xm or .s3m and not the converted one which is .mp3.
As seen on Un4seen, bass.dll supports .xm and .s3m.
Indeed, the BASS_StreamCreateFile returns 0 for both files.
And if you call BASS_ErrorGetCode afterwards, it returns 41 = BASS_ERROR_FILEFORM (unsupported file format).
function BASS_ErrorGetCode(): Integer;
external 'BASS_ErrorGetCode#files:bass.dll stdcall';
SoundStream := BASS_StreamCreateFile(...);
if SoundStream = 0 then
begin
Log(Format('Error playing file, error code = %d', [BASS_ErrorGetCode]));
end;
But as you correctly hinted, you should use the BASS_MusicLoad for MO3 / IT / XM / S3M / MTM / MOD / UMX formats.
type
HMUSIC = DWORD;
function BASS_MusicLoad(
mem: BOOL; f: string; offset: Int64; length, flags, freq: DWORD): HMUSIC;
external 'BASS_MusicLoad#files:bass.dll stdcall';
Replace the BASS_StreamCreateFile call with:
BASS_MusicLoad(
False, ExpandConstant('{tmp}\tune.xm'), 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP, 0)
Semantically, your should also rename the SoundStream variable to Music or similar; and change its type to HMUSIC.
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;
Is it possible to have an image button in Inno Wizard page instead of a plain Caption Text?
What I would like to accomplish is to create an Off/On image button to mute/play a music while Inno setup is running.
Thanks!
There's no direct support for setting images for buttons in Inno Setup.
So you have to revert to Win32 API.
function LoadImage(hInst: Integer; ImageName: string; ImageType: UINT;
X, Y: Integer; Flags: UINT): THandle;
external 'LoadImageW#User32.dll stdcall';
function ImageList_Add(ImageList: THandle; Image, Mask: THandle): Integer;
external 'ImageList_Add#Comctl32.dll stdcall';
function ImageList_Create(CX, CY: Integer; Flags: UINT;
Initial, Grow: Integer): THandle;
external 'ImageList_Create#Comctl32.dll stdcall';
const
IMAGE_BITMAP = 0;
LR_LOADFROMFILE = $10;
ILC_COLOR32 = $20;
BCM_SETIMAGELIST = $1600 + $0002;
type
BUTTON_IMAGELIST = record
himl: THandle;
margin: TRect;
uAlign: UINT;
end;
function SendSetImageListMessage(
Wnd: THandle; Msg: Cardinal; WParam: Cardinal;
var LParam: BUTTON_IMAGELIST): Cardinal;
external 'SendMessageW#User32.dll stdcall';
function InitializeSetup(): Boolean;
var
ImageList: THandle;
Image: THandle;
ButtonImageList: BUTTON_IMAGELIST;
begin
ImageList := ImageList_Create(16, 16, ILC_COLOR32, 1, 1);
Image := LoadImage(0, 'button.bmp', IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
ImageList_Add(ImageList, Image, 0);
ButtonImageList.himl := ImageList;
SendSetImageListMessage(
WizardForm.NextButton.Handle, BCM_SETIMAGELIST, 0, ButtonImageList);
end;
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;