Currently to access LocalLow I use this:
{%USERPROFILE}\AppData\LocalLow
But I would like to know if there's a constant for that in Inno Setup, since both Roaming and Local have one.
There's no constant for AppData\LocalLow.
You may use Pascal Scripting to resolve it.
To resolve the "LocalLow", one has to use SHGetKnownFolderPath.
See also Detect the location of AppData\LocalLow.
The implementation involves few hacks, due to a lack of (wide) PChar type in Unicode Inno Setup.
const
MAX_PATH = 260;
AppDataLocalLowGUID = '{A520A1A4-1780-4FF6-BD18-167343C5AF16}';
// There's no PChar in Unicode Inno Setup,
// pretend the function returns a pointer to an Integer
function SHGetKnownFolderPath(rfid: TGUID; dwFlags: DWORD; hToken: THandle;
var ppszPath: Integer): Integer;
external 'SHGetKnownFolderPath#Shell32.dll stdcall';
// And allow the Integer to be copied to string
function StrCpy(Dest: string; Source: Integer): Integer;
external 'StrCpyW#Shlwapi.dll stdcall';
// And allow the Integer pointer to be released
procedure CoTaskMemFreeAsInteger(pv: Integer);
external 'CoTaskMemFree#Ole32.dll stdcall';
function GetAppDataLocalLow: string;
var
Path: Integer;
I: Integer;
begin
if SHGetKnownFolderPath(StringToGUID(AppDataLocalLowGUID), 0, 0, Path) = 0 then
begin
// The path should not be longer than MAX_PATH
SetLength(Result, MAX_PATH);
StrCpy(Result, Path);
CoTaskMemFreeAsInteger(Path);
// Look for NUL character and adjust the length accordingly
SetLength(Result, Pos(#0, Result) - 1);
end;
end;
If you need to use the path in non-Code section (outside of the Pascal Script), you can use a scripted constant:
[Files]
Source: myfile.txt; DestDir: {code:GetAppDataLocalLow}
And you need to change the function signature to take a dummy parameter:
function GetAppDataLocalLow(Param: string): string;
For example, to delete file on uninstall from LocalLow with INNO:
[UninstallDelete]
Type: filesandordirs; Name: "{userappdata}\..\LocalLow\MyFile.txt"
Related
I am trying to add some additional font type to resource and then use it in my inno setup installer, only for runtime, but it is not working for OTF Font types but works perfectly for TTF font types
the code I am using is something like that:
[Setup]
AppName=My Application
AppVersion=1.5
DefaultDirName={pf}\My Application
[Files]
Source: Setup\Ubuntu.otf; DestDir: {tmp}; Flags: dontcopy
[code]
const
FR_PRIVATE = $10;
function AddFontResourceEx(lpszFilename:string;fl:dword;pdv:integer): LongInt; external 'AddFontResourceExW#gdi32.dll stdcall';
function RemoveFontResourceEx(lpszFilename:string;fl:dword;pdv:integer): Boolean; external 'RemoveFontResourceExW#gdi32.dll stdcall';
function InitializeSetup(): Boolean;
begin
ExtractTemporaryFile('Ubuntu.otf');
if (AddFontResourceEx(ExpandConstant('{tmp}\Ubuntu.otf'),FR_PRIVATE,0) = 0) then
MsgBox('failed adding font - Ubuntu.otf',mbInformation,MB_OK);
result := true;
end;
procedure InitializeWizard();
begin
WizardForm.WelcomeLabel1.Font.Name := 'Ubuntu';
end;
procedure DeinitializeSetup();
begin
RemoveFontResourceEx(ExpandConstant('{tmp}\Ubuntu.otf'),FR_PRIVATE,0);
end;
AddFontResourceEx function is always successful but there is no effect on font, it is using fail safe default font instead
I think you need to add Flag Parameter 'fontisnttruetype'.
I'm trying to figure out how to use WinAPI functions from Pascal Script/Inno Setup. I didn't find much code examples how to do it and I'm not a Pascal programmer. Here's what I did so far:
Importing the function
function PathCombine (
pszPathOut : PChar;
pszPathIn : PChar;
pszMore : PChar
) : PChar;
external 'PathCombineA#Shlwapi.dll stdcall';
and using it like this:
function InitializeSetup(): Boolean;
var
a, b,c : PChar;
s : string;
begin
SetLength(s, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
c := PathCombine(s, a, b);
MsgBox(s, mbInformation, MB_OK);
end;
The output is this:
The expected output is:
C:\one\two
I'm pretty sure I'm accessing garbage values in memory but I don't know why, how do I fix this?
You didn't specify if you are using Ansi or Unicode version of Inno Setup.
But this should work in either version:
function PathCombine(
pszPathOut : PAnsiChar;
pszPathIn : PAnsiChar;
pszMore : PAnsiChar
) : PAnsiChar; external 'PathCombineA#Shlwapi.dll stdcall';
function InitializeSetup(): Boolean;
var
a, b, c: AnsiString;
begin
SetLength(c, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
PathCombine(c, a, b);
MsgBox(c, mbInformation, MB_OK);
Result := True;
end;
Though I strongly encourage you to use Unicode version of Inno Setup and PathCombineW instead.
function PathCombine(
pszPathOut : string;
pszPathIn : string;
pszMore : string
) : Cardinal; external 'PathCombineW#Shlwapi.dll stdcall';
function InitializeSetup(): Boolean;
var
a, b, c: string;
begin
SetLength(c, 256); { soon it gets working I'll switch to use MAX_PATH instead of }
a := 'C:';
b := 'one\two';
PathCombine(c, a, b);
MsgBox(c, mbInformation, MB_OK);
Result := True;
end;
Note that Inno Setup lacks PWideChar type. While it can marshal string to LPTSTR (PWideChar) function arguments, it cannot marshal LPTSTR return value. So I've used Cardinal for return type. It has the same size as pointer (to char), so a stack will match. And we do not actually need the returned value.
I think (although I haven't worked with Pascal/Delphi for a while) that the problem is that C "strings" (char *) are 0 index based, while Pascal strings are 1 index based (byte 0 is used to store the length).
So, if you declare your s variable as:
s: array[0..255] of Char; //Don't forget to change it to MAX_PATH afterwards
it should work. Also use the PathCombine function like this:
PathCombine(s, a, b);
There's no need to assign its result (which is the same as s) to another variable (that you aren't going to use anyway).
I have been writing a program that ideally will run on a server in the background without ever closing - therefore it is important that any memory leaks are non existent. My program involves retrieving live session information using the Windows Terminal Services API (wtsapi32.dll) and since the information must be live the function is being run every few seconds, I have found that calling the WTSEnumerateSessionsEx function has lead to a fairly sizable memory leak. It seems the call to WTSFreeMemoryEx as instructed in the MSDN documentation seems to have no impact yet I receive no error messages from either call.
To summarize: the problem is not in execution of WTSEnumerateSessionsEx since valid data is returned; the memory is simply not being freed and this leads to problems when left to run for extended periods of time.
Currently the short-term solution has been to restart the process when used memory exceeds a threshold however this doesn't seem to be a satisfactory solution and rectifying this leak would be most desirable.
The enumeration types have been taken directly from the Microsoft MSDN documentation.
Attached is the relevant source file.
unit WtsAPI32;
interface
uses Windows, Classes, Dialogs, SysUtils, StrUtils;
const
WTS_CURRENT_SERVER_HANDLE = 0;
type
WTS_CONNECTSTATE_CLASS = (WTSActive, WTSConnected, WTSConnectQuery,
WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown,
WTSInit);
type
WTS_TYPE_CLASS = (WTSTypeProcessInfoLevel0, WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1);
type
WTS_SESSION_INFO_1 = record
ExecEnvId: DWord;
State: WTS_CONNECTSTATE_CLASS;
SessionId: DWord;
pSessionName: LPtStr;
pHostName: LPtStr;
pUserName: LPtStr;
pDomainName: LPtStr;
pFarmName: LPtStr;
end;
type
TSessionInfoEx = record
ExecEnvId: DWord;
State: WTS_CONNECTSTATE_CLASS;
SessionId: DWord;
pSessionName: string;
pHostName: string;
pUserName: string;
pDomainName: string;
pFarmName: string;
end;
TSessions = array of TSessionInfoEx;
function FreeMemoryEx(WTSTypeClass: WTS_TYPE_CLASS; pMemory: Pointer;
NumberOfEntries: Integer): BOOL; stdcall;
external 'wtsapi32.dll' name 'WTSFreeMemoryExW';
function FreeMemory(pMemory: Pointer): DWord; stdcall;
external 'wtsapi32.dll' name 'WTSFreeMemory';
function EnumerateSessionsEx(hServer: THandle; var pLevel: DWord;
Filter: DWord; var ppSessionInfo: Pointer; var pCount: DWord): BOOL;
stdcall; external 'wtsapi32.dll' name 'WTSEnumerateSessionsExW';
function EnumerateSessions(var Sessions: TSessions): Boolean;
implementation
function EnumerateSessions(var Sessions: TSessions): Boolean;
type
TSessionInfoExArr = array[0..2000 div SizeOf(WTS_SESSION_INFO_1)] of WTS_SESSION_INFO_1;
var
ppSessionInfo: Pointer;
pCount: DWord;
hServer: THandle;
level: DWord;
i: Integer;
ErrCode: Integer;
Return: DWord;
begin
pCount := 0;
level := 1;
hServer := WTS_CURRENT_SERVER_HANDLE;
ppSessionInfo := NIL;
if not EnumerateSessionsEx(hServer, level, 0, ppSessionInfo, pCount) then
begin
ErrCode := GetLastError;
ShowMessage('Error in EnumerateSessionsEx - Code: ' + IntToStr(ErrCode)
+ ' Message: ' + SysErrorMessage(ErrCode));
en
else
begin
SetLength(Sessions, pCount);
for i := 0 to pCount - 1 do
begin
Sessions[i].ExecEnvId := TSessionInfoExArr(ppSessionInfo^)[i].ExecEnvId;
Sessions[i].State := TSessionInfoExArr(ppSessionInfo^)[i].State;
Sessions[i].SessionId := TSessionInfoExArr(ppSessionInfo^)[i].SessionId;
Sessions[i].pSessionName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pSessionName);
Sessions[i].pHostName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pHostName);
Sessions[i].pUserName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pUserName);
Sessions[i].pDomainName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pDomainName);
Sessions[i].pFarmName := WideCharToString
(TSessionInfoExArr(ppSessionInfo^)[i].pFarmName);
end;
if not FreeBufferEx(WTSTypeSessionInfoLevel1, ppSessionInfo, pCount);
begin
ErrCode := GetLastError;
ShowMessage('Error in EnumerateSessionsEx - Code: ' + IntToStr(ErrCode)
+ ' Message: ' + SysErrorMessage(ErrCode));
end;
ppSessionInfo := nil;
end;
end;
end.
Here's is a minimal SSCCE that demonstrates the issue. When this program executes, it exhausts available memory in short time.
program SO17839270;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows;
const
WTS_CURRENT_SERVER_HANDLE = 0;
type
WTS_TYPE_CLASS = (WTSTypeProcessInfoLevel0, WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1);
function WTSEnumerateSessionsEx(hServer: THandle; var pLevel: DWORD;
Filter: DWORD; var ppSessionInfo: Pointer; var pCount: DWORD): BOOL; stdcall;
external 'wtsapi32.dll' name 'WTSEnumerateSessionsExW';
function WTSFreeMemoryEx(WTSTypeClass: WTS_TYPE_CLASS; pMemory: Pointer;
NumberOfEntries: Integer): BOOL; stdcall;
external 'wtsapi32.dll' name 'WTSFreeMemoryExW';
procedure EnumerateSessionsEx;
var
ppSessionInfo: Pointer;
pCount: DWORD;
level: DWORD;
begin
level := 1;
if not WTSEnumerateSessionsEx(WTS_CURRENT_SERVER_HANDLE, level, 0,
ppSessionInfo, pCount) then
RaiseLastOSError;
if not WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, ppSessionInfo, pCount) then
RaiseLastOSError;
end;
begin
while True do
EnumerateSessionsEx;
end.
To summarise the comment trail, I think that there is a fault in the WTS library code, that afflicts the WTSEnumerateSessionsEx and WTSFreeMemoryEx functions. The SSCCE that I added to the question gives a pretty clear demonstration of that.
So, your options to work around the fault would appear to be:
Only call WTSEnumerateSessionsEx when you get notified that a session is created or destroyed. That would minimise the number of calls you make. You'd still be left with a leak, but I suspect that it would take a very long time before you encountered problems.
Switch to WTSEnumerateSessions and then call WTSQuerySessionInformation to obtain any extra information that you need. From my trials, WTSEnumerateSessions would appear not to be afflicted by the same problem as WTSEnumerateSessionsEx.
I created the same sample in MSVC:
#include <Windows.h>
#include <WtsApi32.h>
#pragma comment(lib, "wtsapi32")
int _tmain(int argc, _TCHAR* argv[])
{
DWORD Level = 1;
PWTS_SESSION_INFO_1 pSessionInfo;
DWORD Count = 0;
BOOL bRes;
while (WTSEnumerateSessionsEx(WTS_CURRENT_SERVER_HANDLE, &Level, 0, &pSessionInfo, &Count))
{
if (!WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, pSessionInfo, Count))
{
break;
}
}
return 0;
}
I am observing the same behaviour in Task Manager and even though Task Manager is not a tool to track memory leaks this behaviour is clearly a leak and it seems like a bug.
It happens both in x86 and x64 build (x64 uses the x64 version of WtsApi32.dll).
When you have finished using the array, free it by calling the WTSFreeMemoryEx function. You should also set the pointer to NULL.
(C) https://learn.microsoft.com/en-us/windows/desktop/api/wtsapi32/nf-wtsapi32-wtsenumeratesessionsexa
I need to get the volume serial number for a drive letter during an installation created with Inno Setup. I know that DLL functions can be imported into Inno, but I'm fairly new to it and having some problems getting it to work. I know that the GetVolumeInformation function in kernel32 can do what I need. Could someone show me how to import and use that functionality in an Inno script to retrieve the volume serial number?
Thanks!
Inno-Setup code::
[Code]
function GetVolumeInformation(
lpRootPathName: PChar;
lpVolumeNameBuffer: PChar;
nVolumeNameSize: DWORD;
var lpVolumeSerialNumber: DWORD;
var lpMaximumComponentLength: DWORD;
var lpFileSystemFlags: DWORD;
lpFileSystemNameBuffer: PChar;
nFileSystemNameSize: DWORD
): BOOL;
external 'GetVolumeInformationA#kernel32.dll stdcall';
function LoWord(dw: DWORD): WORD;
begin
Result := WORD(dw);
end;
function HiWord(dw: DWORD): WORD;
begin
Result := WORD((dw shr 16) and $FFFF);
end;
function WordToHex(w: WORD): string;
begin
Result := Format('%.4x', [w]);
end;
function FindVolumeSerial(const Drive: string): string;
var
FileSystemFlags: DWORD;
VolumeSerialNumber: DWORD;
MaximumComponentLength: DWORD;
begin
Result := '';
// Note on passing PChars using RemObjects Pascal Script:
// '' pass a nil PChar
// #0 pass an empty PChar
if GetVolumeInformation(
PChar(Drive),
'', // nil
0,
VolumeSerialNumber,
MaximumComponentLength,
FileSystemFlags,
'', // nil
0)
then
Result := WordToHex(HiWord(VolumeSerialNumber)) + '-' + WordToHex(LoWord(VolumeSerialNumber));
end;
function InitializeSetup(): Boolean;
begin
MsgBox(FindVolumeSerial('c:\'), mbInformation, mb_Ok);
end;
Tested with Inno-setup version 5.2.3
In Unicode versions of Inno-Setup replace PChar with PAnsiChar
Since the InnoSetup doesn't support pointers you will have to create the external library for the call of the GetVolumeInformation function. The following code samples should work for all combinations of the Delphi and InnoSetup (from the Unicode support point of view).
Here's the Delphi library code:
library VolumeInformation;
uses
Types, Classes, SysUtils, Windows;
var
SerialNumber: AnsiString;
function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar; stdcall;
var
FileSystemFlags: DWORD;
VolumeSerialNumber: DWORD;
MaximumComponentLength: DWORD;
begin
SerialNumber := '';
GetVolumeInformationA(Drive, nil, 0, #VolumeSerialNumber,
MaximumComponentLength, FileSystemFlags, nil, 0);
SerialNumber := IntToHex(HiWord(VolumeSerialNumber), 4) + ' - ' +
IntToHex(LoWord(VolumeSerialNumber), 4);
Result := PAnsiChar(SerialNumber);
end;
exports
GetVolumeSerial;
end.
And here's the InnoSetup code:
[Files]
Source: "VolumeInformation.dll"; Flags: dontcopy
[Code]
function GetVolumeSerial(Drive: PAnsiChar): PAnsiChar;
external 'GetVolumeSerial#files:VolumeInformation.dll stdcall setuponly';
procedure ButtonOnClick(Sender: TObject);
var
S: string;
begin
S := GetVolumeSerial('c:\');
MsgBox(S, mbInformation, mb_Ok);
end;
I'm trying to get a game to integrate with the Windows Games Explorer. So far I have embedded a Game Definition Format XML file as a resource in my binary and my actual Inno Setup script to integrate the the DLL to the installer looks like this:
This is what i've managed to do by now:
[Files]
Source: GameuxInstallHelper.dll; DestDir: "{tmp}"; Flags: dontcopy
[Code]
const
GIS_NOT_INSTALLED = 1;
GIS_CURRENT_USER = 2;
GIS_ALL_USERS = 3;
// Given a game instance GUID and path to GDF binary, registers game with Game Explorer
procedure AddToGameExplorer( GDFBinPath: PChar; GameInstallPath: PChar; InstallScope : integer; GameGUID : GUID );
external 'AddToGameExplorerA#files:GameuxInstallHelper.dll stdcall';
// Given a game instance GUID, unregisters a game from Game Explorer
function RemoveFromGameExplorer( GameGUID : GUID ) : boolean;
external 'RemoveFromGameExplorer#files:GameuxInstallHelper.dll stdcall';
// Given a path to a GDF binary that has already been registered, returns a game instance GUID
function RetrieveGUIDForApplication( GDFBinPath: PChar; var GameGUID : GUID ) : boolean;
external 'RetrieveGUIDForApplicationA#files:GameuxInstallHelper.dll stdcall';
// Creates a unique game instance GUID
function GenerateGUID( var GameGUID : GUID ) : boolean;
external 'GenerateGUID#files:GameuxInstallHelper.dll stdcall';
// Register with Media Center using the data from a GDF binary
procedure RegisterWithMediaCenter( GDFBinPath : PChar; GameInstallPath : PChar; InstallScope : integer; ExePath : PChar; CommandLineArgs : PChar; UseRegisterMCEApp : boolean );
external 'RegisterWithMediaCenterA#files:GameuxInstallHelper.dll stdcall';
// Unregister with Media Center
procedure UnRegisterWithMediaCenter( GameInstallPath : PChar; InstallScope : integer; strExePath : PChar; UseRegisterMCEApp : boolean );
external 'UnRegisterWithMediaCenterA#files:GameuxInstallHelper.dll stdcall';
// Given a a game instance GUID, creates a task shortcut in the proper location
procedure CreateTask( InstallScope : integer; GameInstanceGUID : GUID; SupportTask : boolean; TaskID : integer; TaskName :PChar; LaunchPath : PChar; CommandLineArgs : PChar );
external 'CreateTaskA#files:GameuxInstallHelper.dll stdcall';
// This removes all the tasks associated with a game instance GUID
// Pass in a valid GameInstance GUID that was passed to AddGame()
procedure RemoveTasks( GameInstanceGUID : GUID );
external 'RemoveTasks#files:GameuxInstallHelper.dll stdcall';
// Creates the registry keys to enable rich saved games. The game still needs to use
// the rich saved game header as defined in the documentation and support loading a
// saved game from the command line.
procedure SetupRichSavedGames( SavedGameExtension : PChar; LaunchPath : PChar; CommandLineToLaunchSavedGame : PChar);
external 'SetupRichSavedGamesA#files:GameuxInstallHelper.dll stdcall';
// Removes the registry keys to enable rich saved games.
procedure RemoveRichSavedGames( SavedGameExtension : PChar);
external 'RemoveRichSavedGamesA#files:GameuxInstallHelper.dll stdcall';
However, i have two errors and i can't get past them:
Unknown type 'GUID'
and
Cannot import dll:C:\Users\my user\AppData\Local\Temp\a random name folder\GameuxInstallHelper.dll
Any ideas how to solve the errors or modify the code?
As far as the issue with importing the DLL into the User folder - are you checking for administrative privileges?
Section: PrivilegesRequired