开发者

installutil bindingRedirect

I have a windows service that depends on a 3:rd party API

The API is already installed in the GAC on the client computer

There are several versions of the API (1.0.0.0, 1.1.0.0 etc)

My service works with all versions of the API

I use a bindingRedirect tag in the app.config file which works fine when running the service.

Problem is that the app.config file is not used when running InstallUtil so I get a binding exception when registering the service开发者_运维百科.

Currently I use "sc create" to manually register the service but is there a better way?

(without editing machine.config etc)


I just ran in to this, the only solution I could find is from https://connect.microsoft.com/VisualStudio/feedback/details/525564/installutil-exe-does-not-honor-app-config-especially-binding-information:

As a workaround, you might be able to make this work by modifying the InstallUtil.exe.config file to contain the binding information. The InstallUtil.exe.config is installed to %WinDir%\Microsoft.NET\Framework\\InstallUtil.exe.config where is the version of framework you're using.


I came up with another workaround to install service with binding redirects. As I have a lot of services, this is what I decided to go after.

  1. Change Windows installer to Console app and implement functionality to self install (using command line and ManagedInstallerClass.InstallHelper).

  2. Implement an installer class capable of executing command line in a completely separate assembly, for example CommandLineInstaller.DLL. CommandLineInstaller.DLL shall implement methods Install/Uninstall/Rollback identically - execute a command line with parameters such as: FileName, WorkingDirectory, Args, WindowStyle.

  3. Modify setup project to deploy both 1) service and b) CommandLineInstaller.DLL

  4. Modify setup project custom actions: instead of running actions of service, run actions of CommandLineInstaller.DLL. CustomActionData property for Install action will look like: /FileName="[TARGETDIR]MyService.exe" /Args="/install" WindowStyle="Hidden"

    Action configuration: Install: myservice /install Rollback: myservice /uninstall Uninstall: myservice /uninstall

No need to write Commit, AFAIK.

Now, setup project will execute CommandLineInstaller.DLL installer in its own process. And then CommandLineInstaller.DLL will in turn launch MyService.exe in its own process with bloody binding redirects as they should be.

PS MyService.exe can use exit code mechanism to inform installer about failures and I highly recommend checking them from CommandLineInstaller.

Hopefully it's a good enough outline.

PS Be mindful of TARGETDIR needs to have a slash when passed by itself to directories: /WorkDir="[TARGETDIR]\"

Example of Install CustomActionData: /FileName="[TARGETDIR]\MyService.exe" /Args="/install" /WorkingDir="[TARGETDIR]\" /ValidExitCode="0" /WindowStyle="Normal"

Some code:


using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;

namespace QT.Install
{
    [RunInstaller(true)]
    public partial class ExecuteCommandInstaller : System.Configuration.Install.Installer
    {
        public class CommandArgs
        {
            public string FileName { get; set; }
            public string WorkingDir { get; set; }
            public string Args { get; set; }
            public string ValidExitCode { get; set; }
            public ProcessWindowStyle WindowStyle { get; set; }
        }

        public ExecuteCommandInstaller()
        {
            InitializeComponent();
        }

        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);
            ExecuteCommand(stateSaver);
        }

        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);
            ExecuteCommand(savedState);
        }

        public override void Uninstall(IDictionary savedState)
        {
            base.Uninstall(savedState);
            ExecuteCommand(savedState);
        }

        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
            ExecuteCommand(savedState);
        }
        private void ExecuteCommand(IDictionary stateSaver)
        {
            CommandArgs commandArgs = new CommandArgs()
            {
                FileName = StripDoubleSlash(Context.Parameters["FileName"] ?? ""),
                WorkingDir = StripDoubleSlash(Context.Parameters["WorkingDir"] ?? ""),
                Args = Context.Parameters["Args"] ?? "",
                ValidExitCode = Context.Parameters["ValidExitCode"] ?? "*"
            };

            try
            {
                commandArgs.WindowStyle = (ProcessWindowStyle)Enum.Parse(typeof(ProcessWindowStyle), Context.Parameters["WindowStyle"] ?? "Hidden");
            }
            catch (Exception err)
            {
                throw new Exception($"Invalid WindowStyle parameter value: {Context.Parameters["WindowStyle"]}", err);
            }
            InternalExecuteCommand(commandArgs);
        }

        private void InternalExecuteCommand(CommandArgs commandArgs)
        {
            if (string.IsNullOrEmpty(commandArgs.FileName))
                throw new Exception("FileName is not specified.");

            System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo(commandArgs.FileName, commandArgs.Args);

            if (!string.IsNullOrEmpty(commandArgs.WorkingDir))
                startInfo.WorkingDirectory = commandArgs.WorkingDir;

            startInfo.WindowStyle = commandArgs.WindowStyle;

            using (var process = Process.Start(startInfo))
            {
                process.WaitForExit();

                if (commandArgs.ValidExitCode != "*")
                {
                    if (process.ExitCode.ToString() != commandArgs.ValidExitCode)
                        throw new Exception($"Executing {commandArgs.FileName} {commandArgs.Args} returned exit code {process.ExitCode}. Expected exit code is: {commandArgs.ValidExitCode}.");
                }
            }
        }

        private static string StripDoubleSlash(string value)
        {
            return value.Replace("\\\\", "\\");
        }
    }
}

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜