【问题标题】:Starting and stopping IIS Express programmatically以编程方式启动和停止 IIS Express
【发布时间】:2011-06-13 21:54:37
【问题描述】:

我正在尝试用 C# 构建一个小型应用程序,它应该启动/停止 IIS Express 工作进程。为此,我想使用 MSDN 上记录的官方“IIS Express API”:http://msdn.microsoft.com/en-us/library/gg418415.aspx

据我了解,API 是(仅)基于 COM 接口的。为了使用这个 COM 接口,我通过 Add Reference -> COM -> "IIS Installed Versions Manager Interface" 在 VS2010 中添加了对 COM 库的引用:

到目前为止一切顺利,但下一步是什么?有一个IIISExprProcessUtility 接口可用,其中包括启动/停止 IIS 进程的两个“方法”。我必须编写一个实现这个接口的类吗?

public class test : IISVersionManagerLibrary.IIISExprProcessUtility
{
    public string ConstructCommandLine(string bstrSite, string bstrApplication, string bstrApplicationPool, string bstrConfigPath)
    {
        throw new NotImplementedException();
    }

    public uint GetRunningProcessForSite(string bstrSite, string bstrApplication, string bstrApplicationPool, string bstrConfigPath)
    {
        throw new NotImplementedException();
    }

    public void StopProcess(uint dwPid)
    {
        throw new NotImplementedException();
    }
} 

如您所见,我不是专业的开发人员。有人可以指出我正确的方向。 非常感谢任何帮助。

更新 1: 根据建议,我尝试了以下代码,但不幸的是它不起作用:

好的,可以实例化但我看不到如何使用这个对象...

IISVersionManagerLibrary.IIISExpressProcessUtility test3 = (IISVersionManagerLibrary.IIISExpressProcessUtility) Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("5A081F08-E4FA-45CC-A8EA-5C8A7B51727C")));

Exception: Retrieving the COM class factory for component with CLSID {5A081F08-E4FA-45CC-A8EA-5C8A7B51727C} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).

【问题讨论】:

    标签: c# iis com com-interop iis-express


    【解决方案1】:

    我试图做类似的事情。我的结论是微软提供的 COM 库是不完整的。我没有使用它,因为文档提到“注意:此主题是预发布文档,可能会在未来版本中更改”。

    所以,我决定看看 IISExpressTray.exe 在做什么。它似乎在做类似的事情。

    我反汇编了IISExpressTray.dll,发现列出所有IISexpress进程并停止IISexpress进程并没有什么神奇之处。

    它不调用那个 COM 库。它不会从注册表中查找任何内容。

    所以,我最终得到的解决方案非常简单。要启动 IIS express 进程,我只需使用 Process.Start() 并传入我需要的所有参数。

    为了停止 IIS express 进程,我使用反射器从 IISExpressTray.dll 复制了代码。我看到它只是向目标 IISExpress 进程发送 WM_QUIT 消息。

    这是我编写的用于启动和停止 IIS express 进程的类。希望这可以帮助其他人。

    class IISExpress
    {
        internal class NativeMethods
        {
            // Methods
            [DllImport("user32.dll", SetLastError = true)]
            internal static extern IntPtr GetTopWindow(IntPtr hWnd);
            [DllImport("user32.dll", SetLastError = true)]
            internal static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
            [DllImport("user32.dll", SetLastError = true)]
            internal static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint lpdwProcessId);
            [DllImport("user32.dll", SetLastError = true)]
            internal static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        }
    
        public static void SendStopMessageToProcess(int PID)
        {
            try
            {
                for (IntPtr ptr = NativeMethods.GetTopWindow(IntPtr.Zero); ptr != IntPtr.Zero; ptr = NativeMethods.GetWindow(ptr, 2))
                {
                    uint num;
                    NativeMethods.GetWindowThreadProcessId(ptr, out num);
                    if (PID == num)
                    {
                        HandleRef hWnd = new HandleRef(null, ptr);
                        NativeMethods.PostMessage(hWnd, 0x12, IntPtr.Zero, IntPtr.Zero);
                        return;
                    }
                }
            }
            catch (ArgumentException)
            {
            }
        }
    
        const string IIS_EXPRESS = @"C:\Program Files\IIS Express\iisexpress.exe";
        const string CONFIG = "config";
        const string SITE = "site";
        const string APP_POOL = "apppool";
    
        Process process;
    
        IISExpress(string config, string site, string apppool)
        {
            Config = config;
            Site = site;
            AppPool = apppool;
    
            StringBuilder arguments = new StringBuilder();
            if (!string.IsNullOrEmpty(Config))
                arguments.AppendFormat("/{0}:{1} ", CONFIG, Config);
    
            if (!string.IsNullOrEmpty(Site))
                arguments.AppendFormat("/{0}:{1} ", SITE, Site);
    
            if (!string.IsNullOrEmpty(AppPool))
                arguments.AppendFormat("/{0}:{1} ", APP_POOL, AppPool);
    
            process = Process.Start(new ProcessStartInfo()
            {
                FileName = IIS_EXPRESS,
                Arguments = arguments.ToString(),
                RedirectStandardOutput = true,
                UseShellExecute = false
            });
        }
    
        public string Config { get; protected set; }
        public string Site { get; protected set; }
        public string AppPool { get; protected set; }
    
        public static IISExpress Start(string config, string site, string apppool)
        {
            return new IISExpress(config, site, apppool);
        }
    
        public void Stop()
        {
            SendStopMessageToProcess(process.Id);
            process.Close();
        }
    }
    

    我不需要列出所有现有的 IIS express 进程。如果您需要,从我在反射器中看到的内容来看,IISExpressTray.dll 所做的是调用Process.GetProcessByName("iisexpress", ".")

    要使用我提供的类,这是我用来测试它的示例程序。

    class Program
    {
    
        static void Main(string[] args)
        {
            Console.Out.WriteLine("Launching IIS Express...");
            IISExpress iis1 = IISExpress.Start(
                @"C:\Users\Administrator\Documents\IISExpress\config\applicationhost.config",
                @"WebSite1(1)",
                @"Clr4IntegratedAppPool");
    
            IISExpress iis2 = IISExpress.Start(
                @"C:\Users\Administrator\Documents\IISExpress\config\applicationhost2.config",
                @"WebSite1(1)",
                @"Clr4IntegratedAppPool");
    
            Console.Out.WriteLine("Press ENTER to kill");
            Console.In.ReadLine();
    
            iis1.Stop();
            iis2.Stop();
        }
    }
    

    这可能不是您问题的答案,但我认为对您的问题感兴趣的人可能会发现我的工作很有用。随意改进代码。有些地方您可能想要增强。

    1. 您可以修复我的代码以从注册表中读取,而不是硬编码 iisexpress.exe 位置。
    2. 我没有包含 iisexpress.exe 支持的所有参数
    3. 我没有进行错误处理。所以,如果 IISExpress 进程由于某些原因(例如端口正在使用)而无法启动,我不知道。我认为修复它的最简单方法是监视 StandardError 流并在我从 StandardError 流中得到任何东西时抛出异常

    【讨论】:

    • 哇!非常感谢您的详细解答!我认为只要 MSDN 上没有完整/正确的 COM 文档,这似乎是完美的解决方案。我一回到 PC 上就试试。
    • 太好了,停止消息就像一个魅力。我正在从命令行运行 IIS Express,但这是缺少的部分。谢谢!
    • 太棒了!我在 Process.Start 中添加了CreateNoWindow = true,,并在我的装置的 SetUp/TearDown 期间使用它。非常感谢!
    • 谢谢。我写了一个类似的代码,并为 ISS Express 使用了process.Kill(),但由于某种原因它不起作用(有人知道为什么顺便说一句吗?)并且使用停止消息有效。
    【解决方案2】:

    虽然为时已晚,但我会提供这个问题的答案。

    IISVersionManagerLibrary.IISVersionManager mgr = new IISVersionManagerLibrary.IISVersionManagerClass();
    IISVersionManagerLibrary.IIISVersion ver = mgr.GetVersionObject("7.5", IISVersionManagerLibrary.IIS_PRODUCT_TYPE.IIS_PRODUCT_EXPRESS);
    
    object obj1 = ver.GetPropertyValue("expressProcessHelper");
    
    IISVersionManagerLibrary.IIISExpressProcessUtility util = obj1 as IISVersionManagerLibrary.IIISExpressProcessUtility;
    

    就是这样。然后就可以在 util 对象上调用 StopProcess 方法了。

    但是,您必须得到 Microsoft 的通知。

    " 版本管理器 API (IIS Express) ; http://msdn.microsoft.com/en-us/library/gg418429(v=VS.90).aspx

    注意:IIS 版本管理器 API 支持 IIS Express 基础设施和不打算 直接从您的代码中使用。 "

    【讨论】:

    • 谢谢 (+1)。编译器抱怨并告诉我修改第一行: IISVersionManagerLibrary.IISVersionManager mgr = new IISVersionManagerLibrary.IISVersionManager();
    • 谢谢!这段代码对我来说运行良好,但我想我对下一步该做什么有点迷茫。我怎样才能知道它在哪个端口上运行?如何使用它来启动我正在开发的网站?
    • 唉,这不适用于最新(4.5+)的 .NET.... 错误 CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'
    • @DavidV.Corbin 您的问题与此答案无关。去寻找你的答案。必须有。 (仅供参考,我在 .NET 的 4.6.1 中编译成功)
    • 永远不会太晚!
    【解决方案3】:

    此实现适用于以编程方式启动/停止 IIS Express,可在测试中使用。

    public class IisExpress : IDisposable
    {
        private Boolean _isDisposed;
    
        private Process _process;
    
        public void Dispose()
        {
            Dispose(true);
        }
    
        public void Start(String directoryPath, Int32 port)
        {
            var iisExpressPath = DetermineIisExpressPath();
            var arguments = String.Format(
                CultureInfo.InvariantCulture, "/path:\"{0}\" /port:{1}", directoryPath, port);
    
            var info = new ProcessStartInfo(iisExpressPath)
                                        {
                                            WindowStyle = ProcessWindowStyle.Normal,
                                            ErrorDialog = true,
                                            LoadUserProfile = true,
                                            CreateNoWindow = false,
                                            UseShellExecute = false,
                                            Arguments = arguments
                                        };
    
            var startThread = new Thread(() => StartIisExpress(info))
                                     {
                                         IsBackground = true
                                     };
    
            startThread.Start();
        }
    
        protected virtual void Dispose(Boolean disposing)
        {
            if (_isDisposed)
            {
                return;
            }
    
            if (disposing)
            {
                if (_process.HasExited == false)
                {
                    _process.Kill();
                }
    
                _process.Dispose();
            }
    
            _isDisposed = true;
        }
    
        private static String DetermineIisExpressPath()
        {
            String iisExpressPath;
    
            iisExpressPath = Environment.GetFolderPath(Environment.Is64BitOperatingSystem 
                ? Environment.SpecialFolder.ProgramFilesX86
                : Environment.SpecialFolder.ProgramFiles);
    
            iisExpressPath = Path.Combine(iisExpressPath, @"IIS Express\iisexpress.exe");
    
            return iisExpressPath;
        }
    
        private void StartIisExpress(ProcessStartInfo info)
        {
            try
            {
                _process = Process.Start(info);
    
                _process.WaitForExit();
            }
            catch (Exception)
            {
                Dispose();
            }
        }
    }
    

    【讨论】:

    • 这太棒了!我发现了这个here的更完整的实现。
    • Environment.Is64BitOperatingSystem 在这里是错误的选择。这实际上取决于构建网站的二进制文件的位数。
    【解决方案4】:

    我觉得你做得很艰难。从这个问题Automatically stop/restart ASP.NET Development Server on Build 中获得提示,看看您是否可以采用相同的流程。

    回答您的问题,我认为pinvoke.net 可能会对您有所帮助。他们也有很多示例可以帮助您构建解决方案。

    【讨论】:

    • Pradeep,感谢您的回答。第一个链接的解决方案对我不起作用,因为它们使用 proc.Kill(); 终止了该进程; pinvoke.net 非常有趣,感谢您的提示。不幸的是,我没有找到有关如何使用 IIS Express COM API 的信息
    【解决方案5】:

    Harvey Kwok 提供了一个很好的提示,因为我想在运行集成测试用例时拆掉服务。但是 PInvoke 和消息传递的 Harvey 代码太长了。

    这是另一种选择。

        public class IisExpressAgent
    {
        public void Start(string arguments)
        {
            ProcessStartInfo info= new ProcessStartInfo(@"C:\Program Files (x86)\IIS Express\iisexpress.exe", arguments)
            {
              // WindowStyle= ProcessWindowStyle.Minimized,
            };
    
            process = Process.Start(info);
        }
    
        Process  process;
    
        public void Stop()
        {
            process.Kill();
        }
    }
    

    在我与 MS Test 的集成测试套装中,我有

           [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            iis = new IisExpressAgent();
            iis.Start("/site:\"WcfService1\" /apppool:\"Clr4IntegratedAppPool\"");
        }
    
        static IisExpressAgent iis;
    
        //Use ClassCleanup to run code after all tests in a class have run
        [ClassCleanup()]
        public static void MyClassCleanup()
        {
            iis.Stop();
        }
    

    【讨论】:

    • 您可能会发现这里没有使用通常的 IDisposable 模式。但是,进程资源实际上是在 ClassCleanup/teardown 方法中释放的,并且测试套件进程是短暂的。
    【解决方案6】:

    这也是我的解决方案。它运行带有隐藏窗口的 IIS Express。 Manager 类控制多个 IIS Express 实例。

    class IISExpress
    {               
        private const string IIS_EXPRESS = @"C:\Program Files\IIS Express\iisexpress.exe";        
    
        private Process process;
    
        IISExpress(Dictionary<string, string> args)
        {
            this.Arguments = new ReadOnlyDictionary<string, string>(args);
    
            string argumentsInString = args.Keys
                .Where(key => !string.IsNullOrEmpty(key))
                .Select(key => $"/{key}:{args[key]}")
                .Aggregate((agregate, element) => $"{agregate} {element}");
    
            this.process = Process.Start(new ProcessStartInfo()
            {
                FileName = IIS_EXPRESS,
                Arguments = argumentsInString,
                WindowStyle = ProcessWindowStyle.Hidden                
            });
        }
    
        public IReadOnlyDictionary<string, string> Arguments { get; protected set; }        
    
        public static IISExpress Start(Dictionary<string, string> args)
        {
            return new IISExpress(args);
        }
    
        public void Stop()
        {
            try
            {
                this.process.Kill();
                this.process.WaitForExit();
            }
            finally
            {
                this.process.Close();
            }            
        }        
    }
    

    我需要几个实例。设计了管理器类来控制它们。

    static class IISExpressManager
    {
        /// <summary>
        /// All started IIS Express hosts
        /// </summary>
        private static List<IISExpress> hosts = new List<IISExpress>();
    
        /// <summary>
        /// Start IIS Express hosts according to the config file
        /// </summary>
        public static void StartIfEnabled()
        {
            string enableIISExpress = ConfigurationManager.AppSettings["EnableIISExpress"]; // bool value from config file
            string pathToConfigFile = ConfigurationManager.AppSettings["IISExpressConfigFile"]; // path string to iis configuration file
            string quotedPathToConfigFile = '"' + pathToConfigFile + '"';
    
            if (bool.TryParse(enableIISExpress, out bool isIISExpressEnabled) 
                && isIISExpressEnabled && File.Exists(pathToConfigFile))
            {                
                hosts.Add(IISExpress.Start(
                    new Dictionary<string, string> {
                        {"systray", "false"},
                        {"config", quotedPathToConfigFile},
                        {"site", "Site1" }                        
                    }));
    
                hosts.Add(IISExpress.Start(
                    new Dictionary<string, string> {
                        {"systray", "false"},
                        { "config", quotedPathToConfigFile},
                        {"site", "Site2" }
                    }));
    
            }
        }
    
        /// <summary>
        /// Stop all started hosts
        /// </summary>
        public static void Stop()
        {
            foreach(var h in hosts)
            {
                h.Stop();
            }
        }
    }
    

    【讨论】:

      【解决方案7】:

      图我也会把我的解决方案放在这里。源自 SeongTae Jeong 的解决方案和另一个帖子(现在不记得在哪里了)。

      1. 安装Microsoft.Web.Administrationnuget。
      2. 如上所述引用IIS Installed Versions Manager Interface COM 类型库。
      3. 添加以下类:

        using System;
        using System.Diagnostics;
        using System.IO;
        using System.Text.RegularExpressions;
        using IISVersionManagerLibrary;
        using Microsoft.Web.Administration;
        
        public class Website
        {
            private const string DefaultAppPool = "Clr4IntegratedAppPool";
            private const string DefaultIISVersion = "8.0";
        
            private static readonly Random Random = new Random();
            private readonly IIISExpressProcessUtility _iis;
            private readonly string _name;
            private readonly string _path;
            private readonly int _port;
            private readonly string _appPool;
            private readonly string _iisPath;
            private readonly string _iisArguments;
            private readonly string _iisConfigPath;
            private uint _iisHandle;
        
            private Website(string path, string name, int port, string appPool, string iisVersion)
            {
                _path = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path));
                _name = name;
                _port = port;
                _appPool = appPool;
                _iis = (IIISExpressProcessUtility)new IISVersionManager()
                    .GetVersionObject(iisVersion, IIS_PRODUCT_TYPE.IIS_PRODUCT_EXPRESS)
                    .GetPropertyValue("expressProcessHelper");
                var commandLine = _iis.ConstructCommandLine(name, "", appPool, "");
                var commandLineParts = new Regex("\\\"(.*?)\\\" (.*)").Match(commandLine);
                _iisPath = commandLineParts.Groups[1].Value;
                _iisArguments = commandLineParts.Groups[2].Value;
                _iisConfigPath = new Regex("\\/config:\\\"(.*?)\\\"").Match(commandLine).Groups[1].Value;
                Url = string.Format("http://localhost:{0}/", _port);
            }
        
            public static Website Create(string path,
                string name = null, int? port = null,
                string appPool = DefaultAppPool,
                string iisVersion = DefaultIISVersion)
            {
                return new Website(path,
                    name ?? Guid.NewGuid().ToString("N"),
                    port ?? Random.Next(30000, 40000),
                    appPool, iisVersion);
            }
        
            public string Url { get; private set; }
        
            public void Start()
            {
                using (var manager = new ServerManager(_iisConfigPath))
                {
                    manager.Sites.Add(_name, "http", string.Format("*:{0}:localhost", _port), _path);
                    manager.CommitChanges();
                }
                Process.Start(new ProcessStartInfo
                {
                    FileName = _iisPath,
                    Arguments = _iisArguments,
                    RedirectStandardOutput = true,
                    UseShellExecute = false
                });
                var startTime = DateTime.Now;
                do
                {
                    try
                    {
                        _iisHandle = _iis.GetRunningProcessForSite(_name, "", _appPool, "");
                    }
                    catch { }
                    if (_iisHandle != 0) break;
                    if ((DateTime.Now - startTime).Seconds >= 10)
                        throw new TimeoutException("Timeout starting IIS Express.");
                } while (true);
            }
        
            public void Stop()
            {
                try
                {
                    _iis.StopProcess(_iisHandle);
                }
                finally
                {
                    using (var manager = new ServerManager(_iisConfigPath))
                    {
                        var site = manager.Sites[_name];
                        manager.Sites.Remove(site);
                        manager.CommitChanges();
                    }
                }
            }
        }
        
      4. 如下设置您的测试夹具。该路径相对于您的测试套件的 bin 文件夹。

        [TestFixture]
        public class Tests
        {
            private Website _website;
        
            [TestFixtureSetUp]
            public void Setup()
            {
                _website = Website.Create(@"..\..\..\TestHarness");
                _website.Start();
            }
        
            [TestFixtureTearDown]
            public void TearDown()
            {
                _website.Stop();
            }
        
            [Test]
            public void should_serialize_with_bender()
            {
                new WebClient().UploadString(_website.Url, "hai").ShouldEqual("hai");
            }
        }
        

      还有一点,如果这也将在构建服务器上运行。首先你需要install IIS Express on the build server。其次,您必须在构建服务器上创建一个applicationhost.config。您可以从C:\Users\&lt;User&gt;\Documents\IISExpress\config\ 下的开发框中复制一份。需要将其复制到您的构建服务器正在运行的用户的相应路径。如果它作为系统运行,则路径为C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\。

      【讨论】:

        【解决方案8】:

        不,您不继承接口。您可以使用 new 关键字创建 IISVersionManager 的实例。这如何让您获得对 IIISExpressProcessUtility 实例的引用完全不清楚。 MSDN 文档很糟糕。也许您可以新建一个,但它看起来不支持。

        【讨论】:

        • 如果你想知道新接口是如何工作的,请阅读stackoverflow.com/questions/1093536/…。
        • 它是特定于 COM 的,所有方法都通过接口公开。
        • 非常感谢您的支持!我做了一些测试并更新了我的帖子。
        • 是的,很抱歉,但我确实警告过你。恐怕你已经严重领先了。蹩脚的 MSDN 文档实际上是一个主要标志,微软可以雇佣的那种程序员有点意识到自动化接口很重要,但不知道如何实际实现一个。 COM 是有胡子有肚皮的人会做的事。它根本没有很好的记录,因为它不起作用。您要避免的流行词是“CTP”和“Express”。
        • 感谢您的支持。不过,既然IIS Express现在已经发布了,我还是希望微软在不久的将来完成/更正MSDN上的COM文档。
        【解决方案9】:

        如果您修改了 Web 应用程序的 web.config 文件,IIS(包括 Express)将重新启动应用程序池。这将允许您部署更新的程序集。

        修改 web.config 的一种方法是将其复制到一个新文件中,然后将其移回原处。

        copy /Y path/web.config path/web_touch.config
        move /Y path/web_touch.config path/web.config
        

        您可能希望对 IIS Express 进行更多控制,而不是简单地重新启动应用程序池。但是,如果这就是您所需要的,这将起作用。

        【讨论】:

          【解决方案10】:

          我采用了不同的解决方案。您可以使用“taskkill”和进程名称简单地终止进程树。 这在本地和 TFS 2013 上完美运行

          public static void FinalizeIis()
          {
              var startInfo = new ProcessStartInfo
              {
                  UseShellExecute = false,
                  Arguments = string.Format("/F /IM iisexpress.exe"),
                  FileName = "taskkill"
              };
          
              Process.Start(startInfo);
          }
          

          【讨论】:

            猜你喜欢
            • 2011-03-30
            • 1970-01-01
            • 1970-01-01
            • 2017-11-10
            • 1970-01-01
            • 1970-01-01
            • 2019-03-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多