MSI properties set during the pre-file transfer events (OnBegin, OnFirstUIBefore, etc.) or set during immediate custom actions will lose their values in post-file transfer events (OnFirstUIAfter, OnEnd, etc.). This is an unintended side-effect due to how InstallScript MSI projects are implemented.
The basic reason for this is the InstallScript engine acts as an external UI handler for the MSI package being installed. In order to interact with Windows Installer, the InstallScript engine opens an installation session through the MsiOpenPackage API. All properties set during pre-file transfer events are backed by this installer session. Just before file-transfer is started, this installer session needs to be closed, otherwise, Windows Installer would be unable to read the MSI package and the install would fail. A consequence of closing the session is any properties set during that session are essentially lost. Properties that are Directory table entries, such as INSTALLDIR, are reinitialized after file-transfer by running the Windows Installer costing actions.
If you need to maintain property values between pre and post file transfer events in InstallScript, global variables should be used in addition to or instead of MSI properties. Properties set during custom actions in the execute sequence would need to be written to a file or registry entries on the machine as there is no way to pass global variables from InstallScript custom actions to InstallScript events.
__________________
Josh Stechnij - Software Engineer - Acresso Software: InstallShield Team
Showing posts with label InstallShield. Show all posts
Showing posts with label InstallShield. Show all posts
Thursday, June 11, 2009
InstallShield and MsiGetProperty
For a long time I have had a problem with MsiGetProperty in that sometimes it would work and sometimes it wouldn't. I posted about it on the InstallShield forums and got this great piece of information:
Tuesday, March 24, 2009
InstallShield Merging Web Sites
I had this problem with my InstallShield project where it was merging my web site into the default web site instead of creating a new web site. This was happening if I was installing the site using port 80 which was also being used by the default web site.
After some research (and help) I found that the problem was caused by the web site number (a value in the IIS config settings in the project). I had set this value to 0 thinking InstallShield would just use the next available web site number. However, this is not so. I don't know why, but if the port was used by another site, it used that site's number. If the port wasn't in use, it would used the next number. As far as I know, there isn't a mechanism to use the next available. So, I wrote a function in InstallScript that calls out the the IIS admin script absutil.vbs to get the numbers in use and return the next one.
Here is the function:
Hopefully this will save you time and pain.
*EDIT* This doesn't quite work. Look for the posted correction.
After some research (and help) I found that the problem was caused by the web site number (a value in the IIS config settings in the project). I had set this value to 0 thinking InstallShield would just use the next available web site number. However, this is not so. I don't know why, but if the port was used by another site, it used that site's number. If the port wasn't in use, it would used the next number. As far as I know, there isn't a mechanism to use the next available. So, I wrote a function in InstallScript that calls out the the IIS admin script absutil.vbs to get the numbers in use and return the next one.
Here is the function:
//---------------------------------------------------------------------------
// GetNextWebSiteNumber
//
// Author: Yevi
//
// Description: This function returns the next available web site number based
// on entries for current web sites in IIS. The admin script adsutil.vbs is
// used the query current web sites.
// If there is an error, the function returns -1.
//---------------------------------------------------------------------------
function GetNextWebSiteNumber()
NUMBER nResult;
NUMBER nvTempWebSiteNumber;
NUMBER nWebSiteNumber;
NUMBER nvFileHandle;
NUMBER nNumPos;
STRING szProgName;
STRING szProgParams;
STRING szFileDir;
STRING szFileName;
STRING svLine;
STRING svSiteNum;
begin
nvTempWebSiteNumber = -1;
nWebSiteNumber = nvTempWebSiteNumber;
szFileDir = SUPPORTDIR;
szFileName = "websiteenum.txt";
// Run the adsutil.vbs script and get all the web sites. Put the
// output into a file.
szProgName = WINDIR^"system32\\cscript.exe";
szProgParams = " C:\\inetpub\\AdminScripts\adsutil.vbs ENUM /p W3SVC > " + szFileDir^szFileName;
if (LaunchAppAndWait(szProgName, szProgParams, WAIT) >= 0) then
// Open the file with the web sites and find the number
// of the last one.
OpenFileMode(FILE_MODE_NORMAL);
if (!OpenFile (nvFileHandle, szFileDir, szFileName) < 0) then
while GetLine (nvFileHandle, svLine) = 0
if (svLine % "W3SVC/") then
nNumPos = StrFind(svLine, "/");
StrSub(svSiteNum, svLine, nNumPos, 10);
StrToNum(nvTempWebSiteNumber, svSiteNum);
if (nvTempWebSiteNumber > nWebSiteNumber) then
nWebSiteNumber = nvTempWebSiteNumber;
endif;
endif;
endwhile;
CloseFile(nvFileHandle);
nWebSiteNumber = nWebSiteNumber + 1;
endif;
endif;
return (nWebSiteNumber);
end;
Hopefully this will save you time and pain.
*EDIT* This doesn't quite work. Look for the posted correction.
Labels:
Default Web Site,
IIS,
InstallScript,
InstallShield,
Web Site Number
Thursday, August 21, 2008
InstallShield XML File Changes
InstallShield 2008 has an interesting quirk when editing XML File Changes. When changing the value of an XML tag by typing a new value in, it doesn't like to get saved. You need to do something else on that screen like check/uncheck a box before the new value is saved. Or you can select the new value from the list of string values.
One thing I don't do well is use the string values from InstallShield. I like to type in the values I want instead of just changing the value of a string. I had several XML files that needed the exact same change. If I had properly used the string values, I could have just made the change once.
One thing I don't do well is use the string values from InstallShield. I like to type in the values I want instead of just changing the value of a string. I had several XML files that needed the exact same change. If I had properly used the string values, I could have just made the change once.
Tuesday, August 5, 2008
InstallShield and LogonAsService Right
I was somewhat perplexed by this one. I am installing a Windows NT Service (don't let the "NT" bother you, that is just the OS when services where introduced or something like that). I have configured the service in my components. I filled out the properties for NT Services and set properties for the user and password. Then I use the user dialog I mentioned in previous posts to get the user credentials for the service. This dialog can create a new user for this, which is typically the case for my project.
However, the installed service doesn't start. Instead I get an error that the service can't be started with the message being "Error 1069: The service did not start due to a logon failure." I went into the properties to the Log On tab to check things. The password is obscured, so I retyped it in and clicked Apply. Then I get the message "The account has been granted the Log On As A Service right." After that, the service works.
This is what I learned. Users, especially domain users, don't have this right typically. I am guessing that since the new user is specified in the format "DOMAIN\USER" it is considered a domain user even if the domain is the local system. The localsystem user already has this right granted to it.
For some reason, MSI or InstallShield doesn't allow you to grant this right when the user is created. There are some posts on the InstallShield forums that say this. There is also mention of the answer.
The answer is calling the utility NTRIGHTS.EXE. This is the Windows command-line utility for administrating rights for users. So, first you need to acquire the utility. It comes from the Windows Resource Kit. It is not installed as part of Windows, so you will need to download it and install it. After that, you have the utility. Next it needs to be included in your InstallShield project. I copied the utility for a folder where I have my support files (I have all my files together for easy source-control). I then added it to my support files inside my project as a language independent file. Then I run it using LaunchAppAndWait.
szProgName = SUPPORTDIR^"ntrights.exe";
szProgParams = " -u" + szUserName + " +r SeServiceLogonRight";
StatusBox("Executing '" + szProgName + szProgParams + "'", DEBUG);
if (LaunchAppAndWait(szProgName, szProgParams, WAIT) <>
MessageBox("Unable to launch " + szProgName + ". Retype the password for the the user in the workflow service properties.", WARNING);
endif;
BTW, I call it from a later event in my InstallScript, specifically OnFirstUIAfter. That may not be the best as it would also need to be in OnMaintUIAfter, but this is a first cut and I haven't got all the maintenance mode worked out yet. Also, I put the call in a function so that I can call it from wherever it needs. The point is that it needs to called AFTER the user is created which is after InstallShield's "Do Stuff" phase.
Problem Solved.
However, the installed service doesn't start. Instead I get an error that the service can't be started with the message being "Error 1069: The service did not start due to a logon failure." I went into the properties to the Log On tab to check things. The password is obscured, so I retyped it in and clicked Apply. Then I get the message "The account
This is what I learned. Users, especially domain users, don't have this right typically. I am guessing that since the new user is specified in the format "DOMAIN\USER" it is considered a domain user even if the domain is the local system. The localsystem user already has this right granted to it.
For some reason, MSI or InstallShield doesn't allow you to grant this right when the user is created. There are some posts on the InstallShield forums that say this. There is also mention of the answer.
The answer is calling the utility NTRIGHTS.EXE. This is the Windows command-line utility for administrating rights for users. So, first you need to acquire the utility. It comes from the Windows Resource Kit. It is not installed as part of Windows, so you will need to download it and install it. After that, you have the utility. Next it needs to be included in your InstallShield project. I copied the utility for a folder where I have my support files (I have all my files together for easy source-control). I then added it to my support files inside my project as a language independent file. Then I run it using LaunchAppAndWait.
szProgName = SUPPORTDIR^"ntrights.exe";
szProgParams = " -u" + szUserName + " +r SeServiceLogonRight";
StatusBox("Executing '" + szProgName + szProgParams + "'", DEBUG);
if (LaunchAppAndWait(szProgName, szProgParams, WAIT) <>
MessageBox("Unable to launch " + szProgName + ". Retype the password for the the user in the workflow service properties.", WARNING);
endif;
BTW, I call it from a later event in my InstallScript, specifically OnFirstUIAfter. That may not be the best as it would also need to be in OnMaintUIAfter, but this is a first cut and I haven't got all the maintenance mode worked out yet. Also, I put the call in a function so that I can call it from wherever it needs. The point is that it needs to called AFTER the user is created which is after InstallShield's "Do Stuff" phase.
Problem Solved.
Monday, August 4, 2008
SdLogonUserInformation
I discovered a interesting tidbit today -- SdLogonUserInformation only works with one user. It has a single set of properties it uses to gather user information. If run this dialog more than once to get multiple users, the last set of credentials wins.
Friday, August 1, 2008
Quiet Install of an MSI
My project has AJAX Extensions as a prerequisite. So, it needs to run the APSAJAXExtSetup.msi. It needs to run it as a prereq for my install. Also, I don't want my users to see, I want it installed for all users and I don't want it to run if it exists.
First, I found this link which was helpful. That and running the MSI with /? to get the help list got me the following command line.
APSAJAXExtSetup.msi /quiet ALLUSERS=2
I guess I need to back up a moment. I had to create a prerequisite in IS before all this. There didn't seem to be a standard one for AJAX extensions. In the Redistributable view, I added one. I then added the MSI on the File To Include tab. Then on the Application to Run tab I selected the MSI. The parameters can then be put into command line box on the Application to Run tab.
In order to conditionally run the MSI, you need to set a condition on the conditions tab. You need to specifiy a registry key or a file to check for that shows the package exists. To find a something, I opened the AJAX MSI in InstallShield and started poking around. I looked at the Registry view and settled on the key
HKEY_CURRENT_USER\Software\Microsoft\ASP.NET\ASP.NET 2.0 AJAX Extensions
I set the condition that if the key does not exist, run the prerequisite.
*EDIT* I guess the install is not completely silent. InstallShield has a dialog with a status bar showing that a prerequisite is being installed and what the prereq is. But the actual dialogs from the MSI package don't show.
First, I found this link which was helpful. That and running the MSI with /? to get the help list got me the following command line.
APSAJAXExtSetup.msi /quiet ALLUSERS=2
I guess I need to back up a moment. I had to create a prerequisite in IS before all this. There didn't seem to be a standard one for AJAX extensions. In the Redistributable view, I added one. I then added the MSI on the File To Include tab. Then on the Application to Run tab I selected the MSI. The parameters can then be put into command line box on the Application to Run tab.
In order to conditionally run the MSI, you need to set a condition on the conditions tab. You need to specifiy a registry key or a file to check for that shows the package exists. To find a something, I opened the AJAX MSI in InstallShield and started poking around. I looked at the Registry view and settled on the key
HKEY_CURRENT_USER\Software\Microsoft\ASP.NET\ASP.NET 2.0 AJAX Extensions
I set the condition that if the key does not exist, run the prerequisite.
*EDIT* I guess the install is not completely silent. InstallShield has a dialog with a status bar showing that a prerequisite is being installed and what the prereq is. But the actual dialogs from the MSI package don't show.
Labels:
AJAX Extensions,
ALLUSERS,
InstallShield,
MSI,
setup prerequisite
InstallShield LaunchAppAndWait and File Access
The Task: Grant access to the files I have installed to the users I have created.
In the Files & Folders view, you can right click on a folder and select properties to get access to the permissions for a file or folder. However, this doesn't work so well because you can't add permissions to a file, you can only replace them. I didn't want that as I didn't want to clear out the Administrators group access to my install folder. I just wanted to add a couple of users to the access list. The real answer is to use LaunchAppAndWait to run cacls.exe on the folder.
I have used LaunchAppAndWait in the past and have struggled with it. It is difficult to debug. So, there are some things I would recommend. First, add a MessageBox for debug that shows what you are going to run. If you are not getting desired results, copy exactly what is in your message into a console window on the target system with your files in place and see what happens. You might be surprised. LaunchAppAndWait tells you if it launched the program, but does not tell your if the program had errors.
Of course, that isn't the way I did it the first time. The first time I was convinced LaunchAppAndWait was messing up. So, I created a sanity-check app in Visual Studio and called it from LaunchAppAndWait.

It's very simple. Take the arguments from the command line and put them into a text file. This way, you can use whatever command parameters your real command needs. Just change the program to the test app.
To use LaunchAppAndWait, set up a variable for the program name and a variable for the parameters. Actually, here is my function.
function SetDirectoryPermissions(szDirectoryName, szUserName)
STRING szProgName;
STRING szProgParams;
STRING svTempString;
begin
// Remove any trailing slash from the directory name.
StrSub(svTempString, szDirectoryName, StrLength(szDirectoryName) - 1, 1);
if (svTempString = "\\") then
StrSub(svTempString, szDirectoryName, 0, StrLength(szDirectoryName) - 1);
szDirectoryName = svTempString;
endif;
szProgName = WINDIR^"system32\\cacls.exe";
szProgParams = " " + szDirectoryName + " /E /G " + szUserName + ":F";
if (LaunchAppAndWait(szProgName, szProgParams, WAIT) <>
MessageBox("Unable to launch cacls. File permissions will need to be set manually.", WARNING);
endif;
end;
With all that out there in the open, it shows the issue I had with cacls.exe. If you pass in INSTALLDIR, there is a trailing slash (IS always puts one there). cacls.exe throws an error saying that it can't find the file. So, I had to remove the trailing slash and IT WORKED!
BTW, the command it is running is
cacls.exe C:\InstallDir /e /g DOMAIN\USER:F
Which brings up my last point. You should include the domain with the user name. Windows needs that to properly locate the credentials for the user.
Ok, so my code samples are rough. I am still getting used to blogger.
In the Files & Folders view, you can right click on a folder and select properties to get access to the permissions for a file or folder. However, this doesn't work so well because you can't add permissions to a file, you can only replace them. I didn't want that as I didn't want to clear out the Administrators group access to my install folder. I just wanted to add a couple of users to the access list. The real answer is to use LaunchAppAndWait to run cacls.exe on the folder.
I have used LaunchAppAndWait in the past and have struggled with it. It is difficult to debug. So, there are some things I would recommend. First, add a MessageBox for debug that shows what you are going to run. If you are not getting desired results, copy exactly what is in your message into a console window on the target system with your files in place and see what happens. You might be surprised. LaunchAppAndWait tells you if it launched the program, but does not tell your if the program had errors.
Of course, that isn't the way I did it the first time. The first time I was convinced LaunchAppAndWait was messing up. So, I created a sanity-check app in Visual Studio and called it from LaunchAppAndWait.

It's very simple. Take the arguments from the command line and put them into a text file. This way, you can use whatever command parameters your real command needs. Just change the program to the test app.
To use LaunchAppAndWait, set up a variable for the program name and a variable for the parameters. Actually, here is my function.
function SetDirectoryPermissions(szDirectoryName, szUserName)
STRING szProgName;
STRING szProgParams;
STRING svTempString;
begin
// Remove any trailing slash from the directory name.
StrSub(svTempString, szDirectoryName, StrLength(szDirectoryName) - 1, 1);
if (svTempString = "\\") then
StrSub(svTempString, szDirectoryName, 0, StrLength(szDirectoryName) - 1);
szDirectoryName = svTempString;
endif;
szProgName = WINDIR^"system32\\cacls.exe";
szProgParams = " " + szDirectoryName + " /E /G " + szUserName + ":F";
if (LaunchAppAndWait(szProgName, szProgParams, WAIT) <>
MessageBox("Unable to launch cacls. File permissions will need to be set manually.", WARNING);
endif;
end;
With all that out there in the open, it shows the issue I had with cacls.exe. If you pass in INSTALLDIR, there is a trailing slash (IS always puts one there). cacls.exe throws an error saying that it can't find the file. So, I had to remove the trailing slash and IT WORKED!
BTW, the command it is running is
cacls.exe C:\InstallDir /e /g DOMAIN\USER:F
Which brings up my last point. You should include the domain with the user name. Windows needs that to properly locate the credentials for the user.
Ok, so my code samples are rough. I am still getting used to blogger.
Labels:
cacls,
File Permissions,
InstallScript,
InstallShield,
LaunchAppAndWait
Subscribe to:
Comments (Atom)
