Unable to write registry in custom action of MSI installer - c#

I have an MSI installer that installs my Windows Service and in my custom actions I need to write some values into the Registry in the HKEY_LOCAL_MACHINE/SOFTWARE/MYSoftware key.
I am trying to do this and it's not working, but from my Windows Service it's working fine. Can anybody tell me where I am going wrong?
string registryLocaltion = AgentProperties.TMAGENT_REGISTRY_LOCATION
+ #"\" +AgentProperties.TMAgentVersion;
tmKeyMain = Registry.LocalMachine.OpenSubKey(registryLocaltion, true);
if (tmKeyMain == null)
{
log.Error("Unable to open registry key " + registryLocaltion);
}
tmKeyMain.SetValue("UseProxySettings", settings.UseProxySettings);
if (settings.UseProxySettings)
{
tmKeyMain.SetValue("ProxyHost", settings.ProxyHost);
tmKeyMain.SetValue("ProxyPort", settings.ProxyPort);
tmKeyMain.SetValue("ProxyUsername",
GenericHelper.ConvertToBase64Encoding(settings.ProxyUsername));
tmKeyMain.SetValue("ProxyPassword",
GenericHelper.ConvertToBase64Encoding(settings.ProxyPassword));
tmKeyMain.SetValue("ProxyExclusion", settings.ProxyExclusion);
tmKeyMain.SetValue("BypassProxy", settings.BypassProxy);
}
This code is working fine in my Windows Service, but if I do some thing very similar in my custom action in the MSI installer it doesn't work.
Can anybody tell me where I am going wrong?

You are up against a couple problems. The most obvious problem is that Visual Studio Deployment projects incorrectly schedule custom actions to impersonate the client context. This means in a UAC scenario you won't have permissions. The quick work around is to run the MSI from an already elevated command prompt context.
The second problem is that Visual Studio Deployment Projects abstract / hide the underlying MSI too much. For Custom Actions, it only gives you the options of "install, uninstall, rollback, commit" without exposing any additional settings. It hides the ServiceInstall and ServiceControl tables. This causes you to write a custom action that reinvents the wheel.
See, all your custom action should be doing is performing the business logic and setting properties. Then you should be using the Registry table to set the data based on the properties. This way you leverage as much of Windows Installer as possible and all of it's free transactional / rollback capabilities.
This problem repeats over and over and is why Microsoft killed of the setup project types in VS2012.
If it was my install, I'd be refactoring the design to use AppSearch/Reglocator to read in the data, have a minimalist custom action to do the processing and then use the Registry table to apply the data.
That will require you at a minimum to look at Windows Installer XML to create a merge module that has all of this logic and gets merged into your existing setup project. That takes awhile to learn though.

Related

Why does WScript work when I run my script from the desktop, but not when run as part of Setup.exe?

I've been working on this issue for about 6 hours now and feel like I'm getting nowhere. I have a vbscript to write to the windows registry that I want to use during the installation of a .net Visual Studio program. It writes to the Local Machine part of the registry, so permissions have to be edited to accomplish that. The code in question is as follows:
If Not WScript.Arguments.Named.Exists("elevate") Then
CreateObject("Shell.Application").ShellExecute WScript.FullName _
, WScript.ScriptFullName & " /elevate", "", "runas", 1
WScript.Quit
End If
The code works perfectly when I just run the script from my desktop, and does exactly what I need it to do. But when I run the setup.exe that includes the script (even if I run setup.exe from my desktop), I get an Object Required error from the WScript code above. I need to know either 1) why I am getting this error and how to allow access to the WScript object, or if that is impossible 2) how to give my script the proper admin privileges required to write to the Local Machine Registry without using WScript.
If you are running this script as a VBScript custom action, then Windows Installer uses a custom VB script host. Unlike wscript.exe and cscript.exe, this host does not provide the WScript object, so any references to it will fail. You could choose to launch it as an EXE action via wscript.exe instead. I would not recommend this, at least not without reading my third paragraph.
Also, as a general recommendation, you should avoid elevating within a custom action, as doing so leads to the potential of multiple UAC prompts during a single installation. So to write to per-machine areas of the registry you should prefer using the Registry view, and thus entries in the Registry table. If you need custom values, note that you can specify property references in the view/table, and they will be evaluated when writing the values.
If you absolutely need to write to the registry through a custom action, consider carefully the rollback scenarios, and prefer to use an action that is "deferred in system context," as this kind of custom action will have access to administrative privileges if the installation is per-machine. Note that deferred custom actions do not have access to most properties.

InstallShield Internal Error 2769 - Error 1001 during installation

Sorry for a long question but I have to put as many details as I can to help you understand this issue better.
I'm using an msi installer created by InstallShield 2012, it runs properly on most computers but on some I get the generic 1001 error and upon clicking OK on that error everything rolled back. To troubleshoot, I ran the following code to generate the debug log from the installation
Setup.exe /v"/l*v \"C:\log.dat\""
The debug log shows error 2769 with a custom action xxxx.install did not close 1 MSIHANDLES.
While googling about this issue, I see a lot of people having this exact same error and most of the suggestions come down to check what your custom action is doing because it is the one that generates this error.
Here are what I've done to troubleshoot and isolate this problem so far:
Open up the InstallShield project and look at the MSI deubbger, I noticed the custom action names in the log is part of the custom action InstallShield uses to install a Window Service as part of the installation.
I look at how this service is installed, it turns out to be a C# executable that InstallShield invokes as a .NET Installer Class to install the service under the component's setting.
As per what is .NET installer class? you can look at the note below from InstallShield.
Fearing something is not right with my custom action code, i put in some debug logging and run the whole installation again, I still receive the same error but I do not see any exception being logged. The service actually created successfully and i can even run it as long as I don't click OK on the 1001 error which will trigger the roll back and uninstall this service.
public ProjectInstaller()
{
try
{
using (StreamWriter w = File.AppendText("c:\\log.txt"))
{
Log("start installing", w);
}
InitializeComponent();
using (StreamWriter w = File.AppendText("c:\\log.txt"))
{
Log("End Install", w);
}
}
catch (Exception ex)
{
using (StreamWriter w = File.AppendText("c:\\log.txt"))
{
Log(ex.Message, w);
Log(ex.StackTrace, w);
}
}
}
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
//
// serviceInstaller1
//
this.serviceInstaller1.Description = "Healthcare Platform Service";
this.serviceInstaller1.ServiceName = "psService";
this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1,
this.serviceInstaller1});
}
Based on my troubleshooting so far, I don't think the error is within the custom action code. However, that really leave me hanging because I don't really know what cause the custom action to fails; it looks like something did not close out the msi handles but this is really a black box to me.....
So any idea what this might be?
How can I further delve down to figure out what the heck went wrong with this customer action _502E509F9B6F6675DFF9C310662BC1B5.install ?
Below are the custom actions sequence.
*EDIT:
I found the link that talks about the similar error I have... however I verified my custom action doesn't have any parameter and that based on my verbose debug log I see all the path are properly resolved.
**EDIT: Add custom action sequence screenshots.
The error is a generic one resulting from the infrastructure of installer classes not working. This is mostly a black box developed for Visual Studio setup projects. It uses a C++ Dll call to ManagedInstall which then loads a framework version, locates your assembly, instantiates your class with reflection and then calls the Install method. InstallUtilLib is architecture specific, and a mismatch between it and your managed code and the framework version will cause errors. If this happens only on one machine it might be this mismatch, or maybe that machine is broken in some way regarding that service.
That's just information that may help. However, if you have an actual InstallShield 2012 then you don't need installer classes at all. They were created for Visual Studio setups, and are not needed for practically every other MSI building tool that exists because they have built-in support for the MSI ServiceInstall and ServiceControl tables.
To quote Jerry MaGuire... Stop, you had me at 1001. InstallUtil managed custom actions must be avoided at all cost. The first action is to eliminate the need for the custom action by using native windows installer capabilities. If the custom action is still needed the second action is to refactor using Windows Installer XML (Wix) Deployment Tools Foundation (DTF). Is a far better pattern for integrating managed code with MSI. It works very well with InstallShield.
IMO, InstallShield should have never made it so easy to wire up an InstallUtil custom action. They undoubtedly did so to satisfy customer requests and to ease migration to InstallShield but at the huge sacrifice of quality standards.
I really need to see where this is sequenced. If this is deferred and before install initialize or after install finalize, that could be an issue.
Hmm,
This is rather interesting... I found a Microsoft link that talks about the Event Source
By default, if I uninstall my software the event source will get removed as well. For some reason that is not the case I see, i think it isn't removed because the file to which EventMessageFile points to was being used/uninstall?
So reading the article above I went and remove out the registry manually found under
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\MyProgramName
The installation no longer gives me 1001 error after I remove the above registry. It looks like InstallShield tries to install a window service but upon looking at this registry it deems the services already exists (even though it isn't).

Use admin privilege on WIX custom action

Glytzhkof: This question relates to a common problem for new MSI users, the belief that one needs a custom action for something MSI supports natively by a feature they are unaware of.
In this case the IniFile table in MSI that allows merging, rollback and updating of shared Ini files on the system. All updates to Ini files should be performed via this table since you get a lot of features for free and you will be in line with all MSI guidelines.
Always avoid custom actions if there is a way to achive the same effect by native features.
I have a c# customaction on my WIX msi.
When msi start require admin account and all works fine excepted for my customaction, it can't write inside programdata folder.
I have set impersonate="no" according to wix reference:
This attribute specifies whether the Windows Installer, which executes as LocalSystem, should impersonate the user context of the installing user when executing this custom action. Typically the value should be 'yes', except when the custom action needs elevated privileges to apply changes to the machine.
But does not works.
This is my custom action configuration:
<CustomAction
Id="MyCustomActionCA"
BinaryKey="mycustomactionDLL"
DllEntry="MyCustomAction"
Execute="immediate" Return="check" Impersonate="no" />
<InstallUISequence>
<Custom Action="MyCustomActionCA" After="CustomDialog1">NOT Installed</Custom>
</InstallUISequence>
MyCustomAction is launched after CustomDialog1 is closed, and this is OK, my code run fine but in MyCustomAction i have a line of code like this:
File.WriteAllText(mypath, mycontent);
mypath refers to ProgramData folder and throw an exception becouse user can't write inside it.
There seems to be two problems here as far as I can tell:
The first is that the CA needs to be deferred if it's impersonate="no", and it's not clear that you've done that.
The second problem is that ProgramData is a user profile notion. For example it's in C:\Users\ on my system. If you run with impersonate="no" you're running with the system account, so it's going to try to write to some weird (non existent) C:\Users\System folder.
You might have a design issue here in that you say you need elevated user privileges to write to that folder, but you can't elevate unless you are deferred with system account privilege. You may need to describe the exact problem you're trying to solve to see if there is another way.
Check this thread for information on how to use MSI's built-in support for writing to INI files:
Wix modify an existing ini file
Some further reference:
http://wixtoolset.org/documentation/manual/v3/xsd/wix/inifile.html
Best option: redesign your application to write this ini file as a per-user file (any user can write to his own user profile) on application launch from internal defaults in the exe itself. Solves the deployment problem too.
As to what is happening in your deployment context: Windows installer runs only part of the installation as LocalSystem - what is referred to as the installation transaction. The rest of the setup is run as the user who launches the MSI - and this includes the entire user interface sequence which is where you have added your custom action.
Try setting the action deferred, schedule it prior to InstallFinalize and set impersonation to no.
With regards to per-user data management, see this post on a slightly different topic for a way to handle per-user data that needs management between deployed versions of the application: http://forum.installsite.net/index.php?showtopic=21552

Installer options available in application?

I have a basic installer with an option to install for all users or only for the current user. Based on this selection I copy several files in the common app data or the local app data.
My question is, how do I let my application know where have these files been installed. And I don't mean providing a hard coded path but more like providing the ability to choose between Environment.SpecialFolder.LocalApplicationData (Single User) and Environment.SpecialFolder.CommonApplicationData (All Users).
You should send the ALLUSERS custom action data to a installer class using the installer Custom Actions.
Here is a great custom action installer example that uses Regasm to register .NET assemblies.
Once you have your installer custom action and custom action data - you can store it somewhere where your application can retrieve it - either in the registry, application config, or to a fixed location on disk.
An easy way to figure out which folder your data is in from your application is to check the LocalApplicationData, and if something is there, use it, else check the CommonApplicationData folder.

Embed individualized code into ClickOnce setup.exe?

I know that it is possible to pass in parameters via URL to ClickOnce apps launched online. However, most users downloads setup.exe and launch it from their machine. Is there any way that I can re-write setup.exe at download, insert a code (let's say the user's email address), and then have the app launch with knowledge of the code? Assume that we can somehow re-sign setup.exe so that it is legit.
Assume .NET 3.5.
Update The goal here is to pass on either email address and/or referrer information to setup.exe so that even when the user runs the installer from a different machine and a different ip we can figure out who did the referral.
Update 2 Assume .NET 3.5 SP1, does it help? Apparently one can now pass parameters to .application while offline. Is it possible to embed parameters into the setup.exe so that it calls .application?ref=someone right when setup.exe is run?
Well, if your goal is to embed a customer id (email, code, etc) into the exe, the easiest way I can think of is using the IPropertyStorage and IPropertySetStorage interfaces. If you are feeling brave, you could call methods directly on IPropertySetStorage via p/invoke, or you could go the easy route and use Microsoft's prepared COM wrapper, which is called dsofile.dll.
Note that while dsofile is intended for office documents, it does indeed work on any file - including .exe files - you are just stuck with the pre-defined property names. Why not throw your customer id into something like the .Comments property. Just do it in such a way that you can parse it out again.
Here's a sample:
var doc = new OleDocumentPropertiesClass();
doc.Open(pathToFile);
doc.SummaryProperties.Comments = "joe#test.com";
doc.Save();
Of course, you need to first copy it to a temp location, and some time after the user downloads it you'll want to delete it.
You can bundle dsofile.dll with your application and register it as a dependancy and use it in your installer to read the property back out. Or if you can p/invoke the IPropertyStorage without it, then you won't have the dependancy.
The other thing to look into would be using the extended file properties that are read by the Shell32.dll. I just haven't been able to find a clean way to write them easily. If you go this route, please share how you wrote the properties to your .exe.
Have a look whether InPlaceHostingManager class can help you in this case. It won't probably do exactly what you have asked for. But may be able to help...
Any ClickOnce application based on an .exe file can be silently
installed and updated by a custom installer. A custom installer can
implement custom user experience during installation, including custom
dialog boxes for security and maintenance operations. To perform
installation operations, the custom installer uses the
InPlaceHostingManager class.
Walkthrough: Creating a Custom Installer for a ClickOnce Application
EDIT
I am not sure whether you could achieve what you want exactly in the way that you have described in the question. Check whether these threads help you.
Accessing Local and Remote Data in ClickOnce Applications
How to include custom data files in ClickOnce deployment?
How to: Retrieve Query String Information in an Online ClickOnce Application
How would you imagine to "rewrite" setup.exe at download? if instead of opening your application with the provided link (url) users are downloading the file locally directly from the network share, you can't intercept this.
I would try to play with permissions and have the users to execute it from the link provided to them, but unable to connect directly to the share or web address and download it. Not sure this is possible anyway.
You can try embedding that information as a resource into the exe.
Here's a c++ example of updating a resource of an exe. http://msdn.microsoft.com/en-us/library/ms648008(v=vs.85).aspx#_win32_Updating_Resources
You should combine approach by Charith and Josh - essentially, configure your web server so that you can generate a new setup based on URL parameters. Use custom installer to read from the referral information from resource for setup.exe. Check this link for how to manipulate resources for a native application in C# - you have to write to resource file while generating setup and need to read it from your custom installer.
Yet another way of generating custom setup would be to build your executable or helper assemblt from command line embedding the necessary information. And then build the setup from command line tools (see http://msdn.microsoft.com/en-us/library/xc3tc5xx.aspx). It appears to be quite cumbersome and will take long time to generate the custom setup as opposed to modifying the resource of already built setup.
A completely different approach would be to email the unique referral code (registration code) whenever user downloads the application. In the setup (or application), use custom installer to prompt user for this code. If the setup is invoked via URL then the code will be available from there and in such case Custom Installer need not ask for the code. The email that you send when user download the setup should inform user to preseve the code into some text file along with the setup file.

Categories