I trigger an c# application by an custom action:
On failing condition, my application tells Install Shield to abort the installation process using an exit code:
static void Main(string[] args)
{
if(false)
{
Environment.ExitCode = 1;
}
}
Using this approach, Install shield´s setup displays an error message like expected:
How can I overwrite that error message by a custom text?
Reading between the lines here, it appears your custom action launches an EXE. If that is so, there is no way to do what you ask. You could show a message from your EXE before returning a non-zero exit code, but then Windows Installer would still show the Error 1722 message.
If you can instead run a function from a DLL, you have more options. Instead of returning errors, you'd be able to set properties (assuming this is an immediate mode action), and could use those properties to do further things, such as show another dialog, or exit the installation without the Error 1722 message. I don't think all the necessary configuration options are available in the limited edition - you certainly cannot edit dialogs in LE - so to do all of that, you would have to change to a more capable tool (including the Professional edition, or options from other vendors).
Related
I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability
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).
I'm developing a console application in C# that should only run once on a specific day/time each week. When I modify, publish and install the application, the installer always runs the program. I had to add an if statement at the beginning of my code to compare the current date/time that I have to manually change any time I recompile/republish just to make sure the program doesn't run when I install the new version. While that works, it seems silly to have to do that.
I'm sure I'm missing an obvious setting somewhere, and I'm just waiting for some hero to give me my Head-Slap-Moment-of-the-Day.
Thanks!!
(Edited to more accurately describe the schedule.)
If your problem is the inability to control whether Install or publish process will run the program - you can have your main expect a certain command line parameter to run, without it - it will simply exit.
If the program is run via a scheduler, have the scheduler pass in the correct argument.
For example
public static void Main(string[] args)
{
if (args.Length == 1 && args[0].Equals("NOW_IS_THE_TIME"))
{
// Run
}
}
You could also, save a flag in your settings indicating if it's the first run or not, like proposed in Install ClickOnce without running
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 made a windows service & add project installer.in which only contain this code.
System.Diagnostics.Process.Start(#"C:\Windows\system32\notepad.exe"); inside the timer tick event & interval is 60 sec.i just wanted to try to run Windows service.
1st-serviceProcessInstaller1 i have been changed its account setting as local system.
2nd-serviceInstaller1 in this case i have been changed its start up type as Automatic.
then i create a setup add another project then right click add project output then add primary output then press ok.
then go to Right click on project->view->custom Action->right click on Install->Add custom Action->select Application folder & add primary output.the same thing done for all the remaining options like commit,rollback,uninstall.
after that i build the setup it build succesfully then i install the setup it installed properly into program file n create one .exe file n one Instalfile.
but problem is that when i search the service into "services.msc" the service is not there.
means service is not showing there.i tried but not getting the ans.plz help me to solve this problem.
Not the answer to your original question, but
Starting an application with a GUI from a service is a bad idea (tm) and won't work in the majority of cases
Check what kind of Timer you use. .Net provides 3 Timer classes, not all of them work in a service (because they depend on a window's message loop, iirc)
To test any service: Why don't you go for the installutil binary first (bypassing the setup project)? You find it in your framework directory, for example in "C:\Windows\Microsoft.NET\Framework\v2.0.50727".
I'd suggest trying to install the assembly manually to see if you get any kind of error message.
Just open a Visual Studio command prompt and run InstallUtil.exe [YourService].exe. At least you'll know if it installed properly.
Even better option is to use the command line tool sc.exe
Try "sc create /?" on command line and see its options.
You can then use "sc start " , "sc stop " & "sc query " to control service. You can use sc.exe to send custom command to the service. Check windows event log for errors related to service installation. It may be .Net version used to create it is not on target machine.