Inno Setup Create individual shortcuts on all desktops of all users - permissions

I'm creating a shortcut on users Desktop with Inno Setup:
Name: "{commondesktop}\Setup"; Filename: "{app}\Setup.exe"; WorkingDir: "{pf}\Program"; IconFilename: "{app}\Setup.ico"
But users, with no admin rights, cannot delete this Shortcut, how to grant permissions to regular users, to delete this icon? Icon should be created on every user's desktop, but user should have permission to delete it.

The {commondesktop} shortcut is shared on a common desktop. So there's only one copy of the shortcut.
If you allow the users to delete, when one user deletes the icon, it's deleted for every other user. That's why regular users are not allowed to modify/delete shared shortcuts.
While you can grant a delete permission to all users to that shortcut, this is not what you should do.
If each machine is used by one user only, install the icon to userdesktop, not commondestop. But that works only if that user (not Administrator) actually run the installer. For a general discussion about this problem, see Installing application for currently logged in user from Inno Setup installer running as Administrator.
There's no easy way to install the icons to all desktops. You have to use Pascal Scripting and iterate all profiles.
Easy way is to iterate subfolders of C:\Users, creating a shortcut in Desktop subfolder of each user's subfolder:
procedure CurStepChanged(CurStep: TSetupStep);
var
UsersPath: string;
CommonDesktopShortPath: string;
DesktopPath: string;
ShortcutPath: string;
FindRec: TFindRec;
ShortcutsCount: Integer;
begin
{ Once the files are installed }
if CurStep = ssPostInstall then
begin
Log('Creating shortcuts');
{ Assuming the common users root (typically C:\Users) is two level up }
{ from the current user desktop folder }
UsersPath :=
AddBackslash(ExtractFilePath(RemoveBackslash(ExtractFilePath(
RemoveBackslash(ExpandConstant('{userdesktop}'))))));
Log(Format('Users root [%s]', [UsersPath]));
CommonDesktopShortPath := GetShortName(ExpandConstant('{commondesktop}'));
Log(Format('Common desktop [%s]', [CommonDesktopShortPath]));
ShortcutsCount := 0;
{ Iterate all users }
if FindFirst(UsersPath + '*', FindRec) then
begin
try
repeat
{ Just directories, not interested in files }
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then
begin
{ Check if there is a Desktop subfolder }
DesktopPath := UsersPath + FindRec.Name + '\Desktop';
if DirExists(DesktopPath) then
begin
if CompareText(
CommonDesktopShortPath, GetShortName(DesktopPath)) = 0 then
begin
Log(Format('Skipping common desktop [%s]', [DesktopPath]));
end
else
begin
ShortcutPath := DesktopPath + '\My Program.lnk';
Log(Format(
'Found desktop folder for user [%s], creating shortcut [%s]', [
FindRec.Name, ShortcutPath]));
try
ShortcutPath := CreateShellLink(
ShortcutPath, 'My Program', ExpandConstant('{app}\MyProg.exe'), '',
ExpandConstant('{app}'), '', 0, SW_SHOWNORMAL);
Log(Format('Shortcut [%s] created', [ShortcutPath]));
Inc(ShortcutsCount);
except
Log(Format('Failed to create shortcut: %s', [GetExceptionMessage]));
end;
end;
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
Log(Format('%d shortcuts created', [ShortcutsCount]));
end
else
begin
Log(Format('Error listing [%s]', [UsersPath]));
end;
end;
end;
The code will work only, if the desktops are local and in common locations.
If you need a more robust solution, you can iterate profiles listed in
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
Or use a WMI query like:
SELECT * FROM Win32_UserAccount WHERE localAccount = true and disabled = false
See Query list of Windows accounts in Inno Setup.

Related

Is there a FILE_READ equivalent in oracle?

I was trying to load data in to db on application startup.
With H2 I was using the below query and it worked perfectly. With Oracle it doesn't work. Can someone point me the right direction? I went through oracle documentation but didn't manage to find an equivalent.
INSERT INTO TEMPLATES(ID,NAME,BODY) VALUES('2b04469f31c445ca82c354322845b52b', 'Records', FILE_READ('/opt/bin/Records.txt'));
Oracle SQL has no equivalent of a file_read() function. However, it is possible to write your own.
However, before we start you need to known that Oracle is much more locked down when it comes to database interoperability with the OS. Many things are not enabled by default, and consequently you may require assistance from a friendly DBA to get this working.
For instance, we cannot use OS filepaths directly (at least in more recent versions) so we need to create a DIRECTORY object. Normally the privilege to do this is restricted to DBAs.
create directory opt_bin as '/opt/bin';
grant read on directory opt_bin to <<your_user>>;
Note that /opt/bin must be a directory to which the database has access. On a *nix environment that means the oracle OS user has at least read on the directory.
With that infrastructure in place we can create a function which loads an OS file into a blob. It uses the directory and file name to instantiate a BFILE then applies DBMS_LOB capability to load that BFILE into a BLOB.
create or replace file_to_blob
(p_dir in varchar2, p_file in varchar2)
return blob
is
bf bfile;
tmp_blob blob := empty_blob();
l_dest_offset pls_integer := 1;
l_src_offset pls_integer := 1;
begin
bf := bfilename(p_dir, b_file);
dbms_lob.createtemporary(tmp_blob, true);
dbms_open(bf, dbms_lob.file_readonly);
dbms_lob.loadblobfromfile(tmp_blob, bf, dbms_lob.lobmaxsize, l_dest_offset, l_src_offset);
dbms_lob.close(bf);
return tmp_blob;
end;
/
You can use this function in your insert statement like this:
INSERT INTO TEMPLATES(ID,NAME,BODY)
VALUES
('2b04469f31c445ca82c354322845b52b', 'Records', file_to_blob('opt_bin', 'Records.txt'));

DWScript Write/Read a simple text file

I would like to write/read a simple text file using dwscript.
My code is here below... but I am non able to get it run, please someone might help...:
(I am using the Simple.exe in the Demos folder of DWS installation)
// uses Classes;
{$INCLUDE_ONCE 'c:/.../System.Classes.pas'}
var
s: TFileStream;
o: string; // out
i: integer;
f: word; // flag
f := fmOpenReadWrite;
if not FileExists('C:\Temp\Junkfile.txt') then
f := f or fmCreate;
s := TFileStream.Create('C:\Temp\Junkfile.txt', f);
try
s.Position := s.Size; // will be 0 if file created, end of text if not
for i := 1 to 10 do begin
o := Format('This is test line %d'#13#10, [i]);
s.Write(o[1], Length(o) * SizeOf(Char));
end;
finally
s.Free;
end;
By default the script engine keeps everything sand-boxed and nothing that gives access outside the sandbox is exposed. So if you want to give access to arbitrary files to script you need to expose functions & classes to achieve it (through TdwsUnit f.i.).
Also it won't compile the Delphi classes unit, DWScript is not meant to be an alternative to the Delphi compiler, but to offer scripting support, ie. allow end users to run code in a way over which you have full control over what they can do, and that can't crash or corrupt the host application (that last point being the key differentiation with the other notable Pascal scripting engines).
You can use dwsFileFunctions to get basic file I/O support, in which case an equivalent to the file creation portion of your code would be something like
var f := FileCreate('C:\Temp\Junkfile.txt');
for var i := 1 to 10 do
FileWrite(f, Format('This is test line %d'#13#10, [i]));
FileClose(f);

Copy Files from Previous Installation

To fit Win 8 requirements, I need to copy a data file (data.mpd) from the install directory of the previous installation (typically c:\ProgramFiles but users could have changed to something else) to a new directory c:\User\....
How can I:
Get the path of the previous install
check if the file data.mpd exists
Copy that file to the C:\Users...
You might use the WizardForm.PrevAppDir property, which holds the folder path, where the installer with a certain AppId had previously installed the application (empty if it was not yet installed). Note that this property is filled after the wizard form is initialized, so read it after the InitializeWizard event.
For your task, I would do this operation at the pre-installation step, so for the CurStepChanged event method, I would write something like:
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
DataFilePath: string;
begin
// check if the current step is pre-installation step and if the
// application had been previously installed; if so, then...
if (CurStep = ssInstall) and (WizardForm.PrevAppDir <> '') then
begin
// build and store the path to the Data.mpd file from the prev.
// installation path
DataFilePath := AddBackslash(WizardForm.PrevAppDir) + 'Data.mpd';
// check, if that Data.mpd file exists; if so, then...
if FileExists(DataFilePath) then
// copy it to the target directory; if it fails, show error message
if not FileCopy(DataFilePath, <your new directory here>, False) then
MsgBox('Copying of the Data.mpd failed!', mbError, MB_OK);
end;
end;
Use DisableDirPage=auto. This will prevent people from changing the install path on an upgrade.
Then have your application (not the installer) detect this file in its own folder and copy it to the per-user folder. This will give you the most robust behaviour in case multiple users run your application (which is the whole point of having per-user data).

How to bypass Internet Explorer Enhanced Security when using embedded WebBrowser control?

i have a native Windows application that embeds the WebBrowser, i.e.
CLSID_WebBrowser
8856F961-340A-11D0-A96B-00C04FD705A2
Shell.Explorer.2
Unfortunately, when running on Windows Servers, the Internet Explorer Enhanced Security mode interferes with the WebBrowser control, causing it to not render at all:
In this case, the UI of the software is driven as a WebBrowser control - making the software unusable.
i could disable Internet Explorer Enhanced Security mode, but that is not practical.
How can i instruct Internet Explorer browser to allow an embedded browser to render without the security dialog?
Note: i would have suggested adding about:security_Application.exe to the Trusted Zones list"
Sadly, that will require DRP/FRP validation, an ISO security assessment, and the security group will have to be called in to make the change. In addition, an RFC will need to be created so KPMG won't have hissy-fit next audit. i was hoping for the "good" solution.
See also
Customizing (disabling) security settings for IE control
Custom IInternetSecurityManager not being called with dialogs
You can specify a different URL. For example you can extract the content to a temp file and navigate to it. This will not put your content in the trusted zone, but it is better than the internet zone you get for the about protocol.
If you do not want to save the content, you can first navigate to about:blank, then in DocumentComplete, QI the document for IPersistMoniker, and call Load with a TInterfacedObject that basically simulates a url moniker.
The IMoniker.GetDisplayName implementation needs to return the URL. The url needs to be in a trusted zone.
IMoniker.BindToStorage implementation needs to send back a reference to a TMemoryStream when IStream is asked.
There's a third way, write a process-wide security manager that puts your url in a trusted zone.
The solution is to implement your own Internet Security Manager service creating an object that implements IInternetSecurityManager (see MSDN: Implementing a Custom Security Manager). There are five security zones:
Local: URLZONE_LOCAL_MACHINE (0)
Intranet: URLZONE_INTRANET (1)
Trusted: URLZONE_TRUSTED (2)
Internet: URLZONE_INTERNET (3)
Restricted: URLZONE_UNTRUSTED (4)
The only method you really need to worry about is MapUrlToZone:
TEmbeddedSecurityManager = class(TInterfacedObject, IInternetSecurityManager)
public
//...
function MapUrlToZone(pwszUrl: LPCWSTR; out dwZone: DWORD; dwFlags: DWORD): HResult; virtual; stdcall;
//...
end;
This method checks if the Url starts with about:security
about:security_Contoso.exe
and if so, returns that the zone should be Local:
function TEmbeddedSecurityManager.MapUrlToZone(pwszUrl: LPCWSTR; out dwZone: DWORD; dwFlags: DWORD): HResult;
var
url: UnicodeString;
begin
Result := INET_E_DEFAULT_ACTION;
{
https://msdn.microsoft.com/en-us/library/ms537133(v=vs.85).aspx
}
url := pwszUrl;
{
When IE Enchanced Security is enabled, the url goes from
about:blank_xxxx
to
about:security_xxxx
In that case we will put the page in the "Local" zone
}
if url.StartsWith('about:security') then
begin
dwZone := URLZONE_LOCAL_MACHINE; //Local
Result := S_OK;
end;
end;
Every other method must return INET_E_DEFAULT_ACTION (i.e. not S_OK nor E_NOTIMPL), e.g.:
function TEmbeddedSecurityManager.SetSecuritySite(Site: IInternetSecurityMgrSite): HResult;
begin
Result := INET_E_DEFAULT_ACTION;
end;
You give the embedded WebBrowser this service when it calls IServiceProvider.QueryService. In the case of Delphi's TEmbeddedWB control, it is exposed in the OnQueryService event:
function TForm1.EmbeddedWBQueryService(const rsid, iid: TGUID; out Obj: IInterface): HRESULT;
var
sam: IInternetSecurityManager;
begin
Result := E_NOINTERFACE;
//rsid ==> Service Identifier
//iid ==> Interface identifier
if IsEqualGUID(rsid, IInternetSecurityManager) and IsEqualGUID(iid, IInternetSecurityManager) then
begin
sam := TEmbeddedSecurityManager.Create;
Obj := sam;
Result := S_OK;
end;
end;
Maybe you could consider to load a different embedded browser. There is:
WebKit: http://www.webkit.org/
SWT (eclipse)

Running an application at the end of the uninstall in InnoSetup, handling the user cancelling

During un-installation, user will be asked "Are you sure to uninstall ...".
Then the user will click either "Yes" or "No".
Is it possible to catch this on the script?
Because I need to execute an application at the end of uninstallation process.
If I execute the application during "InitializeUninstall()", that is not correct because the user could cancel the uninstallation later (the above dialog is displayed AFTER this function).
Same with "DeInitializeUninstall()", this function is still executed even the user cancel the uninstallation.
Basically, I need to execute the application when the user is really un-installing (agreed to uninstall). Because I need to catch the ExitCode of this application to set the UninstallNeedRestart() function.
Thanks.
What you can do is add a global variable in your [Code] section
[Code]
var
ApplicationWasUninstalled: Boolean;
After that, in the InitializeUninstallProgressForm procedure you can set the global variable to 1 (Note: this function gets executed only if the user clicks Yes when they are prompted if they want to uninstall your application
procedure InitializeUninstallProgressForm();
begin
ApplicationWasUninstalled := true;
end;
Moving on, you will check the value of ApplicationWasUninstalled in DeinitializeUninstall function
procedure DeinitializeUninstall();
begin
if ApplicationWasUninstalled Then Begin
//your code here
end;
end;
Another way to achieve this is using the [Run] and / or [UninstallRun] sections to run an executable after install / before uninstall.
You can run a executable to do whatever clean up you need.
Compile a helper exe to do what you want, or add the function to your main executable when supplied a command line parameter.
[Run]
Filename: "{app}\CleanUp.exe"; WorkingDir: "{app}"
[UninstallRun]
Filename: "{app}\CleanUp.exe"; Parameters: "/uninstall"; WorkingDir: "{app}"; RunOnceId: "CleanUpApp"
More information in the inno setup documentation: http://www.jrsoftware.org/ishelp/index.php?topic=runsection
You need to add the helper exe to the installation too:
[Files]
...
Source: "C:\myprog\CleanUp.exe"; DestDir: "{app}"; Flags: ignoreversion
You can do this in the CurUninstallStepChanged() event function when it is called with the usPostUninstall parameter.
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
begin
// Do your uninstall time code here
Exec(ExpandConstant('{app}\CleanUp.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
end;
end;