Refresh shell associations in Inno Setup conditionally (only if a function is checked) - windows

As the title says how can I make that Inno Setup use the:
[Setup]
ChangesAssociations=yes
Only when a certain function is checked:
function installation: Boolean;
begin
Result := install.Checked; { only if this is checked }
end;
function portable: Boolean;
begin
Result := porta.Checked;
end;
I need that association doesnt get called when I simply extract the portable version of my software.

Fyi, in the next version you will be able to write:
[Setup]
ChangesAssociations=installation
[Code]
function installation: Boolean;
begin
Result := install.Checked; { only if this is checked }
end;
Thanks for the idea :)

Instead of using ChangesAssociations directive, call SHChangeNotify WinAPI function conditionally from CurStepChanged(ssPostInstall):
[Code]
const
SHCNE_ASSOCCHANGED = $08000000;
SHCNF_IDLIST = $00000000;
procedure SHChangeNotify(wEventID: Integer; uFlags: Cardinal; dwItem1, dwItem2: Cardinal);
external 'SHChangeNotify#shell32.dll stdcall';
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
if installation then
begin
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0);
end;
end;
end;
This is what ChangesAssociations=yes internally does.
Partially based on: Inno Setup refresh desktop.

Related

Detect if restart is pending before installing in Inno Setup

Is there a way to prevent installation if a reboot/restart is already pending/required?
Our setup installs SQL Server Express and it will sometimes refuse to do so if there is a pending restart in the system. Can Inno Setup detect this condition so I can tell the user to reboot before installing our software?
I know about MakePendingFileRenameOperationsChecksum but it's usually mentioned to detect whether the reboot required condition appeared DURING the setup. Can it be used BEFORE?
If you want to detect, if there is a pending rename that requires a restart, query PendingFileRenameOperations registry value.
See also How to find out if an MSI I just installed requested a Windows reboot?
function IsRestartPending: Boolean;
var
S: string;
begin
if RegQueryMultiStringValue(
HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager',
'PendingFileRenameOperations', S) then
begin
Log(Format('PendingFileRenameOperations value exists with value [%s]', [S]));
Result := (Trim(S) <> ''); { This additional check is probably not needed }
end
else
begin
Log('PendingFileRenameOperations value does not exist');
Result := False;
end;
end;
function InitializeSetup(): Boolean;
begin
if IsRestartPending then
begin
MsgBox('Restart your machine please', mbError, MB_OK);
Result := False;
Exit;
end;
Result := True;
end;
If you need to test for other actions that may need restart, you will have to adapt the answer by #Jerry for Inno Setup.
The other accepted answer only covered one scenario. However, there are actually numerous different scenarios to check. I found this article describing all the different registry things to check, and wrote a function around it. This is actually in Delphi, but should be easily implemented in Inno Setup as well. In fact, part of it I got from here, which was in Inno Setup, for checking valid GUID.
Please note that this generally checks for pending Windows updates, which may not necessarily be desired. You can modify it and remove those checks you don't wish to perform for your purpose. The second and third checks for PendingFileRenameOperations actually appear not necessary for SQL Server.
const
S_OK = $00000000;
CO_E_CLASSSTRING = $800401F3;
type
LPCLSID = TGUID;
LPCOLESTR = WideString;
function CLSIDFromString(lpsz: LPCOLESTR; pclsid: LPCLSID): HRESULT;
stdcall; external 'ole32.dll';
function IsValidGuid(const Value: string): Boolean;
var
GUID: LPCLSID;
RetVal: HRESULT;
begin
RetVal := CLSIDFromString(LPCOLESTR(Value), GUID);
Result := RetVal = S_OK;
if not Result and (RetVal <> CO_E_CLASSSTRING) then
OleCheck(RetVal);
end;
function IsRestartPending(const RestartOnly: Boolean = True): Boolean;
var
R: TRegistry;
L: TStringList;
X: Integer;
T: String;
begin
R:= TRegistry.Create(KEY_READ);
try
L:= TStringList.Create;
try
Result:= False;
R.RootKey:= HKEY_LOCAL_MACHINE;
if R.OpenKey('SOFTWARE\Microsoft\Updates', False) then begin
try
if R.ValueExists('UpdateExeVolatile') then begin
if R.ReadInteger('UpdateExeVolatile') <> 0 then
Result:= True;
end;
finally
R.CloseKey;
end;
end;
if not RestartOnly then begin
//These next 2 checks are not necessary for a SQL Server installation.
if not Result then begin
if R.OpenKey('SYSTEM\CurrentControlSet\Control\Session Manager', False) then begin
try
Result:= R.ValueExists('PendingFileRenameOperations');
finally
R.CloseKey;
end;
end;
end;
if not Result then begin
if R.OpenKey('SYSTEM\CurrentControlSet\Control\Session Manager', False) then begin
try
Result:= R.ValueExists('PendingFileRenameOperations2');
finally
R.CloseKey;
end;
end;
end;
end;
if not Result then begin
Result:= R.KeyExists('SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired');
end;
if not Result then begin
if R.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending', False) then begin
try
L.Clear;
R.GetKeyNames(L);
for X := 0 to L.Count-1 do begin
if IsValidGuid(L[X]) then begin
Result:= True;
Break;
end;
end;
finally
R.CloseKey;
end;
end;
end;
if not Result then begin
Result:= R.KeyExists('SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting');
end;
if not Result then begin
if R.OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', False) then begin
try
Result:= R.ValueExists('DVDRebootSignal');
finally
R.CloseKey;
end;
end;
end;
if not Result then begin
Result:= R.KeyExists('Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending');
end;
if not Result then begin
Result:= R.KeyExists('Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress');
end;
if not Result then begin
Result:= R.KeyExists('Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending');
end;
if not Result then begin
Result:= R.KeyExists('SOFTWARE\Microsoft\ServerManager\CurrentRebootAttempts');
end;
if not Result then begin
if R.OpenKey('SYSTEM\CurrentControlSet\Services\Netlogon', False) then begin
try
Result:= R.ValueExists('JoinDomain');
finally
R.CloseKey;
end;
end;
end;
if not Result then begin
if R.OpenKey('SYSTEM\CurrentControlSet\Services\Netlogon', False) then begin
try
Result:= R.ValueExists('AvoidSpnSet');
finally
R.CloseKey;
end;
end;
end;
if not Result then begin
if R.OpenKey('SYSTEM\CurrentControlSet\Control\ComputerName\ActiveComputerName', False) then begin
try
T:= R.ReadString('ComputerName');
finally
R.CloseKey;
end;
end;
if R.OpenKey('SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName', False) then begin
try
if R.ReadString('ComputerName') <> T then
Result:= True;
finally
R.CloseKey;
end;
end;
end;
finally
L.Free;
end;
finally
R.Free;
end;
end;

Referencing Service.Name crashes after moving service code to ancestor

I had a unit for a Windows (32 bit) service that was built up like this:
unit uSvcBase;
interface
type
TMyServiceBase = class(TService)
procedure ServiceBeforeUninstall(Sender: TService);
procedure ServiceCreate(Sender: TObject);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceExecute(Sender: TService);
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure ServiceAfterInstall(Sender: TService);
private
public
function GetServiceController: TServiceController; override;
end;
var
MyServiceBase: TMyServiceBase;
implementation
{$R *.DFM}
{$R SvcEventLogMessages.res}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
MyServiceBase.Controller(CtrlCode);
end;
function TMyServiceBase.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
const
rsServiceMessages =
'SYSTEM\CurrentControlSet\Services\EventLog\Application';
procedure TMyServiceBase.ServiceAfterInstall(Sender: TService);
var
lReg : TRegistry;
lAppName: String;
begin
lReg := TRegistry.create;
try
with lReg do
begin
Rootkey := HKEY_LOCAL_MACHINE;
if OpenKey(rsServiceMessages, False) then
begin
if OpenKey(MyServiceBase.Name, True) then
begin
lAppName := ParamStr(0);
WriteString('EventMessageFile', lAppName);
WriteString('CategoryMessageFile', lAppName);
WriteInteger('CategoryCount', 2);
WriteInteger('TypesSupported', EVENTLOG_ERROR_TYPE OR EVENTLOG_WARNING_TYPE OR EVENTLOG_INFORMATION_TYPE);
CloseKey;
end;
CloseKey;
end; { if OpenKey }
end; { with lReg }
finally
lReg.Free;
end;
end;
Because I needed to make a second service which was largely identical, I decided to make this a 'base' unit that others derive from (you can already see that in the names above):
unit uSvcTasks;
interface
uses
System.SysUtils, System.Classes, uSvcBase;
type
TMyServiceScheduler = class(TMyServiceBase)
procedure ServiceCreate(Sender: TObject);
private
public
end;
var
MyServiceScheduler: TMyServiceScheduler;
implementation
{%CLASSGROUP 'System.Classes.TPersistent'}
{$R *.dfm}
Uses uTypesAlgemeen;
procedure TMyServiceScheduler.ServiceCreate(Sender: TObject);
begin
inherited;
// Set some properties
end;
At design time, the MyServiceScheduler.Name in this descendant differs from the MyServiceBase.Name.
Issue: The AfterInstall now crashed. Trying to use the original code using OpenKey(MyServiceBase.Name was not allowed.
I worked around his by using a property for the name (setting it in the descendant Create), but I do not understand why referencing MyServiceBase.Name in the AfterInstall does not work. Can anyone explain?
Thanks to Uwe Raabe's comments I was able to figure out how to fix this:
The project had an Application.CreateForm(TMyServiceScheduler, MyServiceScheduler) in the project source which initializes MyServiceScheduler, but there was nothing initializing MyServiceBase, so refering to it was illegal.
Replace the reference to MyServiceBase.Name with Name in the AfterInstall(That should've been done anyway).
Move the code for the ServiceController from uSvcBase to uSvcTasks

Inno Setup - Check if file exist in destination or else if doesn't abort the installation

I need my installer to check if a file exists in the destination location, and if is not there, then the installation aborts. My project is a update patch, so I want the installer to avoid installing the update files if the main exe of the application is not in the destination. How can I do this?
Can someone give an example of code to check file version through the Windows registry?
[Files]
Source C:\filename.exe; DestDir {app}; Flags: ignoreversion; BeforeInstall: CheckForFile;
[code]
procedure CheckForFile(): Boolean;
begin
if (FileExists('c:\somefile.exe')) then
begin
MsgBox('File exists, install continues', mbInformation, MB_OK);
Result := True;
end
else
begin
MsgBox('File does not exist, install stops', mbCriticalError, MB_OK);
Result := False;
end;
end;
Just don't let the user proceed until they pick the correct folder.
function NextButtonClick(PageId: Integer): Boolean;
begin
Result := True;
if (PageId = wpSelectDir) and not FileExists(ExpandConstant('{app}\yourapp.exe')) then begin
MsgBox('YourApp does not seem to be installed in that folder. Please select the correct folder.', mbError, MB_OK);
Result := False;
exit;
end;
end;
Of course, it's also a good idea to try to automatically pick the correct folder for them, eg. by retrieving the correct location out of the registry.
Another solution would be the InitializeSetup():
Credit: Manfred
[code]
function InitializeSetup(): Boolean;
begin
if (FileExists(ExpandConstant('{pf}\{#MyAppName}\somefile.exe'))) then
begin
MsgBox('Installation validated', mbInformation, MB_OK);
Result := True;
end
else
begin
MsgBox('Abort installation', mbCriticalError, MB_OK);
Result := False;
end;
end;

How do I close open OLE dialogs

I have a function that closes all the forms in the application apart from the main form
procedure CloseOpenForms(const Component: TComponent);
var
i: Integer;
begin
for i := 0 to pred(Component.ComponentCount) do
begin
CloseOpenForms(Component.Components[i]);
if Component.Components[i] is TForm then
begin
TForm(Component.Components[i]).OnCloseQuery := nil;
TForm(Component.Components[i]).Close;
end;
end;
end;
Called from the main form:
CloseOpenForms(Self);
It works fine as long as there are no active OLE dialogs (e.g. TJvObjectPickerDialog).
How can I force these non modal OLE dialogs to close?
JVCL passes the application handle to 'hwndParent' parameter of IDSObjectPicker.InvokeDialog, hence the dialog is owned (not like 'owner' as in VCL, but more like popup parent) by the application window. Then you can enurate windows to find out the ones owned by the application window and post them a close command.
procedure CloseOpenForms(const AComponent: TComponent);
function CloseOwnedWindows(wnd: HWND; lParam: LPARAM): BOOL; stdcall;
begin
Result := TRUE;
if (GetWindow(wnd, GW_OWNER) = HWND(lParam)) and (not IsVCLControl(wnd)) then
begin
if not IsWindowEnabled(wnd) then // has a modal dialog of its own
EnumWindows(#CloseOwnedWindows, wnd);
SendMessage(wnd, WM_CLOSE, 0, 0);
end;
end;
procedure CloseOpenFormsRecursive(const RecComponent: TComponent);
var
i: Integer;
begin
for i := 0 to pred(RecComponent.ComponentCount) do
begin
CloseOpenFormsRecursive(RecComponent.Components[i]);
if RecComponent.Components[i] is TForm then
begin
TForm(RecComponent.Components[i]).OnCloseQuery := nil;
TForm(RecComponent.Components[i]).Close;
end;
end;
end;
begin
EnumWindows(#CloseOwnedWindows, Application.Handle);
CloseOpenFormsRecursive(AComponent)
end;

How to get the Windows version is Vista and up versus XP on Delphi?

Is there any way to know which verion of Windows we are working on?
I need to set image to TBitButton in Windows XP and no image in Windows7. It should be done automatically.
Check the SysUtils.Win32MajorVersion (in Delphi 7, you'll need to add SysUtils to your uses clause if it's not there already - later versions add it automatically). The easiest way is to assign the Glyph as usual in the IDE, and clear it if you're running on Vista or higher:
if SysUtils.Win32MajorVersion >= 6 then // Windows Vista or higher
BitBtn1.Glyph := nil;
For more info on detecting specific Windows editions and versions, see this post. It hasn't been updated for the latest Windows versions and editions, but it'll get you started. You can also search SO for [delphi] GetVersionEx to see other examples.
This is actually a little project of mine - a drop-in component which provides info of the operating system - even preview it in design-time...
unit JDOSInfo;
interface
uses
Classes, Windows, SysUtils, StrUtils, Forms, Registry;
type
TJDOSInfo = class(TComponent)
private
fReg: TRegistry;
fKey: String;
fMinor: Integer;
fMajor: Integer;
fBuild: Integer;
fPlatform: Integer;
fIsServer: Bool;
fIs64bit: Bool;
fProductName: String;
function GetProductName: String;
procedure SetProductName(Value: String);
procedure SetMajor(Value: Integer);
procedure SetMinor(Value: Integer);
procedure SetBuild(Value: Integer);
procedure SetPlatform(Value: Integer);
procedure SetIs64Bit(const Value: Bool);
procedure SetIsServer(const Value: Bool);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Major: Integer read fMajor write SetMajor;
property Minor: Integer read fMinor write SetMinor;
property Build: Integer read fBuild write SetBuild;
property Platf: Integer read fPlatform write SetPlatform;
property ProductName: String read GetProductName write SetProductName;
property IsServer: Bool read fIsServer write SetIsServer;
property Is64Bit: Bool read fIs64bit write SetIs64Bit;
end;
function IsWOW64: Boolean;
function GetOSInfo: TOSVersionInfo;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('JD Custom', [TJDOSInfo]);
end;
function GetOSInfo: TOSVersionInfo;
begin
FillChar(Result, SizeOf(Result), 0);
Result.dwOSVersionInfoSize := SizeOf(Result);
if not GetVersionEx(Result) then
raise Exception.Create('Error calling GetVersionEx');
end;
function IsWOW64: Boolean;
type
TIsWow64Process = function( // Type of IsWow64Process API fn
Handle: THandle;
var Res: BOOL): BOOL; stdcall;
var
IsWow64Result: BOOL; // result from IsWow64Process
IsWow64Process: TIsWow64Process; // IsWow64Process fn reference
begin
// Try to load required function from kernel32
IsWow64Process:= GetProcAddress(GetModuleHandle('kernel32'),'IsWow64Process');
if Assigned(IsWow64Process) then
begin
// Function is implemented: call it
if not IsWow64Process(GetCurrentProcess, IsWow64Result) then
raise Exception.Create('Bad process handle');
// Return result of function
Result := IsWow64Result;
end else
// Function not implemented: can't be running on Wow64
Result:= False;
end;
constructor TJDOSInfo.Create(AOwner: TComponent);
var
Info: TOSVersionInfo;
Str: String;
begin
inherited Create(AOwner);
fReg:= TRegistry.Create(KEY_READ);
fReg.RootKey:= HKEY_LOCAL_MACHINE;
fKey:= 'Software\Microsoft\Windows NT\CurrentVersion';
fReg.OpenKey(fKey, False);
Info:= GetOSInfo;
fMajor:= Info.dwMajorVersion;
fMinor:= Info.dwMinorVersion;
fBuild:= Info.dwBuildNumber;
fIsServer:= False;
fIs64bit:= False;
fPlatform:= Info.dwPlatformId;
if fMajor >= 5 then begin
//After 2000
if fReg.ValueExists('ProductName') then
Str:= fReg.ReadString('ProductName')
else begin
Str:= 'Unknown OS: '+IntToStr(fMajor)+'.'+IntToStr(fMinor)+'.'+
IntToStr(fBuild)+'.'+IntToStr(fPlatform);
end;
if fReg.ValueExists('InstallationType') then begin
if UpperCase(fReg.ReadString('InstallationType')) = 'SERVER' then
fIsServer:= True;
end;
fIs64bit:= IsWOW64;
if fIs64bit then
Str:= Str + ' 64 Bit';
end else begin
//Before 2000
case fMajor of
4: begin
case fMinor of
0: Str:= 'Windows 95';
10: Str:= 'Windows 98';
90: Str:= 'Windows ME';
end;
end;
else begin
Str:= 'Older than 95';
end;
end;
end;
Self.fProductName:= Str;
end;
destructor TJDOSInfo.Destroy;
begin
if assigned(fReg) then begin
if fReg.Active then
fReg.CloseKey;
fReg.Free;
end;
inherited Destroy;
end;
function TJDOSInfo.GetProductName: String;
begin
Result:= Self.fProductName;
end;
procedure TJDOSInfo.SetProductName(Value: String);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetMinor(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetMajor(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetBuild(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetPlatform(Value: Integer);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetIs64Bit(const Value: Bool);
begin
//Do Nothing Here!
end;
procedure TJDOSInfo.SetIsServer(const Value: Bool);
begin
//Do Nothing Here!
end;
end.

Resources