Adding folder navigation to an msi - c#

I am creating an MSI package for a windows service, untill recently i have always just performed the minimum using the installer class and then manually edited the app.config file afterwards. However, i need to make it easier for the users to carry out an installation themselves and this has meant replacing manual configuration with prompts at install.
I have managed to add a custom action to display a textbox and prompt for a folder destination, as well as ammending the installer to create the folder using the filepath entered into the text box, but i would prefer it if the filepath could be selected by browsing to the folder.
Can anyone suggest a good tutorial or steps to do this, as i am struggling.
Thank you

This would mean a redo, but you could use WiX to build your installer.
It has built in support for everything you need, and I don't think you would need any custom actions at all (unless there is something special you don't mention).
It does have a bit of a learning curve, however. Even if you don't go with it this time, it would be worth looking into for a new project.

Related

Microsoft Visual Studio Installer Projects - how to provide restart prompt to MSI executed with Process.Start(), but without Repair option

The problem is following: I have my custom uninstaller called before MSI uninstall. After shutting down my application properly it calls msiexec to use Windows Installer to uninstall MSI.
It's done by executing something like "msiexec /x{PRODUCT_CODE} /promptrestart".
And here is important thing - if the system is not restarted after uninstallation, and then the user installs the app again, some of its files will be deleted after next restart, so it's not acceptable. The restart is required, however, I need prompt, automatic and unconditional restart is evil and should never ever be used.
So, the invocation above displays STUPID "uninstall / repair" dialog. I do not want it. When I use "msiexec /x{PRODUCT_CODE} /qr /promptrestart" - then it uninstalls nicely, however it refuses propt for restart afterwards.
I have read about setting ARPNOREPAIR property.
But the idiots who gave that answer wouldn't care to say WHERE and HOW that property could be set. Even... Where the property belongs, it's the property of what? MSI file?
Then, maybe is it another way to achieve this, like invoke the prompt for restart from my code, but... how? The uninstaller should remove all my files until that moment. Maybe it's possible to execute a kind of script after the uninstallation process is complete?
(One more problem, the Windows Installer doesn't delete ALL files and directories created by my app. It would be nice if I could execute some code to clean up better.)
UPDATE
I see 2 paths ahead: make a script to be run once the uninstallation ends (like using Registry or Task Scheduler or IDK), use Win32 API to modify MSI file, because AFAIK it's possible to change its properties that way.
Questions: Some questions first.
Restart Manager: Are you familiar with the Restart Manager feature of MSI? Intended to help shut down and restart
applications without the need for reboots. I would give it a quick
skim? I think this is your real solution?
Alternative MSI Tools: There are many tools available for creating MSI setups. This link also contains further links to a summary of the shortcomings of Visual Studio Installer Projects.
Using the free, open-source WiX toolset - for example - you can change MSI dialogs. Github sample. SO question 1. SO question 2. And here is the official WiX toolset site.
Adding my own answer from SO: Changing text color to Wix dialogs (please do skim)
I am not familiar with how to change dialogs in Visual Studio Installer Projects. Commercial products Advanced Installer and Installshield can certainly change dialogs.
Services: What is the nature of the product you are installing? Does it has a lot of services for example? Services can be shut down and restarted via MSI tables if you use a proper tool to build the MSI.
REINSTALLMODE: Do you use a custom REINSTALLMODE for your setup? (some settings can cause more reboot prompts).
Custom Uninstaller: How do you invoke that custom uninstaller of yours? Manually or do you register an uninstall command line with Add / Remove Programs? (this latter approach is not recommended).
ARP Applet vs MSI Dialogs: The ARPNOREPAIR property is set in the MSI itself - in the property table. It affects only what is seen in Windows' Add / Remove Programs applet (ARP = Add / Remove Programs), and not what you see when your MSI is invoked via command line. Then you see the dialogs defined in that MSI itself (which can be changed - not entirely trivial to do).
ARP / Add Remove Programs Applet: A quick review of this applet below:
Hold Windows Key and Tap R. Type: appwiz.cpl and press Enter. This opens the Add /Remove Programs Applet.
Select the different package entries in the list to see different settings for ARPNOREPAIR, ARPNOMODIFY, etc...
If ARPNOREPAIR is set in the MSI's property table then the Repair entry is missing.
If ARPNOMODIFY is set in the MSI's property table then the Change entry is missing.
If ARPNOREMOVE is set in the MSI's property table then the Remove entry is missing.
If the special ARPSYSTEMCOMPONENT property is set, then the MSI will be missing from ARP altogether.
Links:
In-use files not updated by MSI-installer (Visual Studio Installer project)
So, there is an "ugly hack" which solves the exact problem:
First - we need an executable, that isn't affected by the installer. This one is easy, we just copy that one installed exe to a TEMP directory and run it from there.
The next step is to that file must wait unit the uninstall phase is done. There are a couple of ways of doing so. You can observe the installer process, you can observe the file system if main program file is deleted. Considering the pace of common installer operations, polling once a second is a good enough option.
The next step is optional - you remove remaining files created by application, empty directories and such.
The next step is reboot itself, MessageBox.Show() from PresentationFramework is fine to ask user, when user answer OK or YES, then reboot itself can be performed in many ways, I use ExitWindowsEx() from user32.dll since it's probably what msiexec calls internally.
Here's example code:
if (MessageBox.Show(RestartPromptMsg, "", MessageBoxButton.OKCancel, MessageBoxImage.Exclamation) == MessageBoxResult.OK) {
NativeMethods.ExitWindowsEx(
NativeMethods.Flags.Reboot,
NativeMethods.Reason.MajorApplication | NativeMethods.Reason.MinorInstallation | NativeMethods.Reason.FlagPlanned
);
}
Of course some parameters could be passed to our special clean up executable, so it could do some extra things, like skip the restart prompt if it's not really required.
The last step is to delete our executable itself. It's easy, but it's tricky. Again I hope my example code would help:
var cleanUpTempPath = Path.Combine(Path.GetTempPath(), CleanUpExe);
File.Copy(CleanUpPath, cleanUpTempPath, overwrite: true);
Process.Start(new ProcessStartInfo {
FileName = "cmd",
Arguments = $"/c (\"{cleanUpTempPath}\" -purge \"{InstallerDir}\") & (del \"{cleanUpTempPath}\")",
UseShellExecute = false,
CreateNoWindow = true
});
We use cmd.exe special feature, the power of & and (). Commands separated with & gets executed when previous command exits. So when our clen up exe completes, it's gets deleted by the same cmd instance which called it. Remember to quote all paths, they can contain spaces. Remember to enclose a command with arguments in parentheses, because otherwise the & operator would be seen as a parameter to the previous command, not the cmd.exe.
I tested it in my big production application and it works as charm. The code examples don't work when just pasted, if you're looking for complete code, just google for it, there are plenty of working examples on pinvoke.net and StackOverflow.

Windows installer forbid certain install locations

Have an msi file that is run by the user manually. They need to be able to choose the install directory in most cases however we need to forbid certain install locations. E.g. Installing it to the root directory C:\ will cause all kinds of problems, so we need to either overwrite that decision (i.e. overwrite C:\ with C:\Program Files (x86)\xxx) or pop up with an error. Is there some way I can enforce this?
The msi in question has custom actions already however there doesn't seem to be a way to edit the install location from there.
Alternatively, the msi in this case is wrapped up in a WiX bundle so if we can forbid certain directories from there that would also be good. Cannot find a way to do this either though (only know how to edit the default with <Variable Name="InstallFolder" ...>)
Only other solution I can think of would be rather horrible: make a separate application that selects a directory that then runs the installer with acceptable directory.
Can this be done either through an msi or a WiX Bundle?
I am using the "Visual Studio 2013 Installer Projects" extension to build the msi.
As a contrary view:
In general this is a bad idea. In most cases the correct answer will be to install the application code to the appropriate Program Files folder (64-bit or x86) and the data files to data locations and so on, and the user should get no choice. It is not clear to me that a choice is a good idea when (for example) the Windows Certification rules say that your code must go to the Program Files location, so just do it right. Users simply care that the installed application works correctly, and if it fails when installed to some locations then the answer is to either 1) Fix the application so that it works or 2) use Program Files and give the user no choice.
Also, if you are using Visual Studio Installer projects then you can't write custom actions to do this because they all run too late to change the install location. You seem to have discovered this already. But you CAN hide the browse folder dialog and install to the default correct location.
The other issue is that it's not clear how you would define an "allowed" location. If it's not C:\, then can it be D:\SomeOtherLocation? Can it be an attached USB drive? Can it be a network share such as \\Servername\share? A mapped drive to a network share? There are likely to be any number of chosen locations that will fail the install or the app when it runs, and I don't think there can be a useful list of what's allowed. On top of that, let's say you have a 32-bit install and the user chooses the native Program Files folder on a 64-bit system, then it won't even go there - it will be redirected to the Program Files(x86) location. Finally, it's not clear what you do in silent install mode assuming the user specifies a location on the command line, it fails your test, then the install silently fails (because silent means silent, and the install might be unattended).
In other words, just install to Program Files and have done with it.
Custom Action: This will be short. Will check back later. I can't say I have bothered implementing this recently, but a custom action can certainly inspect the installation location and abort the setup or halt it - if the path selected is found to not be satisfactory. It should also be noted that MSI actively resists installing directly to the root of C:\ and stuff like that due to the way the Directory table is implemented.
GUI: I guess one way would be to run a custom action when the user clicks the Next button in the setup's destination path customization dialog which then does "whatever you want" in terms of checking the path, and then reports any errors. This involves a DoAction event hooked up to the OK or Next button on the path customization dialog.
Silent Mode:You can also hook up the same custom action to run in silent mode (or another custom action calling the same path check function) - to account for the fact that an undesirable path could also be specified for a silent installation. In that case the custom action should abort the setup after writing into the log file, instead of reporting the path problem to the user - which is what you would do from the dialog event mentioned above - obviously.
Github: I do not have WiX code for you to implement this available on this computer. I would hit github.com and search for other projects that use WiX - you will probably find something quickly - no money for nothing and WiX for free.
Based on the users being able to manually install it (and hence using the UI sequence), it might be easier to:
In the InstallUISequence, sequence the LaunchCondition action to just before the ExecuteAction action.
Then in the LaunchCondition table, add a condition like so:
Condition:
TARGETDIR~<<"C:\Program Files\"
Description:
You must install to the Program Files folder
What we're saying in the condition is:
If the TARGETDIR starts with "C:\Program Files\" (therefore the user can install anywhere under this folder) continue with the install. Otherwise throw an error.
Rather then preventing certain locations, I'd probably just enforce the Program Files folder as a best practise.

Creating a C# installation package that backs up the previous version before updating.

I am trying to create an installation program that will backup the previous version of a C# program before updating it. I'm using VS 2015, and have looked at the installer, advanced installer and InstallShield LE. I don't really know what I'm looking at, how to use custom actions, pretty much anything. Any advice or help would be appreciated.
Let me explain a few things first.
Backing up the previous version
Firstly,you need to identify your current application's installation folder.You can create a registry key to save where the application is installed(You need to do this in the first-time installer of your application).For that you can use Registry.LocalMachine.CreateSubKey(#"SOFTWARE\ProductName\appPathHere").Then,in your new installer,you can read the registry key to get the path of the application.Then,what you can do is create a ZIP of that path/folder.For that you can use :
System.IO.Compression.ZipFile.CreateFromDirectory(pathofApp, zipFilePath);
This will backup the current application.You can even modify the file type/extension to give it your custom type/extension.
Installing the application
Read the registry key to get the path of the installed file.Delete it using System.IO.Directory.Delete(path, true).You can ZIP all your files and then make your installer extract it to the specific location.You can simply use :
System.IO.Compression.ZipFile.ExtractToDirectory(zipPath, extractPath);
Creating the installer
I suggest you create a winform or WPF application,design the UI and implement the above methods.
This is not the ideal way but it will give you an idea on how to get it done with basic knowledge.Hope it helps you

What is the best way to publish multiple versions of the same ClickOnce application?

I have a c# ClickOnce application that I need to be able to publish multiple times for OEM purposes.
The way I understand it now is that publish settings are located in the .csproj file.
However, this is inconvenient in the case where I want to publish multiple versions.
for example, Company A needs totally different icons, start menu location, product name etc. from Company B, but the assemblies need not be renamed.
Here are a couple approaches/questions that I can think of to solve this issue...
1.Is there a way to create a separate publish settings file to use during build time?
2.Can I edit specific publish settings (like Start Menu location, etc) at build time with MSBuild.exe? I think this would be ideal...
e.g.
MSBuild.exe project.sln /target:Publish /property:edit-project-publish-settings-here
3.Maybe create a 2nd .csproj file? (Would prefer not to do this...)
Please share your thoughts as to the best approach, or any other clever ways to make this happen. Thanks!
I wish I could give you some brilliant solution, but personally I would probably go with option 3.
I mean, its pretty simple, the changes should be pretty static and it will be difficult(ish) to totally screw it up and deploy the wrong changes to the wrong company.
If you copy the .csproj in your project folder, it will reference all of the same source files and you can just change the executable name. Create another VS solution and you can reference the copied .csproj and get rid of your first one so that you can publish two separate versions.
This isn't ideal for ClickOnce however.
If you use a Singleton object that specifies the "mode" (Company A, B, C, etc.) you can easily store that in the app.config (or another xml file). Then just re-publish your ClickOnce Application but copy the correct version of your configuration file in so it gets shipped with the build. This way, you don't need any additional csprojects Just include all of your icons and set them at run-time on App Start based on your Singleton object.
I found that you are able to edit certain properties using MSBuild.exe like this
MSBuild Solution.sln /target:publish /property:ProductName=ProductA\;Publisher=CompanyA\;ApplicationIcon=companyA.ico
I found another useful post on modifying.csproj files programatically with .NET code. (This would only be needed if you're modifying things that are deeper than just the project properties specified in the ClickOnce documentation below)
The MSBuild documentation here was also useful -- especially under Publishing Properties

How to force deployment project to update files during installation of newer version?

I have a Deployment Project for my VS2008 C# application. When installing a new version of my application I want to make sure that files are updated with the files contained in the installer.
I want my installer to automatically remove any old version of the installed application. To do that I follow this procedure (also described here):
Set RemovePreviousVersions to True
Set DetectNewerInstalledVersion to
True
Increment the Version of the
installer
Choose Yes to change the ProductCode
For the assemblies I make sure the AssemblyVersion is set to a higher version:
[assembly: AssemblyVersion("1.0.*")]
Everything is working as intended except for my configuration files (xml files). Since I cannot find a way to "version" these files I cannot make sure that the files are updated if they have been modified on the target machine.
Is there any way to do this and how?
UPDATE: I have tried the approach/solution/workaround found here
Include the file directly in a
project with the following
properties: "Build Action -> Content
file" and "Copy to Output Directory
-> Copy always"
Then add it to the deployment
project via Project
Output->Database->Content Files
Unfortunately it did not make any difference. The behavior is exactly the same.
Add the following property to the Property table of the MSI:
Property REINSTALLMODE with Value amus
Note: This will cause all the files in the MSI (versioned and nonversioned) to overwrite the files that are on the system.
If you're willing to use Orca (there may be another way to do this method, but it's the only one I know of) you may be able to set up RemoveFile directives.
See here for a typically light-weight MSDN document -- could be a starting point.
http://msdn.microsoft.com/en-us/library/aa371201.aspx
Alternatively you could always create a very simple bootstrapper executable that simply calls "msiexec /i REINSTALLMODE=oums" (or whichever command-line switches are needed). Call it setup.exe if you like...
Better option long-term may be to switch to InstallShield or similar -- VS2010 includes a light version of IS and I believe they're moving away from vdproj projects.
Have you tried the approach/solution/workaround found here?
Include the file directly in a
project with the following
properties: "Build Action -> Content
file" and "Copy to Output Directory
-> Copy always"
Then add it to the deployment
project via Project
Output->Database->Content Files
I may be incorrect here, and therefore I am exposing myself to down votes, but here goes anyway!
I believe it is on purpose that config files do not automatically get overwritten; the principle there being that you would not normally want your application's configuration overwritten when you install the new version of the program... at least not without numerous warnings and/or chances to merge configuration first.
Having your application configuration get overwritten by an updated version of a program could make for a very upset end user (in this case, web site admin).
Further, I know that sometimes, the developer may be the person doing the deployment. In such a case, this behavior might not seem so logical when deploying a single site to a single server; but when roles are split and/or you have multiple servers with different configurations, this can be a life saver.
You need to include the new version of your files in your custom installer and manually install these file during Custom Install routine is called
This must be applied to any file that does not have version that can be tracked by the installer.

Categories

Resources