Inno Setup run/execute code as another user - inno-setup

Much as it is possible to use ExecAsOriginalUser to run something using the starting/logged in user's credentials, is there a command or way to run a particular piece of code as a specific user?
To explain further, I am writing the computer name into a log on a file share at the end of installation. This works perfectly as long as not installing using a local Administrator account which does not have a matching local Administrator account on the server. Therefore, what I need to do is have the particular bit of code that writes to the log execute as a specific user account, which I have the credentials for, that I know exists on the server.
The code I am using is as follows:
{ Auto audit the computer name }
procedure AutoAudit();
var
strComputerName: TArrayOfString;
strAutoAuditFile: String;
begin
strAutoAuditFile := '\\' + ClientConnectionPage.Values[0] + '\Audit\Client Audit.txt';
SetArrayLength(strComputerName, 1);
strComputerName[0] :=
ExpandConstant('{computername} ') + GetDateTimeString('dd/mm/yyyy hh:mm', '/', ':');
SaveStringsToFile(strAutoAuditFile, strComputerName, True);
end;
So, what I need is a way of executing the SaveStringsToFile function as another user.
I have thought of a rather messy way to do this, which would involve testing for an available drive letter, mapping a drive using the account credentials, writing to the file and then disconnecting the drive. However, I am hoping there is a more elegant way to do this?

You have to use the LogonUser WinAPI function to run a piece of code as a different account.
It's likely possible to run this from an Inno Setup code, but I haven't found an example.
Alternatively, you can develop a separate tiny application that impersonates the other user and embed the application into your installer.
In C#/VB.NET, you can use the Impersonator class (which uses the LogonUser internally):
using System;
using System.IO;
class Program
{
static void Main(string[] args)
{
using (new Impersonator("username", "domain", "password"))
{
string strAutoAuditFile = #"\\" + args[0] + #"\Audit\Client Audit.txt";
string strComputerName =
Environment.MachineName + " " + DateTime.Now.ToString("dd/mm/yyyy hh:mm");
File.WriteAllText(strAutoAuditFile, strComputerName);
}
}
}
Run the application from Inno Setup like:
[Files]
Source: "getcompname.exe"; Flags: dontcopy
[Code]
...
var
ResultCode: Integer;
begin
...
ExtractTemporaryFile('getcompname.exe');
ExecAsOriginalUser(
ExpandConstant('{tmp}') + '\getcompname.exe ',
ClientConnectionPage.Values[0],
'', SW_HIDE, ewWaitUntilTerminated, ResultCode);
...
end;
If you are not experienced with a console application development, note that the "tiny" application can be even a batch file with use of the runas command (which does not allow specifying password automatically) or the PsExec (which does).

Related

Inno Setup close already open application without user interaction [duplicate]

Referring to the question Basic or Advanced installation mode choice to skip or use advanced options pages, I need to skip the Preparing to Install wizard page now.
In my case this page is displayed because one or more programs are using files that need to be replaced by the installer; so the installer asks to the user if they want the setup to automatically close the applications and restart at the end of the setup.
I need that this page is hide from the setup process in Basic mode, and if some files are used, that the setup automatically closes the applications using them without asking anything to the user.
I've tried editing ShouldSkipPage as:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
{ If "Basic" mode is selected, skip Directory and Components pages }
Result :=
ModePage.Values[0] and
((PageID = wpSelectDir) or (PageID = wpSelectComponents) or (PageID = wpReady) or (PageID = wpPreparing));
end;
adding (PageID = wpPreparing) but the page still displayed in Basic mode.
Is there a way to implement this using Inno Setup?
ShouldSkipPage event is not even called for wpPreparing. That page is not to be skipped.
If you still want to skip it, you have to use hacks like these:
How to skip all the wizard pages and go directly to the installation process?
Inno Setup - How to close finished installer after a certain time?
With the first approach, your code would look like:
[Code]
const
BN_CLICKED = 0;
WM_COMMAND = $0111;
CN_BASE = $BC00;
CN_COMMAND = CN_BASE + WM_COMMAND;
procedure CurPageChanged(CurPageID: Integer);
var
Param: Longint;
begin
{ If Basic mode is selected, skip Preparing page }
if (CurPageID = wpPreparing) and ModePage.Values[0] then
begin
Param := 0 or BN_CLICKED shl 16;
PostMessage(WizardForm.NextButton.Handle, CN_COMMAND, Param, 0);
end;
end;
Just don't do that. Ever. It is absolutely unacceptable for you to close an arbitrary list of applications without prompting the user. It's equally impolite to barrel ahead and then require a reboot at the end of the install. (It's unforgivable to then trigger the reboot without asking.)
What you can do is to put some code in the PrepareToInstall [Code] function which will automatically close your application. This executes before the user is prompted to close apps, so if it was only your apps involved then they will not be prompted.

How to create Start menu items for all users when running installer with PrivilegesRequired=lowest as Administrator

Inno Setup documentation says the following:
{group}
The path to the Start Menu folder, as selected by the user on Setup's Select Start Menu Folder wizard page. This folder is created under the All Users profile unless the user installing the application does not have administrative privileges, in which case it is created in the user's profile.*
When I use PrivilegesRequired=admin (i.e. the default), Start menu items are created for all users in C:\ProgramData\Microsoft\Windows\Start Menu\Programs.
When I use PrivilegesRequired=lowest, but run setup with right-click run-as-administrator, Start menu items are created for just the admin user in C:\Users\admin-user\AppData\Roaming\Microsoft\Windows\Start Menu\Programs. This happens even if the current user is an admin.
How can I make start menu items be for all users when using right-click elevation? In this situation, Setup installs program files for all users in C:\Program Files\. So I would like start menu items also to be for all users.
Use a scripted constant to dynamically change Start menu root path, based on an elevation status of the installer.
You have to use WinAPI (e.g. SHGetFolderPath) to retrieve the path to common Start menu folder, as {commonprograms} actually return {userprograms}, if PrivilegesRequired=lowest, even when the installer is actually running elevated.
[Icons]
Name: "{code:GetMenuRootPath}\{groupname}\My Program"; Filename: "{app}\MyProg.exe"
[Code]
const
CSIDL_COMMON_PROGRAMS = $0017;
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 GetMenuRootPath(Param: string): string;
var
R, I: Integer;
begin
if IsAdminLoggedOn then
begin
SetLength(Result, MAX_PATH);
R := SHGetFolderPath(0, CSIDL_COMMON_PROGRAMS, 0, SHGFP_TYPE_CURRENT, Result);
if R <> S_OK then
begin
Log('Failed to resolve path to common Start menu folder');
end
else
begin
SetLength(Result, Pos(#0, Result) - 1);
Log(Format('Resolved path to common Start menu folder: %s', [Result]));
end;
end
else
begin
Result := ExpandConstant('{userprograms}');
Log(Format('Using user''s Start menu folder: %s', [Result]))
end;
end;
The code is for Unicode version of Inno Setup (the only version as of Inno Setup 6).
Though note that using groups in Start menu is against Windows guidelines for Windows 8 and newer.
You can use the (deprecated and now undocumented) value PrivilegesRequired=none to make Inno adapt to whether it is being run with or without admin privileges, including redirecting the Start Menu entries accordingly.
However the reason that this setting is deprecated is because the entire concept is a little silly. Most applications should be designed to require admin privileges to install (since typically people who are non-admins are not supposed to install software).
If you do end up with an application installed both as an admin and as a regular user then you will have cases where certain users will see two copies of the application installed and will not know which one to use. Furthermore the admin might upgrade the one they installed thinking that all users will see it, but meanwhile some users are still using the older version.
I strongly encourage you to abandon the idea of letting unprivileged users install your application and just stick with PrivilegesRequired=admin, or if you really want to allow that, then PrivilegesRequired=lowest.
If you do use PrivilegesRequired=lowest, then also use {userpf} in your DefaultDirName so that it doesn't install to Program Files if someone erroneously runs it as an admin.

Inno Setup - prevent executing the installer multiple times simultaneously

I've got a bit of a pickle with Inno Setup: on a user machine, my installer was running slowly (something I've yet to diagnose, might be a problem specific with that computer, I still don't know). This lead to said user to run the installer again, while the first instance was still executing - and to my surprise, they both seemed to be running for a time, before crashing and burning...
I searched around but have not found any way to disable this behavior - most of my queries wound up on Inno Setup mutex feature, which is not really what I'm looking for. Anyone got tips on how to make sure there is only one instance / process of the installer executing? Thank you!
Since Inno Setup 5.5.6 you can use the SetupMutex directive:
[Setup]
AppId=MyProgram
SetupMutex=SetupMutex{#SetupSetting("AppId")}
If you want to change a text of the message, that displays when another installer is running already, use:
[Messages]
SetupAppRunningError=Setup has detected that %1 is currently running.%n%nPlease close all instances of it now, then click OK to continue, or Cancel to exit.
Before this version, there was no built-in mechanism available. But you could write your own pretty simply. Principle is that you create a unique mutex when the setup starts. But, as first you check if there is no such mutex already created. If so, you exit the setup, if not, you create the mutex:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
// this needs to be system-wide unique name of the mutex (up to MAX_PATH long),
// there is a discussion on this topic http://stackoverflow.com/q/464253/960757
// you can expand here e.g. the AppId directive and add it some 'salt'
MySetupMutex = 'My Program Setup 2336BF63-DF20-445F-AAE6-70FD7E2CE1CF';
function InitializeSetup: Boolean;
begin
// allow the setup to run only if there is no thread owning our mutex (in other
// words it means, there's no other instance of this process running), so allow
// the setup if there is no such mutex (there is no other instance)
Result := not CheckForMutexes(MySetupMutex);
// if this is the only instance of the setup, create our mutex
if Result then
CreateMutex(MySetupMutex)
// otherwise tell the user the setup will exit
else
MsgBox('Another instance is running. Setup will exit.', mbError, MB_OK);
end;
If your installer was called setup.exe for example, then you could use the following code to check if setup.exe is running and terminate the install.
[Code]
function IsAppRunning(const FileName : string): Boolean;
var
FSWbemLocator: Variant;
FWMIService : Variant;
FWbemObjectSet: Variant;
begin
Result := false;
FSWbemLocator := CreateOleObject('WBEMScripting.SWBEMLocator');
FWMIService := FSWbemLocator.ConnectServer('', 'root\CIMV2', '', '');
FWbemObjectSet := FWMIService.ExecQuery(Format('SELECT Name FROM Win32_Process Where Name="%s"',[FileName]));
Result := (FWbemObjectSet.Count > 0);
FWbemObjectSet := Unassigned;
FWMIService := Unassigned;
FSWbemLocator := Unassigned;
end;
function InitializeSetup: boolean;
begin
result := not IsAppRunning('setup.exe');
if not result then
MsgBox('setup.exe is already running', mbError, MB_OK);
end;

Use default authentication and separate cloaking/impersonation in DCOM call

I'm trying achieve two things with DCOM (Out of process)
Set the process wide authentication using CoInitializeSecurity and its parameter pAuthList.
Using cloaking to change the caller's identity in special situations (COM calls)
My thoughts:
AFAIK the auth info structure contains the default authentication information (like username and password for RPC_C_AUTHN_WINNT) for all new COM calls. So instead of the process token the information in the auth structure should be used by COM.
However, all COM calls/connections are always using the process' identity instead of the applied default one.
Usually, one can use CoSetProxyBlanket to change the auth info for a proxy. This works for me. My question here is whether it must or must not work if I impersonate the token myself and call the COM function. I've read in various MSDN articles that applying EOAC_DYNAMIC_CLOAKING to CoInitializeSecurity should make it working. However, my manually "impersonated COM calls always shows the process identity on the server side.
The client looks like this (Delphi)
var
authList : SOLE_AUTHENTICATION_LIST;
authidentity : SEC_WINNT_AUTH_IDENTITY_W;
authInfo : array[0..1] of SOLE_AUTHENTICATION_INFO;
pcAuthSvc : DWORD;
asAuthSvc : array[0..0] of SOLE_AUTHENTICATION_SERVICE;
Token : TJwSecurityToken;
begin
ZeroMemory( #authidentity, sizeof(authidentity) );
authidentity.User := 'Testbenutzer';
authidentity.UserLength := Length('Testbenutzer');
authidentity.Domain := '';
authidentity.DomainLength := 0;
authidentity.Password := 'test';
authidentity.PasswordLength := 4;
authidentity.Flags := SEC_WINNT_AUTH_IDENTITY_UNICODE;
ZeroMemory( #authInfo, sizeof( authInfo ) );
// NTLM Settings
authInfo[0].dwAuthnSvc := RPC_C_AUTHN_WINNT;
authInfo[0].dwAuthzSvc := RPC_C_AUTHZ_NONE;
authInfo[0].pAuthInfo := #authidentity;
authList.cAuthInfo := 1;
authList.aAuthInfo := #authInfo;
OleCheck(CoInitializeSecurity(
NULL, // Security descriptor
-1, // Count of entries in asAuthSvc
NULL, // asAuthSvc array
NULL, // Reserved for future use
RPC_C_AUTHN_LEVEL_CONNECT, // Authentication level
RPC_C_IMP_LEVEL_IMPERSONATE, // Impersonation level
#authList, // Authentication Information
DWORd(EOAC_DYNAMIC_CLOAKING), // Additional capabilities
NULL // Reserved
));
//create COM object
int := CoSecurityTestObj.Create;
int.TestCall;
The server also has set the flag EOAC_DYNAMIC_CLOAKING. It uses CoImpersonateClient to get the thread token and the username. It also uses CoQueryClientBlanket to get the authInfo (as SEC_WINNT_AUTH_IDENTITY_W structure). However both calls always return the process identity of the client.
Also impersonating manually doesn't work (2.):
Token := TJwSecurityToken.CreateLogonUser(authidentity.User, '', authidentity.Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT);
Token.ImpersonateLoggedOnUser;
int := CoSecurityTestObj.Create;
int.TestCall;
Questions again:
Am I wrong or why is the default auth info structure (WinNT with username and password) not used as default authentication in each COM connection/call ?
Am I wrong or why doesn't manual impersonation work? Be aware that I tested number 2. separately so number 1. cannot interfere.
This is basic work for the JEDI Windows Security Code Library which I extend to support COM security. So your help will go GPL/MPL.
References:
Cloaking:
http://msdn.microsoft.com/en-us/library/ms683778%28VS.85%29.aspx
http://msdn.microsoft.com/en-us/library/cc246058%28PROT.10%29.aspx
http://alt.pluralsight.com/wiki/default.aspx/Keith.GuideBook/WhatIsCoInitializeSecurity.html
CoInitializeSecurity and pAuthInfo
http://www.codeguru.cn/vc&mfc/apracticalguideusingvisualcandatl/93.htm
Getting security blanket (server side)
http://www.codeguru.cn/vc&mfc/apracticalguideusingvisualcandatl/92.htm
Have you tried calling CoInitializeSecurity() with RPC_C_AUTHN_LEVEL_CALL instead of RPC_C_AUTHN_LEVEL_CONNECT?
Usually when I create DCOM clients I create COSERVERINFO and pass to CoCreateInstanceEx() with security credentials, remembering to call CoSetProxyBlanket() on all interfaces.

Delphi: Open a file from another computer

my names's Carlos Im from Brazil.
Im trying to open a file like this:
image1.picture.loadfromfile('\\ntmemo01\c$\ozzy2.bmp');
but it doesnt work. Im receving the exception
class EFOpenError with message "Cannot open file '\ntmemo01\c$\ozzy2.bmp' Access denied."
Thanks,
Carlos
You have to use double backslashes.
image1.picture.loadfromfile('\\ntmemo01\c$\ozzy2.bmp');
If you still get the exception, then the file is inaccessible from your application.
The first thing you should do is making sure, that you can access the file using the Windows Explorer.
Just type it into the Run dialog of the start menu (WinKey+R) and see what happens. If it doesn't work, make it work there first and then go back to your program.
Is the C: drive on ntmemo01 shared? If it's not shared, you can't access it. If it's shared but requires a user name and password to access, you'll have to access it differently. You can map a drive letter to it, providing a user name and password in the process:
const
RemoteName = '\\ntmemo01\C$';
UserName = 'yourusername';
Password = 'yourpassword';
function MapNetworkDrive: Boolean;
var
NetRes: TNetResource;
Res: DWord;
begin
Result := True;
FillChar(NetRes, SizeOf(TNetResource), 0);
NetRes.dwType := RESOURCETYPE_DISK;
NetRes.lpRemoteName := PChar(RemoteName);
NetRes.lpLocalName := 'H:'; // Whatever drive letter you want
Res := WNetAddConnection2(NetRes, PChar(Password), PChar(UserName), 0);
Result := (Res = NO_ERROR);
end;
To unmap afterwards:
function UnMapNetworkDrive: Boolean;
var
Res: DWord;
begin
Res := WNetCancelConnection2(PChar('H:'), 0, True); // same drive letter as above
Result := (Res + NO_ERROR);
end;
As mentioned by DR, the filename requires double backslashes for a UNC path
The access denied message suggests the you do not have permission to access the C$ share on the ntmemo01 computer.
C$ a is hidden administrative share are you sure the current user account has the correct permissions? You make have to the map a drive first, as suggested by Ken White
Administrive shares are disabled by default in Windows Vista and Windows 7, unless you join a domain. You can be enable them manually as follows.
Click on the start button and in the search box type ‘regedit’ and hit enter.
Browse to HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System.
Add a new DWORD called LocalAccountTokenFilterPolicy and give it a value of 1.
Reboot and yer done!
Source: http://www.paulspoerry.com/2007/05/09/how-to-access-administrative-shares-on-vista-c/
when you copy this exact same string in windows explorer, is the file opened?
Otherwise it might be a rights problem, as suggested by the error.

Resources