以编程方式启动和停止IIS Express
我正在尝试在C#中构build一个小应用程序,该应用程序应该启动/停止IIS Express工作进程。 为此,我想使用MSDN上logging的官方“IIS Express API”: http : //msdn.microsoft.com/en-us/library/gg418415.aspx
据我所知,API是基于(只)COM接口。 为了使用这个COM接口,我已经通过添加引用 – > COM – >“IIS安装版本pipe理器接口”在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:根据build议我已经尝试了下面的代码,不幸的是不工作:
好吧,它可以实例化,但我不明白如何使用这个对象…
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)).
我正在尝试做类似的事情。 我的结论是微软提供的COM库不完整。 我不使用它,因为文档中提到“注意:这个主题是预发布文档,在未来的版本中可能会有变化”。
所以,我决定看看IISExpressTray.exe在做什么。 这似乎是在做类似的事情。
我反汇编IISExpressTray.dll,发现列出所有IISexpress进程和停止IISexpress进程没有什么魔力。
它不会调用该COM库。 它不会从registry中查找任何内容。
所以,我结束的解决scheme非常简单。 要启动一个IIS express进程,我只需使用Process.Start()并传入所需的所有参数。
要停止IIS快速过程,我使用reflection器从IISExpressTray.dll复制代码。 我看到它只是发送一个WM_QUIT消息到目标IISExpress进程。
这里是我写的启动和停止IIS快速进程的类。 希望这可以帮助别人。
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过程。 如果你需要的话,从我在reflection器中看到的,IISExpressTray.dll所做的就是调用Process.GetProcessByName("iisexpress", ".")
要使用我提供的类,以下是我用来testing它的示例程序。
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(); } }
这可能不是你的问题的答案,但我认为在你的问题有趣的人可能会发现我的工作有用。 随意改善代码。 有一些地方,你可能想要加强。
- 而不是硬编码的iisexpress.exe位置,您可以修复我的代码从registry中读取。
- 我没有包含iisexpress.exe支持的所有参数
- 我没有做error handling。 所以,如果IISExpress进程由于某些原因(例如,端口在使用中)启动失败,我不知道。 我认为解决这个问题最简单的方法是监视StandardErrorstream并抛出exception,如果我从StandardErrorstream获得任何东西
虽然已经太迟了,我会回答这个问题。
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获得通知。
“版本pipe理器API(IIS Express); http://msdn.microsoft.com/zh-cn/library/gg418429(v=VS.90).aspx
注意:IIS版本pipe理器API支持IIS Express基础结构, 不能直接在您的代码中使用。 “
此实现适用于以编程方式启动/停止IIS Express,可以从testing中使用。
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(); } } }
我觉得你正在以一种困难的方式去做。 从这个问题提示提示自动停止/重新启动生成ASP.NET开发服务器 ,看看你是否可以采用相同的过程。
回答你的问题,我认为pinvoke.net可能会帮助你。 他们有很多的例子,可以帮助你build立你的解决scheme。
由于我想在运行集成testing案例的时候撕毁和拆卸服务,郭富城提供了一个很好的提示。 但是Harvey代码在PInvoke和消息传递方面太长了。
这是一个替代scheme。
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(); } }
而在我与MStesting集成testing,我有
[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(); }
不,你不会inheritance这个接口。 您可以使用new关键字创buildIISVersionManager的实例。 如何让你对一个IIISExpressProcessUtility实例的引用完全不清楚。 MSDN文档是可怕的。 也许你可以新的,但它看起来不像它支持。
如果修改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,而不是简单地重新启动应用程序池。 但是,如果这就是你所需要的,这将工作。
我采取了不同的解决scheme。 你可以简单地使用“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); }
图我也会在这里抛出我的解决scheme。 从SeongTae Jeong的解决scheme和另一个post(不记得现在在哪里)派生。
- 安装
Microsoft.Web.Administration
nuget 。 - 如上所述引用
IIS Installed Versions Manager Interface
COMtypes库。 -
添加以下类:
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(); } } } }
-
如下设置您的testing夹具。 该path相对于您的testing套件的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"); } }
还有一点,如果这也将在生成服务器上运行。 首先,您需要在构build服务器上安装IIS Express 。 其次,你必须在构build服务器上创build一个applicationhost.config
。 您可以从C:\Users\<User>\Documents\IISExpress\config\
下的开发框中复制一个。 它需要被复制到您的构build服务器运行的用户的相应path。 如果以系统身份运行,则path将为C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\
。