InstallShield Run As local account if not ran by admin - installshield

I have a InstallShield InstallScript project that needs to be ran with administrative rights. In a nutshell from the InstallShield I need to:
Detect if the installer currently has administrative privileges.
If setup.exe is not being ran with admin rights spawn a new instance of the setup.exe using a local admin account/password then close the old (non-privledged) setup.exe.
So far I know I can do something like this to find if I have admin rights:
//---------------------------------------------------------------------------
// Run As Utilities Library
//---------------------------------------------------------------------------
// Include Ifx.h for built-in InstallScript function prototypes.
#include "Ifx.h"
//---------------------------------------------------------------------------
export prototype UserRightsCheck();
function UserRightsCheck()
begin
MessageBox(INSTALLPROPERTY_INSTALLLOCATION, INFORMATION);
if(USER_ADMINISTRATOR) then
MessageBox("hello Admin", INFORMATION); // testing only
// do nothing we are an admin
else
MessageBox("hello user", SEVERE); // testing only
RunAsAdmin();
endif;
end;
export prototype RunAsAdmin();
function RunAsAdmin()
begin
STRING username = "myUserID";
STRING password = "myPassword";
STRING filepath = INSTALLPROPERTY_INSTALLLOCATION;
RunAsUserAccount(username,password,filepath);
end;
export prototype RunAsUserAccount();
function RunAsUserAccount()
STRING username;
STRING password;
STRING filepath;
begin
/*
Is this the best way to do this? this is the function I need help with
This seems like a hack
*/
if ( SYSINFO.WINNT.bWinXP ) then
LAAW_SHELLEXECUTEVERB = "open"; // target PC is on Windows XP
else
LAAW_SHELLEXECUTEVERB = "runas"; // Windows 7 (or Vista)
endif;
LaunchApplication(
filepath
,"" // Arguments
,"" // Directory
,SW_NORMAL // Use Window Mode
,0
,LAAW_OPTION_WAIT_INCL_CHILD | LAAW_OPTION_USE_SHELLEXECUTE
);
end;
How do I relaunch the installer though? This can be done in Wise Package Studio and many other tools but I haven't found the answer how to do it in this one yet.
I know I could probably do a runas.exe or psexec.exe but that feels like a hack and sounds like a poor practice. After about a day of reading I am still not sure how to do this though.
Could someone please point me in the right direction of the proper way to do this in InstallShield?

Tell the user in the MessageBox to relaunch the setup with admin rights. Explain that they can do this by right clicking the setup.exe in Explorer and then clicking "run as admin" and then OK in the UAC prompt. This is the conceptually "clean" way to do it, since you are doing nothing unexpected.
As I have already stated in the comment above, do think twice about Installscript MSI. It is extremely buggy. The better option is a Basic MSI with Installscript custom action code.
A basic MSI is a standard MSI, and the Installscript custom actions do not interfere with the setup GUI or the overall MSI operation. In Installscript MSI the whole installation sequence is Installscript controlled, and this causes serious bugs - some of which have no workarounds or fixes (even years after their discovery).

Perhaps this is along the lines of what you are looking for: How can I make the installer Run as admin. Also check this.
If you use an MSI instead of Installscript and use the built-in MSI concept of elevated rights, you can use MSI properties set at the command line to install anywhere with elevated rights: msiexec /i MySetup.MSI USER=OneUser /PASS=PassWord /qn. Just stating what is possible and perhaps easier.

Related

Failed to run script function from Custom Actions

I implemented an installscript file of my own that will allow the installer to register dll's. I combined this with a custom action to actually run the function I created. I had to make this script because the dlls were failing to register with setting the property to self register. So I am stuck on how to resolve this problem with the custom action failing so that I can test my script.
Below is my script:
#include "Ifx.h"
export prototype RegisterComponents(HWND);
function RegisterComponents(hMSI)
STRING sRunStr;
begin
sRunStr = WINSYSDIR ^ "regsvr32.exe";
LongPathToQuote(sRunStr, TRUE);
//change the directory to target directory
ChangeDirectory(TARGETDIR);
//register dll
if(LaunchAppAndWait(sRunStr, "/s " +
"C:\NCRUniEmulatorService\NCRUniEmulatorSO.dll",
WAIT)) < 0) then
MessageBox("NCRUniEmulatorSO.dll", SEVERE);
else
endif;
end;
Below is my custom actions:
FunctionName: RegisterComponents
Return Processing: Synchronous
In-Script Execution: Deferred Execution
Install Exec Sequence: After PublishProduct
All other sequence settings are set to Absent from sequence.
log file
InstallShield 14:17:18: Invoking script function RegisterComponents
InstallShield 14:17:18: Failed to run script function, error
0x80020006 InstallShield 14:17:18: CallScriptFunctionFromMsiCA() ends,
result 0x643 CustomAction RegisterComponents returned actual error
code 1603 but will be translated to success due to continue marking
Is this an MSI project? I would not use self-registration, instead enable COM Extract at Build in the property page for the component in question:
If this extraction does not work, then you might have dependencies that are not met for the file to load. For example a missing resource dll or something like that. The extraction process for "COM Extract at Build" will populate a number of MSI-specific COM tables that take care of all COM registration details for you (including rollback support).
InstallShield Self-Registration: Additionally you can enable self-registration for a file in Installshield and not run via custom action code at all. I think it is in the property page for each file.

How can i change property in InstallScript

I want to change property value to selected text in a dialog.
this is my sample source.
#include "ifx.h"
STRING outPath;
export prototype MyFunction(HWND);
function OnFirstUIBefore()
NUMBER nResult, nSetupType, nvSize, nUser;
STRING szTitle, szMsg, szQuestion, svName, svCompany, szFile, szDir;
STRING szLicenseFile;
BOOL bCustom, bIgnore1, bIgnore2;
begin
Dlg_SdAskDestPath:
nResult = SdAskDestPath(szTitle, szMsg, INSTALLDIR, 0);
if (nResult = BACK) goto Dlg_SdAskDestPath;
Dlg_AskOutPath:
nResult = AskDestPath(szTitle, szmsg, szDir, 0);
if (nResult = BACK) goto Dlg_SdAskDestPath;
outPath = szDir;
MyFunction(ISMSI_HANDLE);
return 0;
end;
function MyFunction(hMSI)
STRING value;
begin
MsiSetProperty(hMSI, "OutPutPath", outPath);
end;
OutPutPath used in custom action after finish install.
But OutPutPath was not changed when read in custom action.
I think I must not use ISMSI_HANDLE. But i don't know what i have to use instead.
I tried to make custom action which load Install scripts's method MyFunction after finish install.
It worked well, But the global variable outPath was nul..
Please teach me how can i do this if you know.
Thank you.
At a minimum, you must use a public property, that is one with a name that does not contain any lowercase letters. You may also have to list it in SecureCustomProperties in order to allow users to modify it, if you support installation in restricted environments.
However I'm not certain the exact scenario described by your comment:
I tried to make custom action which load Install scripts's method MyFunction after finish install.
If this scenario is truly after the end of the Windows Installer portion of the installation (an InstallScript MSI runs code both before and after), properties as a whole may not survive to do what you need. To support reading the value at that time you will have to consider other approaches, such as writing the value in the registry, or into a file (e.g. in SUPPORTDIR).

Running into issue creating shortcut on desktop

I've just created a custom dialog with a checkbox asking if the user wants to create a desktop shortcut. I used to always include a shortcut I'm not using the AskText() function as I plan on adding more pieces to this page later and want to simplify these few options to this one page.
I get an item on my desktop when I run, but it's not what I expect. The target seems to be pointing to a location on the desktop itself and not the actual executable. Also, this shortcut does not delete on uninstall (I'm assuming this needs to be handled separately anyway) and the shortcut needs admin rights to be manually deleted (which I don't want, for obvious reasons).
Below is my InstallScript code. It is in a custom action that was inserted after InstallFiles.
function MyFunction(hMSI)
STRING szProgramFolder, szItemName, szCommandLine, szWorkingDir;
STRING szShortCutKey, szProgram, szParam, szIconPath;
NUMBER nIcon, nResult;
begin
szProgramFolder = FOLDER_DESKTOP;
szItemName = "myProgram";
szProgram = INSTALLDIR + "myProgram.exe" ;
LongPathToQuote (szProgram, TRUE);
szCommandLine = szProgram;
szWorkingDir = INSTALLDIR;
szIconPath = "";
nIcon = 0;
szShortCutKey = "";
nResult = AddFolderIcon (szProgramFolder, szItemName, szCommandLine,szWorkingDir,
szIconPath, nIcon, szShortCutKey, REPLACE);
end;
I'm not quite sure where I'm going wrong here, although my knowledge of InstallShield (let alone InstallScript) is very limited.
As it turned out, this is a deferred custom action, hence the INSTALLDIR variable is not initialized (nor any other Windows Installer built-in variables). Change it to an Immediate-type custom action (and relocate it to an appropriate location in the execution sequence) and it should work.
To fix the shortcut's parameters, start by ensuring they are correct. Debug your function to verify you are actually passing what you want to. As commented, INSTALLDIR may not be available directly to an InstallScript custom action. A simple way to "debug" would be to add calls like MessageBox(szCommandLine, 0); to key points in your code. If you find you are passing something like C:\Program Files\Company\ProductmyProgram.exe, consider using the ^ operator to concatenate your paths: szProgram = INSTALLDIR ^ "myProgram.exe";.
To uninstall the shortcut, you have to understand that custom actions in MSI projects are not automatically reversed. So use a different approach. Either explicitly code up its removal during uninstall in another action, switch to pure InstallScript where logging will reverse your actions, or go with a proper MSI-based approach. For the last of those, define the shortcut in its own component, and give the component a condition that correlates to a property you set in your UI (or via AskText for now), or skip the condition and just use feature selection by putting the component in a child feature. Then Windows Installer will track and remove the shortcut for you.

string too long with MsiGetProperty with Installshield Installscript

I am using MsiGetProperty to get string parameter value from the installer.
And after that I am calling a managed dll and I pass the that value:
nvBufferSize = MAX_STRING;
MsiGetProperty (hMSI, "DBHMS", sDbHost, nvBufferSize);
when I pass the value of sDbHost is like this when I receive it from managed code:
srvdata-02NULNULNULNULNULNUL......
however in the interface I wrote just "srvdata-02".
With that same code it was fine with Installshield 2010, now I am upgrading it to installshield 2012.
Do you have any solution with that please?
There were some behavior changes to MsiGetProperty awhile back. Try setting nvBufferSize to MAX_SIZE instead of MAX_STRING. Also check the return code of MsiGetProperty to see if it equals ERROR_MORE_DATA or if it's returning some other code. Finally check the value of nvBufferSize to see how many bytes are needed.
BTW, if you are just trying to marshal a property over to managed code, you might want to conisder looking into Deployment Tools Framework (DTF) in Windows Installer XML (WiX). This is a very nice SDK that allows you to write managed code custom actions and package them up as if they are native Win32 libraries. InstallShield can then easily use this as an MSI DLL custom action.
DTF provides an interop library and a session object that can be used like:
Deployment Tools Foundation (DTF) Managed Custom Actions
Reasons DTF is Better
As ridiculous as it may seem, here's a working InstallScript solution for you:
nvBufferSize = MAX_STRING;
nResult = MsiGetProperty( ISMSI_HANDLE, szPropertyName, svValue, nvBufferSize );
if( nResult = ERROR_MORE_DATA ) then
MsiGetProperty( ISMSI_HANDLE, szPropertyName, svValue, nvBufferSize );
endif;
The first attempt returns the actual buffer size needed. If it is more then max string (1024?), the second call gets the whole thing.
Alternatively, I found I could assign nvBufferSize to larger value right off the bat e.g. 4096 and use that with a single call (assuming the data was no longer that limit). The double call, however, is more fool proof.
According to: https://msdn.microsoft.com/en-us/library/aa370134(v=vs.85).aspx the api function is actually designed to return the buffersize by passing an empty literal ("") instead of a string variable. InstallScript 2013 throws a compilation error at you if you try that though...

Visual C++ 6.0 on Windows 8

Visual C++ 6.0 is not supported on Windows 8, but we have a couple of legacy apps that still needs to be compiled with Visual C++ 6.0. :-(
It is possible to install Visual C++ 6.0 on Windows 8 by unchecking Data Access -> Change Options -> ADO, RDS and OLE DB Providers. See this SU-question and this thread. You also need to install SP6 afterwards.
Visual C++ 6.0 works perfectly on one computer, but two others cannot use the debugger. The same hardware, same version of Windows, same person doing the installation, same project. There must be some difference...
On the computers with the problem you can set a break point and the debugger will break into the IDE, but when you try do step, step into or run the code will crash with Unhandled exception in EXENAME.EXE (OLE32.DLL): 0xC0000005: Access Violation.
Walter Oney reports the exact same problem on MSDN forums, but they have no solution as VC++ 6.0 is unsupported.
As we have Visual C++ 6.0 working on one Win8 computer there is apparently way to do it. Any ideas on what the difference could be?
Turning off OLE RPC debugging (Tools / Options / Debug) works for me (Windows 8 Pro 64 bit, Visual C++ 6.0 with SP6). This solution was suggested (later) within the above-mentioned MSDN forum thread.
I was eventually able to get VS 6 working on Win 8 and Win 10. The basic steps were these:
Create a dummy file named msjava.dll in \Windows. (E.g., "echo >msjava.dll") Without this step, the VS 6 installer can't get very far.
Install VS 6 and SP 6.
Rename MSDEV.EXE to something else, such as MSDEVQ.EXE.
Create a compatibility database for MSDEVQ that excludes the fault-tolerant heap shim. Without this step, debugging a program that makes heavy use of HeapAlloc, etc., is excruciatingly slow.
For debugging, ensure that a breakpoint is tripped before any calls to OLE32 can occur. I include the following header early in the main program or (for an MFC app) the InitInstance function:
X64DebugHack.h:
#ifdef _DEBUG
// In order to be able to debug this application on x64, we need to single
// step across at least one statement before ole32.dll gets loaded. So
// always leave this breakpoint in place.
requiredbreakpoint:
int junkola = 42;
// Check to see that there was a breakpoint...
PUCHAR pjunk;
_asm lea eax, requiredbreakpoint
_asm mov pjunk, eax
if (*pjunk != 0xCC)
AfxMessageBox("Required breakpoint was not set prior to loading OLE32.DLL -- single stepping will not be possible during this debugging session.", MB_OK | MB_ICONHAND, 0);
LoadLibrary("OLE32");
#endif
Write an extension DLL that provides a "Stop Debugging" button. The extension has to search and destroy debug handles, which have a different handle type in Win64 than in Win32. The mechanics of writing the extension are beyond the scope of this forum, but the code that does the actual work is here:
CCommands::HelpAssistantKill:
typedef LONG NTSTATUS;
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
enum SYSTEM_INFORMATION_CLASS {
SystemHandleInformation = 16,
};
typedef NTSTATUS(NTAPI *PNTQUERYSYSTEMINFORMATION)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG ProcessId;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
typedef struct _SYSTEM_HANDLE_INFORMATION_DATA {
ULONG HandleCount;
SYSTEM_HANDLE_INFORMATION HandleInformation[1];
} SYSTEM_HANDLE_INFORMATION_DATA, *PSYSTEM_HANDLE_INFORMATION_DATA;
#define HANDLE_TYPE_DEBUG_OBJECT 11 // correct value for Win8 x64
STDMETHODIMP CCommands::HelpAssistantKill()
{ // CCommands::HelpAssistantKill
AFX_MANAGE_STATE(AfxGetStaticModuleState());
BOOL didit = FALSE;
HMODULE hDll = NULL;
PSYSTEM_HANDLE_INFORMATION_DATA phi = NULL;
do { // do once
HRESULT hr;
// Locate NtQuerySystemInformation within NTDLL.DLL
hDll = LoadLibrary("NTDLL");
if (!hDll)
break;
PNTQUERYSYSTEMINFORMATION NtQuerySystemInformation = (PNTQUERYSYSTEMINFORMATION) GetProcAddress(hDll, "NtQuerySystemInformation");
if (!NtQuerySystemInformation)
break;
// Do an initial query to get the number of handles presently open in the system.
// This is a large number. The returned length value is meaningless for this query.
SYSTEM_HANDLE_INFORMATION_DATA hid;
DWORD junk;
NTSTATUS status = (*NtQuerySystemInformation)(SystemHandleInformation, &hid, sizeof(hid), &junk);
if (!NT_SUCCESS(status) && status != STATUS_INFO_LENGTH_MISMATCH)
break;
ULONG length = sizeof(SYSTEM_HANDLE_INFORMATION_DATA) + (hid.HandleCount - 1) * sizeof(SYSTEM_HANDLE_INFORMATION);
phi = (PSYSTEM_HANDLE_INFORMATION_DATA) new UCHAR[length];
if (!phi)
break;
// Get a list of all handles open in the system
status = (*NtQuerySystemInformation)(SystemHandleInformation, phi, length, &junk);
if (!NT_SUCCESS(status))
break;
// Find and close any debug objects that are open in this instance of Visual Studio.
DWORD pid = GetCurrentProcessId();
ULONG ihandle;
for (ihandle = 0; ihandle < hid.HandleCount; ++ihandle)
{ // for each open handle
PSYSTEM_HANDLE_INFORMATION p = phi->HandleInformation + ihandle;
if (p->ProcessId != pid || p->ObjectTypeNumber != HANDLE_TYPE_DEBUG_OBJECT)
continue;
if (CloseHandle((HANDLE) p->Handle))
didit = TRUE;
} // for each open handle
// Instruct DevStudio to stop
BSTR bsStopDebugging = SysAllocString(L"DebugStopDebugging");
if (!bsStopDebugging)
break;
hr = m_pApplication->ExecuteCommand(bsStopDebugging);
SysFreeString(bsStopDebugging);
if (hr != 0)
break;
} // do once
while (FALSE);
if (phi)
delete[] phi;
if (hDll)
FreeLibrary(hDll);
if (!didit)
{ // didn't do anything
MessageBox(NULL, "Unable to find and close any debug object handles", "HelpAssistant", MB_OK | MB_ICONINFORMATION);
} // didn't do anything
return S_OK;
} // CCommands::HelpAssistantKill
This felt like a pretty heroic effort, but I had about a million lines of code built on VS 6 that I had to keep working. Now that I've built myself a workable macro processor for VS 2015, I may undertake a conversion of this application.
One wrinkle -- I had the very same same issue with the Visual C++ 6.0 debugger on Windows 8.1 . But I could not find the RPC debug option under the Tools/Options/ Debug option described in the answer above. Instead I had to go into the registry editor and delete the RPC Debug key that is mentioned in the same MSDN thread referenced above (maybe it was there because I had already installed later versions of Microsoft Visual Studio before I had installed 6.0) . The debugger works great now, and thanks to previous posters!
The issue is due to incompatible “ADO, RDS and OLE DB Providers” comes along with the Visual C++ 6.0.
Please follow the below mentioned steps to disable ADO, RDS and OLE DB Providers and install the Visual C++ 6.0 –
1) Start the installation as usual.
2) Click on Custom installation when installer will ask for type of installation.
3) Click on Data Access from available items and then click on ‘Change Option’.
4) In new window de-select “ADO, RDS and OLE DB Providers” and click OK (ignore the warning).
5) Click on continue to proceed with the installation.
6) Installer will not freeze during ‘Updating components’ and will install successfully.
7) Now install the service pack ‘Vs6sp6’ and it will also install successfully.
Non of above answers work for me.
Solution from this site fix my problem.
Re-register ole32.dll file and check if it helps.
Click Start, type cmd in the Start search.
Right click on cmd and click on Run as administrator.
In the Command prompt, type the following commands and hit ENTER
after each command.
takeown /f ole32.dll
regsvr32 ole32.dll
Close the Command prompt after running the above two commands.
Try to run the application and check is the issue persists.

Resources