简体   繁体   English

以编程方式启动和停止IIS Express

[英]Starting and stopping IIS Express programmatically

I am trying to build a small application in C# which should start/stop an IIS Express worker process. 我正在尝试在C#中构建一个小应用程序,它应该启动/停止IIS Express工作进程。 For this purpose I want to use the official "IIS Express API" which is documented on MSDN: http://msdn.microsoft.com/en-us/library/gg418415.aspx 为此,我想使用MSDN上记录的官方“IIS Express API”: http//msdn.microsoft.com/en-us/library/gg418415.aspx

As far as I understand, the API is based (only) on COM interfaces. 据我所知,API仅(仅)基于COM接口。 To use this COM interfaces I've added a reference to the COM library in VS2010 via Add Reference -> COM -> "IIS Installed Versions Manager Interface": 为了使用这个COM接口,我通过Add Reference - > COM - >“IIS Installed Versions Manager Interface”在VS2010中添加了对COM库的引用:

So far so good, but what's next? 到目前为止一切都很好,但下一步是什么? There is an IIISExprProcessUtility interface available which includes the the two "methods" to start/stop an IIS process. 有一个IIISExprProcessUtility接口可用,它包含启动/停止IIS进程的两个“方法”。 Do I have to write a class which implements this interface? 我是否必须编写一个实现此接口的类?

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();
    }
} 

As you can see, I'm not a professional developer. 如您所见,我不是一名专业开发人员。 Can someone point me in the right direction. 有人能指出我正确的方向。 Any help is greatly appreciated. 任何帮助是极大的赞赏。

Update 1: According to the suggestions I've tried the following code which unfortunately doesn't work: 更新1:根据建议,我尝试了下面的代码,但遗憾的是它不起作用:

替代文字 Ok, it can be instantiated but I cannot see how to use this object... 好的,它可以实例化但我看不到如何使用这个对象...

替代文字

替代文字

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)).

I was trying to do similar thing. 我试图做类似的事情。 I concluded that the COM library provided by Microsoft is incomplete. 我得出结论,微软提供的COM库是不完整的。 I don't use it because the doc mentioned that "Note: This topic is pre-release documentation and is subject to change in future releases". 我不使用它,因为该文档提到“注意:本主题是预发布文档,在将来的版本中可能会有所变化”。

So, I decided to take a look at what IISExpressTray.exe is doing. 所以,我决定看看IISExpressTray.exe正在做什么。 It seems to be doing similar things. 它似乎在做类似的事情。

I disassemble the IISExpressTray.dll and found that there is no magic in listing out all the IISexpress processes and stoping the IISexpress process. 我反汇编IISExpressTray.dll,发现列出所有IISexpress进程并停止IISexpress进程没有魔力。

It doesn't call that COM library. 它不会调用该COM库。 It doesn't lookup anything from registry. 它不会从注册表中查找任何内容。

So, the solution I ended up is very simple. 所以,我最终的解决方案非常简单。 To start an IIS express process, I just use Process.Start() and pass in all the parameters I need. 要启动IIS express进程,我只需使用Process.Start()并传入我需要的所有参数。

To stop an IIS express process, I copied the code from IISExpressTray.dll using reflector. 为了停止IIS表达过程,我使用反射器从IISExpressTray.dll复制了代码。 I saw it simply sends a WM_QUIT message to the target IISExpress process. 我看到它只是向目标IISExpress进程发送WM_QUIT消息。

Here is the class I wrote to start and stop an IIS express process. 这是我编写的用于启动和停止IIS表达过程的类。 Hope this can help somebody else. 希望这可以帮助别人。

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();
    }
}

I don't need to list all the existing IIS express process. 我不需要列出所有现有的IIS express进程。 If you need that, from what I saw in the reflector, what IISExpressTray.dll does is to call Process.GetProcessByName("iisexpress", ".") 如果你需要它,从我在反射器中看到的,IISExpressTray.dll的作用就是调用Process.GetProcessByName("iisexpress", ".")

To use the class I provided, here is a sample program I used to test it. 要使用我提供的类,这是我用来测试它的示例程序。

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();
    }
}

This may not be an answer to your question but I think people interesting in your question may find my work useful. 这可能不是您问题的答案,但我认为在您的问题中有趣的人可能会发现我的工作很有用。 Feel free to improve the codes. 随意改进代码。 There are some places that you might want to enhance. 您可能希望增强一些地方。

  1. Instead of hardcoding the iisexpress.exe location, you can fix my code to read from the registry. 您可以修复我的代码以从注册表中读取,而不是对iisexpress.exe位置进行硬编码。
  2. I didn't include all the arguments supported by iisexpress.exe 我没有包含iisexpress.exe支持的所有参数
  3. I didn't do error handling. 我没有做错误处理。 So, if the IISExpress process failed to start for some reasons (eg port is in used), I don't know. 因此,如果IISExpress进程由于某些原因(例如使用端口)而无法启动,我不知道。 I think the easiest way to fix it is to monitor the StandardError stream and throw exception if I get anything from StandardError stream 我认为解决它的最简单方法是监视StandardError流并在从StandardError流中获取任何内容时抛出异常

Although, it's too late, I'll provide an answer to this question. 虽然为时已晚,但我会回答这个问题。

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;

That's it. 而已。 Then you can call StopProcess method on util object. 然后,您可以在util对象上调用StopProcess方法。

However, you have to get notice from Microsoft. 但是,您必须得到Microsoft的通知。

" Version Manager API (IIS Express) ; http://msdn.microsoft.com/en-us/library/gg418429(v=VS.90).aspx “Version Manager API(IIS Express); http://msdn.microsoft.com/en-us/library/gg418429(v=VS.90).aspx

Note: The IIS Version Manager API supports the IIS Express infrastructure and is not intended to be used directly from your code. 注意:IIS Version Manager API支持IIS Express基础结构, 不应在代码中直接使用。 "

This implementation works for starting/stopping IIS Express programmatically, can be used from tests. 此实现适用于以编程方式启动/停止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();
        }
    }
}

Harvey Kwok had provided a good hint, since I want to tear up and tear down the service when running integration test cases. Harvey Kwok提供了一个很好的提示,因为我想在运行集成测试用例时撕掉并拆除服务。 But Harvey codes is too long with PInvoke and messaging. 但是使用PInvoke和消息传递Harvey代码太长了。

Here's an alternative. 这是另一种选择。

    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();
    }
}

And in my integration test suit with MS Test, I have 在我的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();
    }

I feel you are doing it in a hard way. 我觉得你是在艰难地做这件事。 Take a hint from this question Automatically stop/restart ASP.NET Development Server on Build and see if you can adopt the same process. 从这个问题中提示一下在Build上自动停止/重启ASP.NET Development Server ,看看是否可以采用相同的过程。

Answering your question, I think pinvoke.net might help you. 回答你的问题,我认为pinvoke.net可能对你有所帮助。 They have lot of examples as well which can help you build your solution. 他们还有很多例子可以帮助您构建解决方案。

If you modify the web.config file of the web application, IIS (including Express) will restart the app pool. 如果修改Web应用程序的web.config文件,IIS(包括Express)将重新启动应用程序池。 This will allow you to deploy updated assemblies. 这将允许您部署更新的程序集。

One way to modify web.config is to copy it to a new file, and then move it back. 修改web.config的一种方法是将其复制到新文件,然后将其移回。

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

You may want more control over IIS Express than simply restarting the app pool. 您可能希望更多地控制IIS Express,而不仅仅是重新启动应用程序池。 But if that's all you need, this will work. 但如果这就是你所需要的,那就行了。

I have adopted a different solution. 我采用了不同的解决方案。 You can simply kill the process tree using "taskkill" and the name of the process. 您可以使用“taskkill”和进程名称简单地终止进程树。 This works perfectly locally and on TFS 2013 这在本地和TFS 2013上完美运行

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

    Process.Start(startInfo);
}

Figure I'd throw my solution in here too. 图我也在这里抛出我的解决方案。 Derived from the SeongTae Jeong's solution and another post (Can't remember where now). 源自SeongTae Jeong的解决方案和另一篇文章(不记得现在在哪里)。

  1. Install the Microsoft.Web.Administration nuget . 安装Microsoft.Web.Administration nuget
  2. Reference the IIS Installed Versions Manager Interface COM type library as noted above. 如上所述,引用IIS Installed Versions Manager Interface COM类型库。
  3. Add the following class: 添加以下类:

     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. Setup your test fixture as follows. 按如下方式设置测试夹具。 The path is relative to the bin folder of your test suite. 该路径相对于测试套件的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"); } } 

And one more point if this is going to also run on a build server. 还有一点,如果这也将在构建服务器上运行。 First you will need to install IIS Express on the build server . 首先,您需要在构建服务器上安装IIS Express Second, you'll have to create an applicationhost.config on the build server. 其次,您必须在构建服务器上创建applicationhost.config You can copy one from your dev box under C:\\Users\\<User>\\Documents\\IISExpress\\config\\ . 您可以从C:\\Users\\<User>\\Documents\\IISExpress\\config\\下的开发框中复制一个。 It needs to be copied to the corresponding path of the user your build server is running as. 它需要复制到您的构建服务器运行的用户的相应路径。 If it is running as system then the path would be C:\\Windows\\System32\\config\\systemprofile\\Documents\\IISExpress\\config\\ . 如果它作为系统运行,那么路径将是C:\\Windows\\System32\\config\\systemprofile\\Documents\\IISExpress\\config\\

No, you don't inherit the interface. 不,您不继承该接口。 You can create an instance of IISVersionManager with the new keyword. 您可以使用new关键字创建IISVersionManager的实例。 How that gets you a reference to an IIISExpressProcessUtility instance is completely unclear. 如何让你引用IIISExpressProcessUtility实例是完全不清楚的。 The MSDN docs are awful. MSDN文档很糟糕。 Maybe you can new one but it doesn't look like it supports that. 也许你可以换新的,但看起来它不支持。

Here is my solution too. 这也是我的解决方案。 It runs IIS Express with hidden windows. 它运行带有隐藏窗口的IIS Express。 Manager class controls several IIS Express instances. 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();
        }            
    }        
}

I need several instances. 我需要几个实例。 Designed manager class to control them. 设计经理类来控制它们。

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();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM