How to install files for each user, including future new users, in Inno Setup? - windows

I have an installer which needs to distribute some default files for the user to modify. Each Windows user profile needs to have its own copy of these (writable) files, including when a new user is created in Windows in the future.
I already know how to distribute to the current user's profile, but have no idea about all user profiles, especially future users. I've seen how some software can automatically include files in a new Windows user's profile.
How can I make Inno Setup distribute files in such a manner?

For all existing accounts, see:
Inno Setup Create individual shortcuts on all desktops of all users
For future accounts: Whatever is in the Default User profile gets automatically copied to all newly created profiles.
So if you want to add a file to all new users' "documents" folder, add it to the Documents folder of the Default User profile. What typically is:
C:\Users\Default\Documents
To retrieve the correct path, use SHGetFolderPath with nFolder argument set to the path you are after (e.g. CSIDL_PERSONAL for "documents" folder) and the hToken argument set to -1 (default user profile).
[Files]
Source: "default.txt"; DestDir: "{code:GetDefaultUserDocumentsPath}"
[Code]
const
CSIDL_PERSONAL = $0005;
SHGFP_TYPE_CURRENT = 0;
MAX_PATH = 260;
S_OK = 0;
function SHGetFolderPath(
hwnd: HWND; csidl: Integer; hToken: THandle; dwFlags: DWORD;
pszPath: string): HResult;
external 'SHGetFolderPathW#shell32.dll stdcall';
function GetDefaultUserDocumentsPath(Param: string): string;
var
I: Integer;
begin
SetLength(Result, MAX_PATH);
if SHGetFolderPath(0, CSIDL_PERSONAL, -1, SHGFP_TYPE_CURRENT, Result) <> S_OK then
begin
Log('Failed to resolve path to default user profile documents folder');
end
else
begin
{ Look for NUL character and adjust the length accordingly }
SetLength(Result, Pos(#0, Result) - 1);
Log(Format('Resolved path to default user profile documents folder: %s', [Result]));
end;
end;
(The code is for Unicode version of Inno Setup).

Related

SHChangeNotify not updating URL= change in my .url shortcut file

I have a simple Delphi application that creates a desktop shortcut for a URL. It makes a two-line text file with a .url filename extension in the user's Desktop folder:
[InternetShortcut]
URL=http://127.0.0.1/admin
That works fine. When I need to update the file with a new URL, I overwrite the old file. But Windows will not recognize the change until I restart Explorer or reboot. So I learned about SHChangeNotify() and called it after overwriting the file:
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH or SHCNF_FLUSH, PChar(Path), nil);
But it has no effect:
I tried with and without the SHCNF_FLUSH flag;
also the SHCNF_FLUSHNOWAIT flag makes no difference.
I also tried deleting the file first and then using the SHCNE_DELETE event and then re-creating the file. That doesn't work either, it just keeps using the old URL.
How do I force Explorer to reload the URL from the file without a restart?
While the file's content can be treated like any INI file I yet have not found a direct way to control manipulations to it:
When creating a file its content is read as expected: the system's default application for the URL='s protocol is started (i.e. for http it is most likely the internet browser).
Modifying the file per file systems has no effect - either MSIE itself maintains a cache or the COM's magic.
Indirectly manipulation is possible in the following way:
Empty the file's existing content. Why? Because the later step will just add the same INI section with an URL= value again, but the first section's URL= value remains the one that is taken into account.
Access the file per COM and change its properties. Sadly this writes more into the file - in my case the outcome/file's content was:
[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,2
[InternetShortcut]
URL=http://127.0.0.1/index.php
IDList=
However, it "works" as in: the change (speak: a different URL) is recognized. Putting it all together my following code for Delphi 7 on Windows 7 should also work for you - just call the function:
uses
ShlObj, ActiveX, ComObj;
const
SID_IUniformResourceLocatorA= '{FBF23B80-E3F0-101B-8488-00AA003E56F8}';
SID_IUniformResourceLocatorW= '{CABB0DA0-DA57-11CF-9974-0020AFD79762}';
SID_InternetShortcut= '{FBF23B40-E3F0-101B-8488-00AA003E56F8}';
type
PUrlInvokeCommandInfoA= ^TUrlInvokeCommandInfoA;
TUrlInvokeCommandInfoA= record
dwcbSize,
dwFlags: DWORD; // Bit field of IURL_INVOKECOMMAND_FLAGS
hwndParent: HWND; // Parent window. Valid only if IURL_INVOKECOMMAND_FL_ALLOW_UI is set.
pcszVerb: LPCSTR; // Verb to invoke. Ignored if IURL_INVOKECOMMAND_FL_USE_DEFAULT_VERB is set.
end;
PUrlInvokeCommandInfoW= ^TUrlInvokeCommandInfoW;
TUrlInvokeCommandInfoW= record
dwcbSize,
dwFlags: DWORD;
hwndParent: HWND;
pcszVerb: LPCWSTR;
end;
IUniformResourceLocatorA= interface( IUnknown )
[SID_IUniformResourceLocatorA]
function SetURL( pcszURL: LPCSTR; dwInFlags: DWORD ): HRESULT; stdcall;
function GetURL( ppszURL: LPSTR ): HRESULT; stdcall;
function InvokeCommand( purlici: PUrlInvokeCommandInfoA ): HRESULT; stdcall;
end;
IUniformResourceLocatorW= interface( IUnknown )
[SID_IUniformResourceLocatorW]
function SetURL( pcszURL: LPCWSTR; dwInFlags: DWORD ): HRESULT; stdcall;
function GetURL( ppszURL: LPWSTR ): HRESULT; stdcall;
function InvokeCommand(purlici: PUrlInvokeCommandInfoW ): HRESULT; stdcall;
end;
function SetURL( sFile, sUrl: Widestring ): Integer;
const
CLSID_InternetShortCut: TGUID= SID_InternetShortcut;
var
oUrl: IUniformResourceLocatorW;
oFile: IPersistFile;
hFile: THandle;
begin
// First, the existing file's content should be emptied
hFile:= CreateFileW( PWideChar(sFile), GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0 );
if hFile= INVALID_HANDLE_VALUE then begin
result:= 1; // File might not exist, sharing violation, etc.
exit;
end;
// Initial file pointer is at position 0
if not SetEndOfFile( hFile ) then begin
result:= 2; // Missing permissions, etc.
CloseHandle( hFile );
exit;
end;
// Gracefully end accessing the file
if not CloseHandle( hFile ) then begin
result:= 3; // File system crashed, etc.
exit;
end;
// Using COM to access properties
result:= 0;
try
oUrl:= CreateComObject( CLSID_InternetShortCut ) as IUniformResourceLocatorW;
except
result:= 4; // CLSID unsupported, COM not available, etc.
end;
if result<> 0 then exit;
// Opening the file again
oFile:= oUrl as IPersistFile;
if oFile.Load( PWideChar(sFile), STGM_READWRITE )<> S_OK then begin
result:= 5; // Sharing violations, access permissions, etc.
exit;
end;
// Set the property as per interface - only saving the file is not enough
if oUrl.SetURL( PWideChar(sUrl), 0 )<> S_OK then begin
result:= 6;
exit;
end;
// Storing the file's new content - setting only the property is not enough
if oFile.Save( PWideChar(sFile), TRUE )<> S_OK then begin
result:= 7;
exit;
end;
// Success!
result:= 0;
end;
As per my desktop firewall the executing process modifies the memory of explorer.exe upon IPersistFile.Save() - after that executing the URL file should reflect its new content, while any attempt before that should still act upon the old file's content.

Prompt user in Inno Setup for file to be used in Shortcut

In Inno Setup I try to create this shortcut:
"C:\Program Files (x86)\MapInfo\Professional\MapInfow.exe" "{app}\DPImap.MBX"
It works fine with static text, however location of MapInfow.exe can vary so I like to ask the user for it.
This is what I did so far, however the shortcut is not created as intended
; Default value for silent installion
#define MapInfoDefault AddBackslash(GetEnv("ProgramFiles(x86)")) + "MapInfo\Professional\MapInfow.exe"
[Tasks]
Name: desktopicon; Description: {cm:CreateDesktopIcon}; GroupDescription: {cm:AdditionalIcons}; Flags: unchecked
[Icons]
Name: {group}\DPImap; Filename: {code:MapInfoExecutable} {app}\DPImap.mbx
Name: {userdesktop}\DPImap; Filename: {code:MapInfoExecutable} {app}\DPImap.mbx; Tasks: desktopicon
[Code]
function MapInfoExecutable(Param: String): String;
var
FileName: string;
begin
FileName := '';
if GetOpenFileName('Locate your MapInfo Application', FileName, ExpandConstant('{pf32}'), 'Executable (*.exe)|*.exe', 'exe') then
Result := FileName
else
{ Return default #MapInfoDefault if user does not provide any file }
Result := ExpandConstant('{#MapInfoDefault}');
end;
How can I provide proper user dialog?
It should be:
[Icons]
Name: {group}\DPImap; Filename: "{code:MapInfoExecutable}"; \
Parameters: """{app}\DPImap.mbx"""
You should also cache the selected file name, otherwise you get the prompt at least twice, and probably even more times.
var
FileName: string;
function MapInfoExecutable(Param: String): String;
begin
if FileName = '' then
begin
if not GetOpenFileName(
'Locate your MapInfo Application', FileName, ExpandConstant('{pf32}'),
'Executable (*.exe)|*.exe', 'exe') then
begin
{ Return default #MapInfoDefault if user does not provide any file }
FileName := '{#MapInfoDefault}';
end;
end;
Result := FileName;
end;
Or actually even better, use a custom page, rather than a dialog, which pops up at uncontrollable moment.
See Inno Setup Prompt for external file location.
And even if you like the dialog, pop it on specific page/moment of your choice, cache the selected file name to a global variable and use the variable in the MapInfoExecutable.
Note that I've removed ExpandConstant from '{#MapInfoDefault}' - It's nonsense. See Evaluate preprocessor macro on run time in Inno Setup Pascal Script.

Wow64DisableWow64FsRedirection fileExits

a file is located in C:\program files (x86)\my app\myexe.exe
FileExists('C:\program files (x86)\my app\myexe.exe') returns true;
FileExists('C:\program files\my app\myexe.exe') returns false;
in both cases, if I use Wow64DisableWow64FsRedirection or not.
Why ? Thanks
File system redirection is only there for the %windir%\system32 directory. The description of the File System Redirector seems to make this obvious.
Note the comment in the page
Applications should use the SHGetSpecialFolderPath function to determine the %ProgramFiles% directory name.
Edit Turns out that the FOLDERID_ProgramFilesx64 does not work on 32bit applications running on 64bit windows. In this case, you can use the environment variable %ProgramW6432% instead. Note that this variable is only available on Windows 7 and later for 32bit applications.
The following delphi snippet allows accessing the variable:
function GetEnvironmentString(aString : string) : string;
var
dest : string;
retSize : integer;
begin
SetLength(dest, MAX_PATH);
retSize := ExpandEnvironmentStrings(pchar(aString), pchar(dest), MAX_PATH);
if retSize > 0 then
SetLength(dest, retSize - 1);
result := dest;
end;
Called as:
GetEnvironmentString('%ProgramW6432%');
IF you're on a 64bit version of windows, then a 32bit application cannot use FOLDERID_ProgramFilesX64 to explicitly get the 64bit location of Program Files, but can use the environment variable expansion instead. On a 32bit version of windows, this location is invalid, and will not get you a value. You need to check the bitness of the system before attempting to access this variable.
You can use the function IsWow64Process to determine this. The following snippet should allow you to check this:
function IsWow64: Boolean;
type
TIsWow64Process = function(Handle: Windows.THandle; var Res: Windows.BOOL): Windows.BOOL; stdcall;
var
IsWow64Result: Windows.BOOL;
IsWow64Process: TIsWow64Process;
begin
// Try to load required function from kernel32
IsWow64Process := Windows.GetProcAddress(Windows.GetModuleHandle('kernel32.dll'), 'IsWow64Process');
if Assigned(IsWow64Process) then
begin
// Function is implemented: call it
if not IsWow64Process(Windows.GetCurrentProcess, IsWow64Result) then
raise SysUtils.Exception.Create('IsWow64: bad process handle');
// Return result of function
Result := IsWow64Result;
end
else
// Function not implemented: can't be running on Wow64
Result := False;
end;
In summary: FOLDERID_ProgramFiles gives you the 32/64 bit variant when accessed from a 32/64 bit program, FOLDERID_ProgramFilesX64 gives you the 64bit version explicitly on a 64-bit application, and FOLDERID_ProgramFilesX86 gives you the 32bit variant explicitly. You can use the environment variable expansion to get the 64bit value on a 32bit application

Inno Setup image download

I'm working with Inno Setup Compiler (Pascal Scripting).
My form has an image object (TBitmapImage) and I'd like to supply a dynamic image obtained from the web URL. Is it possible to silently download an image (or other type of file) in Inno Setup scripting?
I would write a small Win32 program that downloads a file from the Internet, such as
program dwnld;
uses
SysUtils, Windows, WinInet;
const
PARAM_USER_AGENT = 1;
PARAM_URL = 2;
PARAM_FILE_NAME = 3;
function DownloadFile(const UserAgent, URL, FileName: string): boolean;
const
BUF_SIZE = 4096;
var
hInet, hURL: HINTERNET;
f: file;
buf: PByte;
amtc: cardinal;
amti: integer;
begin
result := false;
hInet := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
try
hURL := InternetOpenUrl(hInet, PChar(URL), nil, 0, 0, 0);
try
GetMem(buf, BUF_SIZE);
try
FileMode := fmOpenWrite;
AssignFile(f, FileName);
try
Rewrite(f, 1);
repeat
InternetReadFile(hURL, buf, BUF_SIZE, amtc);
BlockWrite(f, buf^, amtc, amti);
until amtc = 0;
result := true;
finally
CloseFile(f);
end;
finally
FreeMem(buf);
end;
finally
InternetCloseHandle(hURL);
end;
finally
InternetCloseHandle(hInet);
end;
end;
begin
ExitCode := 0;
if ParamCount < 3 then
begin
MessageBox(0,
PChar(Format('%s: This program requires three command-line arguments.',
[ExtractFileName(ParamStr(0))])),
PChar(ExtractFileName(ParamStr(0))),
MB_ICONERROR);
Exit;
end;
if FileExists(ParamStr(PARAM_FILE_NAME)) then
DeleteFile(PChar(ParamStr(PARAM_FILE_NAME)));
if DownloadFile(ParamStr(PARAM_USER_AGENT), ParamStr(PARAM_URL),
ParamStr(PARAM_FILE_NAME)) then
ExitCode := 1;
end.
This program takes three command-line arguments: the UserAgent to be sent to the web server (can be anything, such as "MyApp Setup Utility"), the URL of the file on the Internet, and the file name of the file that is being created. Don't forget to enclose the arguments inside quotation marks ("). The exit code of the program is 0 if the download failed, and 1 if the download succeeded.
Then, in your Inno Setup script, you can do
[Files]
Source: "dwnld.exe"; DestDir: "{app}"; Flags: dontcopy
[Code]
function InitializeSetup: boolean;
var
ResultCode: integer;
begin
ExtractTemporaryFile('dwnld.exe');
if Exec(ExpandConstant('{tmp}\dwnld.exe'),
ExpandConstant('"{AppName} Setup Utility" "http://privat.rejbrand.se/sample.bmp" "{tmp}\bg.bmp"'),
'', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then
if ResultCode = 1 then
(* Now you can do something with the file ExpandConstant('{tmp}\bg.bmp') *);
end;
Unfortunately, however, I know of no means by which you can change the WizardImageFile during runtime...
It's actually possible to download almost anything from the web using InnoTools Downloader.
Inno setup doesnt have any inbuilt functions for this, however, you can perform this action using batch files that do the job for you.
1) download a command line URL resource downloader like -
http://www.chami.com/free/url2file_wincon.html
Some tips on how to use it -
http://www.chami.com/tips/windows/062598W.html
2) Package it in your installer
3) create a batch file that calls url2file.exe and fetches your image into the app directory
4) Call this batch file in the initialize setup command of Inno Setup installer script.
5) Use that image wherever you want!
ps - If you are using the image in the setup, check if differed image loading is allowed or not.. i am not sure about that.
Let me know if you have any other questions

Windows: How to canonicalize a file to the special folder?

i want to to persist some filenames for the user (e.g. recent files).
Let's use six example files:
c:\Documents & Settings\Ian\My Documents\Budget.xls
c:\Documents & Settings\Ian\My Documents\My Pictures\Daughter's Winning Goal.jpg
c:\Documents & Settings\Ian\Application Data\uTorrent
c:\Documents & Settings\All Users\Application Data\Consonto\SpellcheckDictionary.dat
c:\Develop\readme.txt
c:\Program Files\Adobe\Reader\WhatsNew.txt
i'm now hard-coding path to special folders. If the user redirects their folders, roams to another computer, or upgrades their operating system, the paths will be broken:
i want to be a good developer, and convert these hard-coded absolute paths to relative paths from the appropriate special folders:
%CSIDL_Personal%\Budget.xls
%CSIDL_MyPictures%\Daughter's Winning Goal.jpg
%CSIDL_AppData%\uTorrent
%CSIDL_Common_AppData%\Consonto\SpellcheckDictionary.dat
c:\Develop\readme.txt
%CSIDL_Program_Files%\Adobe\Reader\WhatsNew.txt
The difficulty comes with the fact that there can be multiple representations for the same file, e.g.:
c:\Documents & Settings\Ian\My Documents\My Pictures\Daughter's Winning Goal.jpg
%CSIDL_Profile%\My Documents\My Pictures\Daughter's Winning Goal.jpg
%CSIDL_Personal%\My Pictures\Daughter's Winning Goal.jpg
%CSIDL_MyPictures%\Daughter's Winning Goal.jpg
Note also that in Windows XP My Pictures are stored in My Documents:
%CSIDL_Profile%\My Documents
%CSIDL_Profile%\My Documents\My Pictures
But on Vista/7 they are separate:
%CSIDL_Profile%\Documents
%CSIDL_Profile%\Pictures
Note: i realize the syntax
%CSIDL_xxx%\filename.ext is not valid; that
Windows will not expand those keywords
like they are environment strings. i'm only
using it as a way to ask this
question. Internally i would obviously
store the items some other way, perhaps as a CSIDL parent
and the tail of the path, e.g.:
CSIDL_Personal \Budget.xls
CSIDL_MyPictures \Daughter's Winning Goal.jpg
CSIDL_AppData \uTorrent
CSIDL_Common_AppData \Consonto\SpellcheckDictionary.dat
-1 c:\Develop\readme.txt (-1, since 0 is a valid csidl)
CSIDL_Program_Files \Adobe\Reader\WhatsNew.txt
The question becomes, how to use, as much as possible, paths relative to canonical special folders?
I'm thinking:
void CanonicalizeSpecialPath(String path, ref CSLID cslid, ref String relativePath)
{
return "todo";
}
See also
MSDN: CSIDL Enumeration
New Old Thing: Beware of roaming user profiles
New Old Thing: Beware of redirected folders, too
MSDN: PathCanonicalize Function
I suppose you could find out how the CSIDL map to paths (using something like SHGetKnownFolderPath), build a reverse dictionary of them, then check whether the beginning of the path you want to store matches any of the keys in the dictionary and then remove the beginning and store the CSIDL that matched.
Not overtly elegant, but it should get the work done.
function CanonicalizeSpecialPath(const path: string): string;
var
s: string;
BestPrefix: string;
BestCSIDL: Integer;
i: Integer;
begin
BestPrefix := ''; //Start with no csidl being the one
BestCSIDL := 0;
//Iterate over the csidls i know about today for Windows XP.
for i := Low(csidls) to High(csidls) do
begin
//Get the path of this csidl. If the OS doesn't understand it, it returns blank
s := GetSpecialFolderPath(0, i, False);
if s = '' then
Continue;
//Don't do a string search unless this candidate is larger than what we have
if (BestPrefix='') or (Length(s) > Length(BestPrefix)) then
begin
//The special path must be at the start of our string
if Pos(s, Path) = 1 then //1=start
begin
//This is the best csidl we have so far
BestPrefix := s;
BestCSIDL := i;
end;
end;
end;
//If we found nothing useful, then return the original string
if BestPrefix = '' then
begin
Result := Path;
Exit;
end;
{
Return the canonicalized path as pseudo-environment string, e.g.:
%CSIDL_PERSONAL%\4th quarter.xls
}
Result := '%'+CsidlToStr(BestCSIDL)+'%'+Copy(Path, Length(BestPrefix)+1, MaxInt);
end;
And then there's a function that "expands" the special environment keywords:
function ExpandSpecialPath(const path: string): string;
begin
...
end;
which expands:
%CSIDL_PERSONAL%\4th quarter.xls
into
\\RoamingProfileServ\Users\ian\My Documents\4th quarter.xls
It does it by looking for %xx% at the start of the string, and expanding it.

Resources