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).
Related
I'm using an unmanaged C# dll (using DllExport and dotnet framework 4.0) to add UI capabilities to an NSIS installer, but basic controls constructors throw a font '?' cannot be found. exception on windows 7. Code example:
[DllExport]
public static void CreateRichTextBox()
{
try
{
RichTextBox myRichTextBox = new RichTextBox();
MessageBox.Show("RichTextBox created on windows 10");
}
catch (Exception ex)
{
MessageBox.Show("RichTextBox contractor failed on windows 7 with " + ex.Message);
}
}
So after some digging, I got to the conclusion that this happeneds because of a function used by NSIS that messes internal paths for the Dotnet: SetDefaultDllDirectories.
The function was added for security reasons explaind by the NSIS team:
Using SetDefaultDllDirectories we can globally change the behavior
of LoadLibraryEx so that it only looks into the System32 folder and into any
directories explicitly added with AddDllDirectories.
I only guess that this is a Gdiplus bug that was eventually solved but I still wonder if someone knows of a workaround to make it work on windows 7?
The public Microsoft bug report claims the bug is in Windows (GDI+) and not technically a .NET specific issue.
Unfortunately MSDN says this about SetDefaultDllDirectories:
It is not possible to revert to the standard DLL search path or remove any directory specified with SetDefaultDllDirectories from the search path. ...
This means the only workaround is to not call the function in the first place. The only way to do that in NSIS is to use a hex-editor and modify the stubs in NSIS\Stubs. Search for SetDefaultDllDirectories in the relevant stub and change it to something like SetDefaultxxxxxxxxxxxxxx (replace as many characters as you want but the length has to stay the same!).
This of course means that your installer will be somewhat vulnerable to malicious .DLL planting attacks. You can try to restore the functionality somewhat with:
!include WinVer.nsh
!include LogicLib.nsh
Function .onInit
${If} ${AtLeastWin8}
System::Call 'KERNEL32::SetDefaultDllDirectories(i 0x800|0x400)'
${EndIf}
FunctionEnd
but the protection is slightly less effective this way and does nothing on Windows 7.
I'm trying to setup our CI build environment and having an issue.
First, I'm using VS and TFS 2012 so I can't use the *.12.xaml templates since those are for VS/TFS 2013.
Second, right now I'm configured to use just the defaulttemplate.11.xaml. Originally, I was using WebDeploy for the deployment method and that was working great. Since then, our web/server team has re-configured our test environment to use IIS Shared Configuration as well as DFS Replication to keep everything in sync.
Because of that, I'm no longer able to use WebDeploy (I passed this post over to the TFS admins, but they said no).
Is there a place where I can add some msbuild arguments, or a post-build event where I can send a *.cmd file with some arguments so I can get my code copied/deployed?
I've read Hanselman's (and everyone else that copied him) posts/blogs that say "if you're using xcopy, you're doing it wrong, etc...", but I believe in my case I CAN'T use Web Deploy.
Update:
So I thought I found my answer. Since the web deploy doesn't work for me, I found a workflow activity called CopyDirectory that sounded exactly like what I need.
I went through the process of updating my default template to add this additional step to the build process, which by the way, does NOT work very well. After adding the step, saving, etc, the step doesn't ever show up in my build output. I gave up for awhile to go see if I could do this on our Jenkins build server, got some different errors over there so I came back to TFS to make the changes and commit. Since the CI was still setup in TFS (granted, failing), I noticed that a build got kicked off when I made my commit. I decided to watch for awhile and IT FINISHED SUCCESSFULLY! Woah, all right. So I checked through the build logs, and find out that it threw a WARNING saying "failed to copy. Ensure the source directory exists and that you have the appropriate permissions".
Well, since I just entered this value incorrectly, no big deal, just change to the correct BuildDetail.DropLocation, and we should be golden.
WRONG, after building again with my changes to the source and destination values, I come to find out that since I'm trying to deploy my files to a different domain, it still fails.
Oh, and in addition to that, YOU CAN'T PASS CREDENTIALS TO THE COPYDIRECTORY STEP! REALLY! Phew, I found some documentation though, it says "give the tfs build service/account permissions on the domain that you want to copy to. Well, that would be great, if my server team would allow that, but they don't.
Back to square one...(this is going to turn into a blog about me complaining about TFS...)
I believe you can do it using robocopy. You will want to update your build template to include a new InvokeProcess activity. Set the activity's FileName to "RoboCopy" (include the quotes) and it's Arguments to something like the following:
String.Format(" ""{0}"" ""{1}"" /E /R:10 /W:10 /NFL /NDL ", BinariesDirectory, BuildDetail.DropLocation)
Of course changing the robocopy flags to your specific needs.
I don't think you can pass credentials into robocopy either though, so you might still be SOL there.
One possible alternative though is that because your admins won't give the TFS Build User (i.e. tfsservice) permissions on the destination box, you could change the TFS Builds to run as a different User that does have permissions on that box. To do this I believe you just have to log onto your TFS Build machine, go to the Services, find the Visual Studio Team Foundation Build Service Host 2012 (or something similar), and change the Log On As user from tfsservice to whatever user has permissions on the box that you want to publish to. Of course you will also need to give that user permissions to do everything else that the build system needs to do (download source code, etc.).
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.
Following the advice of Henk, I've created a Setup Project in VS10 with the aim of adding a custom action. This custom action will hopefully add an EventLog whilst running as admin (ie during installation) rather than having my app throw an exception on OSes with UAC.
Unfortunately, I don't ordinarily have access to an OS that uses UAC. The next time I do, I hope the installation will go smoothly.
With that in mind, is there anything in the below code which is obviously wrong?
using System;
using System.Diagnostics;
namespace EventLogCreator
{
class Program
{
static void Main(string[] args)
{
switch (args[0])
{
case "-i":
if (!EventLog.Exists("SSD Log"))
{
Console.WriteLine("Log not found, creating.");
EventLog.CreateEventSource("setup", "SSD Log");
}
break;
case "-u":
if (EventLog.Exists("SSD Log"))
{
Console.WriteLine("Log found, removing.");
EventLog.Delete("SSD Log");
}
break;
}
}
}
}
The output of this project is sucked into the setup project. I then have two custom actions:
On install with "-i" as an argument
On uninstall with "-u" as an argument
I'm not expecting a free code review, but I'm venturing into the unknown here, so I'd appreciate a heads up if I'm humping the wrong bit of trash.
PS I'm particularly concerned that I'm specifying the actual log name, but not an actual source. Will this matter?
You will probably be better off using the "EventLogInstaller" found in the "System.Diagnostics" assembly.
You can see a implementation of this when you create a custom component, then adding a event log component to the design surface, fill in the properties for the component, then click on the "Add Installer" link/command in property window. This will add a project installer component, which will contain a event log installer component.
The event log installer component is what you are looking for, basically it is a windows installer action that can be run when you create a windows installer package (MSI). All you have to do is specify the installer action in the "Custom Actions Editor" of your visual studio deployment project. There is quite a bit of information regarding custom actions in the MSDN library.
Also have a look at the following:
EventLogInstaller Class
Installer Tool (Installutil.exe) - msdn.microsoft.com/en-us/library/50614e95(VS.80).aspx
I can't remember or access the details right now but somewheren in that (horrible) UI for setup-projects there should be alist of 'standard' actions for, amongst others, creating an EventLog. That would be the safest way.
But you should be OK testing this w/o UAC. If it works, it works. A setup.exe runs as Admin
I'd like to release some updates for a WinForm program, but to date I have simply released an all-new compile. People have to un-install the old version and install the new version.
EDIT: I'm using an auto-generated InstalWizard. It preserves my file strucutre and places the [PrimaryProgramOutput] in a particular directory. I forget what this is called.
I bet there's a way to get around this, but I don't know what it's called. As you may guess, searches for "updates" "new version" "install" and the other obvious things I've tried have generated an impressive number of irrelevant results. >_<
I suspect this process has a particular name, which should point me in the right direction, but if it doesn't please link to a tutorial or something.
I see from the tags you are using C#. Visual Studio can create Setup projects for these kind of tasks. The setup projects als contain a property RemovePreviousVersion, which will remove a previous version if the versioning of your setup is correct and the GUID of the application stays the same.
See this link for more information:
http://www.simple-talk.com/dotnet/visual-studio/updates-to-setup-projects/
ClickOnce deployment is a great solution most of the time...
You can deploy to the web and when ever your users start the application it will check for updates and automatically update the application if there is a new version available.
It can also be configured not to update automatically but only to notify the user that there is a new version available and allow the user to control the update process.