Automate deployment of Excel AddIn - c#

I have created an Excel Add-in using AddIn Express .Net component. Business users install the add-in using the MSI provided by the build team. Everytime we make any changes to the product and provide it to business users, they need to manually uninstall existing Add-in and then install new one with the updated MSI.
I wanted to know if there is any way this process can be automated using some windows batch file, scriptcs or a small C# console program. Ideally, it should uninstall existing Add-in, wait for uninstallion process to complete and then install new AddIn.
I tried multiple options using Msiexec, scriptcs etc, but without any success so far. My main problem is once the existing add-in uninstallion process starts, it immediately starts installing new Addin, which then pops up standard windows message that 'Installation is already in progress...'
Any help would be appreciated.
Thanks

I answered already a similiar question where it seemed to help:
Windows batch file does not wait for commands to complete
Normally, when you have a batch file with two lines:
call msiexec /x {...} /qb
call msiexec /i "c:\myPath\myProduct.msi" /qb
it should work in the sense that the uninstall waits before install starts.
The "call" is important !
For uninstalls of previous versions you have to use /x {ProductCode to fill in} instead of /x "filename" . In each case using the product code is safer.
To be sure what happens, you can add a pause line between the two and at the end.
If it still seems not to work you have to loop until the product is really uninstalled, wait two seconds and proceed then with an install.
There are several possibilities to find out, if a program is still installed.
Most people would recommend a VB script as the easiest solution, at least these are most known.
Here is a VBS snippet from saschabeaumont for an uninstall call from another question:
MSI Install Fails because "Another version of this product is already installed"
It mainly finds out the ProductCode of a given productname (part), and starts uninstall, if it fits (be careful about partial matches). You can use it for the same thing and additionally copy the algorithm a second time to ask asynchronously, if the uninstall has already been finished (= product is no longer in list of installed products).
Of course it is possible in other script languages, namely JScript, Powershell and traditional programming languages.
It is difficult to do in pure batch scripts- for example you could test the ProductCode registry entry under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, if a product is installed. But only one disadvantage to mention: You have to consider the difference, if batch is started from 32/64 bit subsystem and/or MSI is 32/64 bit. Therefore I would advise instead using VBS instead of a batch (you can call it from the batch with cscript xxx.vbs).

A few things here:
First question is if you are aware of the xlstart folder in Excel which allows you to easily load certain addin files on Excel startup just by putting them into this folder. As far as I know you can't disable addins this way via the Excel GUI, you have to remove them from the xlstart folder to not load them into Excel.
Second issue is that you should update your MSI file to use a major upgrade so that it automatically removes the existing MSI whilst installing the new one. This would probably remove the whole problem that you describe.
Finally you should be able to use start wait msiexec.exe /i /qn File.msi to have your batch file wait for msiexec to return from the first msiexec call. Check Waiting for msiexec.exe to finish. Or you can try MSI Software Deployment Using Batch File.

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.

Can't build my service : Unable to copy file because it is being used by another process

I build a windows service in c# .net. I added Pre-Build and Post-Build event to automatically deploy my service on the build. But sometime I got this error :
Unable to copy file "[CompletPath...]\bin\Debug\Business.Data.dll" to
"bin\Debug\Business.Data.dll". The process cannot access the file
'bin\Debug\Business.Data.dll' because it is being used by another
process.
In the Pre-Build event i'm closing the service, killing all task that using file in the Debug directory and uninstalling the service. There is the code in the .bat that i'm running in the Pre-Build event :
SET executionPath=%~dp0
SET serviceName=%1
SET frameworkPath=%2
SET targetServicePath=%3
SET targetBinPath=%~4
set targetBinPath=%targetBinPath:~0,-2%
net stop %serviceName%
powershell -NonInteractive -executionpolicy Unrestricted -file "%executionPath%\unlockfiles.ps1" "%targetBinPath%"
%frameworkPath%\installutil.exe /u %targetServicePath%
Exit /b 0
On the post-build event i'm installing and starting the service, there is the code even if this is not the problem because i'm gettring the error on the build, so the post-build event is not executing.
SET serviceName=%1
SET frameworkPath=%2
SET targetServicePath=%3
%frameworkPath%\installutil.exe /ShowCallStack %targetServicePath%
net start %serviceName%
I'm not always having the problem. I usually have the problem the first time i'm building, i'm cleaning the solution, build again and usually it's working after this.
I would separate these processes if I were you. You don't need to uninstall the service in order to update the files.
I'm not a great fan of pre/post build events for much more than moving some files around after the build has completed.
I use xcopy /y /c "$(TargetPath)" "location to copy to"
If memory serves I didn't even actually have to stop the service in order to update the dlls, however you may need to just stop the service in the post build before doing the xcopy commands.
If I were in your situation I would start to employ and more team oriented solution.
I would spend some time setting up a Continuous Integration system using something like Team City (this tool is free for upto 20 build configurations. I REALLY rate everything from JetBrains.
Then each time one of your team checks in new source code for the service it will be detected automatically by the build system (Team City) and a new set of builds will be triggered.
You could have a debug build that runs your unit tests against the code, a release build that also then includes a WiX Project that also then builds an installer for the service.
Once those processes are completed you can configure it to pop the installer in a known location on your network and it can also then email all the developers in your team to notify them that a new version of the service is available for installation.
WiX is a VERY mature installation authoring tool that is used by Microsoft for a lot of their products. There is a lot of support out there for the tool set and will make your entire process much tighter, repeatable and predictable.
It will take some time to get all of this completed but it really is worth the effort.

Uninstall exe program in through cmd

I am writing an app to remove an old version of Qmuzik ERP software from a network and install the new version. I have used the Process methods in C# to execute the msi through a cmd command to install the new version. The command I use is:
msiexec /qn /i "MSI Path"
This works beautifully.
The reason I am doing this is because of the fact that there are more than one msi which has to be run in sequence. Using the process method I can watch the process and detect when it is done and execute the next msi in the sequence. All of this happens quietly on the users pc.
The problem is, before I can install the new version I have to uninstall the previous version. The old version was installed on to the machines using an exe setup file. I have tried converting the exe file to msi and using msiexec to uninstall through command line but it has no effect on the instances that has been installed with the exe and not the converted msi (which is the entire network)
Is there any Command that I can use in cmd to uninstall these instances that has been installed using the exe?
When you install a program in Windows a registry key for its uninstallation is created holding several values, among them is the UninstallString value which is the command line the Add or remove programs uses when you click Remove.
The parent key is:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
And these UninstallStrings usually have the following format:
MsiExec.exe /I{0826F9E4-787E-481D-83E0-BC6A57B056D5}
In order to acomplish what you are trying to do you will have to create a RegistryKey object, read the UninstallString value for the application you want to remove and run the command line.
More info here.
You can use revo uninstaller, not for its uninstallation features, but for its main view, that shows the registered uninstall string of your application.
But it's only a starting point. Every setup engine can have its own uninstall string. You have to find the correct one, if you are lucky enough for it to exists (try /quiet, /passive, etc. until you find the correct one).
Go to the FIle Path, looks for a .exe which you need to uninstall. In command prompt open the path of .exe located and execute "xxxx.exe --uninstall".

Deleting a service

I created an installer and custom actions for a service.
On first install I managed to install the service , but when I tried running the service I got
an Error :Windows installer service cant start Error 193:0xc2
I have tried deleting this service in myriad of ways, yet on install I am still getting an error message 1001 The specified service already exists.
I deleted the service directly from the registry , but this does not seem to have worked.
I have tried
sc delete [service] --> The specified service does not exist as an installed service.
The service is not showing up in the registry nor is it present in installed services.
Any other thoughts or options would be appreciated
I have used this commands in a bat file that runs every time reinstalling. Try this and see.
set path=%path%;%SystemRoot%\Microsoft.NET\Framework\vXXX
InstallUtil /u YourService.exe
InstallUtil /i YourService.exe
net start "Service name"
If you want to delete/uninstall/remove a Windows service, perhaps left from an incomplete installer, you can use the sc command from an Administrator control prompt:
sc delete [servicename]
"sc delete" Deletes a service subkey from the registry. If the service is running or if another process has an open handle to the service, then the service is marked for deletion.
EDIT
I have tried sc delete [service] The specified service does not exist
as an installed service.
Probably restarting the machine would fix this.
sc.exe stop serviceName
sc.exe delete serviceName
and reboot the your VM\PC
This complaint may be coming from the installer, not from Windows.
I had this same issue earlier this year, I installed a service from an MSI file, which fouled up somewhere along the line. I uninstalled the package and tried reinstalling through the installer and got the same message as you did, that the service already existed. Frustrating. It didn't show up as an installed package at all anymore, nor did it show up in the service list.
It ended up that the MSI file did not clean up after itself properly during uninstall, but that I could manually manipulate this database and remove the information myself using MSIZap.exe. You need the Windows SDK to get at this program. There may be other ways, but I don't know of them.
You will need the package guid of your installation package to remove it using MSIZap. In the same directory as MsiZap.exe, you will find another application, MsiDb.exe. Run that, point it at your MSI file, point it to some empty directory to store some exports select the "Property" table, select the "Export" radio button and hit the "OK" button. Open the "Property.idk" file that was generated by MsiDb.exe in any text editor. Look in your text for a line that says "ProductCode". The GUID that follows is what you will feed into MsiZap, brackets and all. Now you will simply (bwahaha... yeah right) enter:
msizap T {product code}
Where "{product code}" is replaced by the GUID you found. This removes all traces of your product from the MSI database in windows, which should shut the installer up.
I realize that all of this is a ridiculous pain in the butt. I don't understand why finding a product code is such a trial. But, I suppose if the uninstaller worked, you wouldn't have to do its job for it now. There really might be a simpler way to do all of this, but I haven't found one. Once I found something that worked, I was over it.
As a final note, what caused this error for me was leaving the services list open during uninstall. An uninstaller which isn't paying attention could ignore some exceptions and leave pieces of itself lying about. I was lucky, the poorly behaving uninstaller was my own. And by the way, to appreciate just how much garbage is left over from uninstallation, check out this article.
I'm not sure if I had the same problem that you had, but if so, I hope this helps. If you need clarification, ask and I'll update the answer. If I'm up a tree and this has nothing to do with your problem, I apologize.
Try with Powershell and Wmi:
(gwmi win32_service -filter "name='yourservicename'").delete()
Make sure services.msc window is closed. Sometime that messes up the service deletion. I am not sure if this will help, you should not have touched the registry!
Perhaps you tried to delete the service while it's running? In that case you might need to reboot to clear things up.

Categories

Resources