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'.
Related
I have a license.exe file that I call in my setup code at the end,
The code needs the environment variable to be set before working correctly,
The code is as follows:
[Registry]
; set PATH
Root: HKLM; \
Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: string; ValueName: "PATH"; ValueData: "{app}"
[Setup]
; Tell Windows Explorer to reload the environment
ChangesEnvironment=yes
[Run]
Filename: "{app}\temp\installation_files\license.exe";
Here the code executes, but does not find the correct path.
When I check the system environment variable, it is set correctly,
When I run the license.exe code afterwards manually, it works correctly and sees the environment variable.
Can someone tell me how to fix this?
Or how to delay the [Run] section until the system recognizes the environment variable?
The processes created for executing entries from the [Run] section inherits the environment block of its parent process, which is the installer itself. So you have to set the environment variable to the installer and let it inherit to your executed application. How to do that is shown in the below script:
[Run]
Filename: "{app}\temp\installation_files\license.exe"; BeforeInstall: SetEnvPath
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function SetEnvironmentVariable(lpName: string; lpValue: string): BOOL;
external 'SetEnvironmentVariable{#AW}#kernel32.dll stdcall';
procedure SetEnvPath;
begin
if not SetEnvironmentVariable('PATH', ExpandConstant('{app}')) then
MsgBox(SysErrorMessage(DLLGetLastError), mbError, MB_OK);
end;
Previous answer for notifying rest of the system about variable change:
As #Jerry pointed out in his comment, a notification about the environment changes is performed after the [Run] section is processed. Actually, it is one of the last things executed by the installer.
So, to notify the system about environment changes before processing the [Run] section, you'll need to have a workaround. I rewrote the RefreshEnvironment procedure from Inno Setup code to script. It's the same function as it's executed if you have ChangesEnvironment directive set to yes.
In the following script I have removed the ChangesEnvironment directive and added execution of the RefreshEnvironment procedure from the AfterInstall parameter function of your registry entry:
[Registry]
Root: HKLM; \
Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
ValueType: string; ValueName: "PATH"; ValueData: "{app}"; \
AfterInstall: RefreshEnvironment;
[Run]
Filename: "{app}\temp\installation_files\license.exe";
[Code]
const
SMTO_ABORTIFHUNG = 2;
WM_WININICHANGE = $001A;
WM_SETTINGCHANGE = WM_WININICHANGE;
type
WPARAM = UINT_PTR;
LPARAM = INT_PTR;
LRESULT = INT_PTR;
function SendTextMessageTimeout(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: PAnsiChar; fuFlags: UINT;
uTimeout: UINT; out lpdwResult: DWORD): LRESULT;
external 'SendMessageTimeoutA#user32.dll stdcall';
procedure RefreshEnvironment;
var
S: AnsiString;
MsgResult: DWORD;
begin
S := 'Environment';
SendTextMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;
The solution with SetEnvironmentVariable in TLama's answer is correct for many situations.
But it won't work for [Run] tasks with runasoriginaluser flag (what is implied by postinstall flag). I.e. the variable won't be propagated to an application run with common "Run My Program" check box on the "Finished" page.
The reason is that the tasks with runasoriginaluser are executed by a un-elevated hidden parent process of the Inno Setup installer. The SetEnvironmentVariable will change environment for the installer, but not for its parent process. Unfortunately, the parent process of the installer cannot be controlled (imo).
As a workaround, to set the variable for the runasoriginaluser tasks, you have to inject an intermediate process between the installer parent process and the task, and have the intermediate process set the variable.
Such an intermediate process can easily be the cmd.exe with its set command:
[Run]
Filename: "{cmd}"; Parameters: "/C set PATH=%PATH%;{app} & ""{app}\MyProg.exe"""; \
Description: "Run My Program"; Flags: postinstall runhidden
The runhidden flag hides the cmd.exe console window, not the application (assuming it's a GUI application). If it's a console application and you want the output to be visible, remove the runhidden flag. Alternatively, you can use start command to start the application in its own console window.
after some modifications below, worked perfectly:
[Run]
Filename: "{app}\{#MyAppExeName}"; BeforeInstall: AppendToPathAndRefresh;Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}";Flags: nowait postinstall shellexec skipifsilent
[Code]
////////////////////////////////////////////////////////////
const
SMTO_ABORTIFHUNG = 2;
WM_WININICHANGE = $001A;
type
WPARAM = UINT_PTR;
LPARAM = INT_PTR;
LRESULT = INT_PTR;
function SendTextMessageTimeout(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: PAnsiChar; fuFlags: UINT;
uTimeout: UINT; out lpdwResult: DWORD): LRESULT;
external 'SendMessageTimeoutA#user32.dll stdcall';
procedure RefreshEnvironment;
var
S: AnsiString;
MsgResult: DWORD;
begin
S := 'Environment';
SendTextMessageTimeout(HWND_BROADCAST, WM_WININICHANGE, 0,
PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;
///PATH ENVINRONMENT//////////////////////////////////////////
function Replace(Dest, SubStr, Str: string): string;
var
Position: Integer;
Ok: Integer;
begin
Ok := 1;
while Ok > 0 do
begin
Position:=Pos(SubStr, Dest);
if Position > 0 then
begin
Delete(Dest, Position, Length(SubStr));
Insert(Str, Dest, Position);
end else
Ok := 0;
end;
Result:=Dest;
end;
procedure AppendToPath();
var
V: string;
Str: string;
begin
RegQueryStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
Str := ExpandConstant('{app}\libav');
V := Replace(V, Str, '');
V := V + ';' + Str;
V := Replace(V,';;',';');
RegWriteStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
// MsgBox(V, mbInformation, MB_OK);
end;
procedure RemoveFromPath();
var
V: string;
Str: string;
begin
RegQueryStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
Str := ExpandConstant('{app}\dlls');
V := Replace(V, Str, '');
V := Replace(V,';;',';');
RegWriteStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
//MsgBox(V, mbInformation, MB_OK);
end;
procedure AppendToPathAndRefresh;
begin
AppendToPath;
RefreshEnvironment;
end;
procedure DeinitializeUninstall();
begin
RemoveFromPath();
end;
///END OF PATH ENVIRONMENT ///////////////////////////////////
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"
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;
My idea is to offer the user the choice of installing the application either in {localappdata} or {commonappdata}, and that part works fine. However, I'm using custom Inno Setup skinning support, which requires a couple functions to get loaded during installation/uninstallation.
I have a DefaultAppDataFolder(): String function which returns the folder that the user chose (either common or local appdata one), and it works fine during installation. However, when I try to uninstall the app, it throws following error right upon execution:
Script error: Could not call proc
You can see that in the DefaultAppDataFolder() function I'm getting the uninstall directory in a bit shady way, extracting the file directory twice from the {UninstallExe} constant, maybe there is a better way to retrieve this?
Here is my script:
#define ApplicationName "MyApp"
#define ApplicationExe "app.exe"
#define ApplicationInstanceMutex "APPMUTEX"
#define InstallerFilename "install_app"
#define SkinName "Carbon.vsf"
[Setup]
AppName={#ApplicationName}
AppVerName={#ApplicationName}
DefaultDirName={code:DefaultAppDataFolder}
DefaultGroupName={#ApplicationName}
UninstallFilesDir={code:DefaultAppDataFolder}\uninstall
UninstallDisplayName={#ApplicationName}
Compression=lzma2
SolidCompression=yes
OutputDir=.\
DisableDirPage=yes
OutputBaseFilename={#InstallerFilename}
UninstallDisplayIcon={code:DefaultAppDataFolder}\{#ApplicationExe}
DisableProgramGroupPage=yes
AppMutex={#ApplicationInstanceMutex}
WizardImageFile=installer_images\installer-1.bmp
WizardSmallImageFile=installer_images\installer-2.bmp
[Files]
Source: "skins\VclStylesInno.dll"; DestDir: "{code:DefaultAppDataFolder}"; Flags: uninsneveruninstall ignoreversion
Source: "skins\{#SkinName}"; DestDir: "{code:DefaultAppDataFolder}"; Flags: ignoreversion
Source: "root_files\*.*"; DestDir: "{code:DefaultAppDataFolder}"; Flags: ignoreversion
Source: "client_files\*.*"; DestDir: "{code:DefaultAppDataFolder}"; Flags: ignoreversion
Source: "ssl_libs\*.*"; DestDir: "{code:DefaultAppDataFolder}"; Flags: ignoreversion
[Icons]
Name: "{group}\{#ApplicationName}"; Filename: "{code:DefaultAppDataFolder}\{#ApplicationExe}"; WorkingDir: "{code:DefaultAppDataFolder}"
Name: "{group}\Uninstall"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#ApplicationName}"; Filename: "{code:DefaultAppDataFolder}\{#ApplicationExe}"; Tasks: desktopicon
[Run]
Filename: "{code:DefaultAppDataFolder}\{#ApplicationExe}"; Description: "Launch {#ApplicationName}"; Flags: postinstall nowait runascurrentuser
[Tasks]
Name: commondir; Description: "&All users"; GroupDescription: "Install For:"; Flags: exclusive
Name: localdir; Description: "&Current user"; GroupDescription: "Install For:"; Flags: exclusive unchecked
Name: desktopicon; Description: "Create a &desktop icon"
[Code]
procedure LoadVCLStyleS(VClStyleFile: String); external 'LoadVCLStyleW#files:VclStylesInno.dll stdcall setuponly';
procedure UnLoadVCLStylesS; external 'UnLoadVCLStyles#files:VclStylesInno.dll stdcall setuponly';
procedure LoadVCLStyleU(VClStyleFile: String); external 'LoadVCLStyleW#{code:DefaultAppDataFolder}\VclStylesInno.dll stdcall uninstallonly';
procedure UnLoadVCLStylesU; external 'UnLoadVCLStyles#{code:DefaultAppDataFolder}\VclStylesInno.dll stdcall uninstallonly';
var
ApplicationUninstalled: Boolean;
WizardInitialized: Boolean;
function InitializeSetup(): Boolean;
var
C1: Integer;
begin
ExtractTemporaryFile('{#SkinName}');
LoadVCLStyleS(ExpandConstant('{tmp}\{#SkinName}'));
result := TRUE;
end;
procedure InitializeWizard();
begin
WizardInitialized := TRUE;
end;
procedure DeinitializeSetup();
begin
UnLoadVCLStylesS;
end;
function InitializeUninstall(): Boolean;
begin
LoadVCLStyleU(ExpandConstant('{code:DefaultAppDataFolder}\{#SkinName}'));
result := TRUE;
end;
procedure InitializeUninstallProgressForm();
begin
ApplicationUninstalled := TRUE;
end;
procedure DeinitializeUninstall();
begin
UnLoadVCLStylesU;
UnloadDLL(ExpandConstant('{code:DefaultAppDataFolder}\VclStylesInno.dll'));
if ApplicationUninstalled then
begin
DeleteFile(ExpandConstant('{code:DefaultAppDataFolder}\VclStylesInno.dll'));
RemoveDir(ExpandConstant('{code:DefaultAppDataFolder}'));
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
mres: integer;
begin
case CurUninstallStep of
usPostUninstall: begin
mres := MsgBox('Do you want to delete user data?', mbConfirmation, MB_YESNO or MB_DEFBUTTON2)
if mres = IDYES then
begin
DelTree(ExpandConstant('{localappdata}\{#ApplicationName}'), TRUE, TRUE, TRUE);
DelTree(ExpandConstant('{userappdata}\{#ApplicationName}'), TRUE, TRUE, TRUE);
end;
end;
end;
end;
function DefaultAppDataFolder(Param: String): String;
begin
if IsUninstaller then
result := ExtractFileDir(ExtractFileDir(ExpandConstant('{uninstallexe}')))
else
if (WizardInitialized) and
(IsTaskSelected('localdir')) then
result := ExpandConstant('{localappdata}') + '\Programs\{#ApplicationName}'
else
result := ExpandConstant('{commonappdata}') + '\Programs\{#ApplicationName}';
end;
The error you got is not related to the uninstaller; you can narrow the problem down to this:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName=My Program
[Code]
procedure DoSomething;
external 'DoSomething#{code:GetLibPath}\MyLib.dll stdcall setuponly';
function GetLibPath(Value: string): string;
begin
Result := 'C:\ValidPathToLib';
end;
From the above script it seems that you cannot use scripted constants for imported DLL file names. And even delayed loading didn't workaround this limit. But for your case you can use just {app} path since your library is actually there (if I get your intention right):
...
procedure LoadVCLStyleU(VClStyleFile: string);
external 'LoadVCLStyleW#{app}\VclStylesInno.dll stdcall uninstallonly';
procedure UnLoadVCLStylesU;
external 'UnLoadVCLStyles#{app}\VclStylesInno.dll stdcall uninstallonly';
...
I'm using Inno Setup to change the recycle bin in the OS. I need to make some cases for if the user is running Windows 7 or Windows XP. I try using:
if not FileExists(winDir + '\System32\imageres.dll') then
if not FileExists(winDir + '\System32\shell32.dll') then
installError(3);
But it seems like it can't find imageres.dll or shell32.dll even though I've verified they exist. What am I doing wrong? Or can I check the Windows version another way?
In most Inno Setup sections (like [Files], [Tasks], [Run], etc.) you can use the MinVersion and OnlyBelowVersion common parameters.
[Files]
Source: MyDllForVistaAndNewer.dll; Dest: {app}\MyDll.dll; MinVersion: 6.0
Source: MyDllForOldWindows.dll; Dest: {app}\MyDll.dll; OnlyBelowVersion: 6.0
In Pascal Script, use the GetWindowsVersionEx function to find the Windows version number. Then compare the number against a specific Windows version number.
Here are few handy functions to check specific Windows versions:
function IsWindowsVersionOrNewer(Major, Minor: Integer): Boolean;
var
Version: TWindowsVersion;
begin
GetWindowsVersionEx(Version);
Result :=
(Version.Major > Major) or
((Version.Major = Major) and (Version.Minor >= Minor));
end;
function IsWindowsXPOrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(5, 1);
end;
function IsWindowsVistaOrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(6, 0);
end;
function IsWindows7OrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(6, 1);
end;
function IsWindows8OrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(6, 2);
end;
function IsWindows10OrNewer: Boolean;
begin
Result := IsWindowsVersionOrNewer(10, 0);
end;
// Windows 11 has the same major.minor as Windows 10.
// So it has to be distinguished by the Build.
// The IsWindows10OrNewer condition is actually redundant.
// Once we have to test for Windows 11 using the build number, we could actually
// unify and simplify all the tests above to use the build numbers only too.
function IsWindows11OrNewer: Boolean;
var
Version: TWindowsVersion;
begin
GetWindowsVersionEx(Version);
Result := IsWindows10OrNewer and (Version.Build >= 22000);
end;
Example of use:
function InitializeSetup: Boolean;
begin
if not IsWindowsVistaOrNewer then
begin
MsgBox(
'This program was not tested on Windows XP and older, proceed with caution.',
mbCriticalError, MB_OK);
end;
Result := True;
end;
To test for server-editions of Windows, see:
Checking for Windows Server 2003
For version checking to work correctly on modern versions of Windows, make sure you always use the latest version of Inno Setup.
You should use the GetWindowsVersionEx function. It fills a TWindowsVersion record:
TWindowsVersion = record
Major: Cardinal; // Major version number
Minor: Cardinal; // Minor version number
Build: Cardinal; // Build number
ServicePackMajor: Cardinal; // Major version number of service pack
ServicePackMinor: Cardinal; // Minor version number of service pack
NTPlatform: Boolean; // True if an NT-based platform
ProductType: Byte; // Product type (see below)
SuiteMask: Word; // Product suites installed (see below)
end;
There are a lot of other related functions. See below 'System functions' at this page.
According to the documentation, the parameters associated with each file can be directly tied to the OS version:
[Files]
Source: "{app}\WinNT2000XP.exe"; DestDir: "{app}"; MinVersion: 0, 1
Source: "{app}\Win9598Me.exe"; DestDir: "{app}"; MinVersion: 1, 0
"0" means never install; "1" means install on any version (i.e. version 1.0 or later).
Note: The above technique isn't limited to the [Files] section; MinVersion and OnlyBelowVersion can be used in most sections.