2011-01-23 4 views
52

Я пытаюсь создать небольшое приложение на C#, которое должно запускать/останавливать рабочий процесс IIS Express. Для этого я хочу использовать официальный «API-интерфейс IIS Express», который документально подтвержден на MSDN: http://msdn.microsoft.com/en-us/library/gg418415.aspxЗапуск и остановка программного обеспечения IIS Express

Насколько я понимаю, API основан (только) на интерфейсах COM. Чтобы использовать этот COM-интерфейсы я добавил ссылку на COM-библиотеки в VS2010 через Add Reference -> COM -> «IIS установленных версий Менеджер интерфейса»:

До сих пор так хорошо, но что дальше ? Имеется доступный интерфейс 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(); 
    } 
} 

Как вы можете видеть, я не профессиональный разработчик. Может кто-то указать мне в правильном направлении. Любая помощь очень ценится.

Update 1: Согласно предложениям я попытался следующий код, который, к сожалению, не работает:

alt text Хорошо, он может быть реализован, но я не могу понять, как использовать этот объект. ..

alt text

alt text

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

ответ

53

Я пытался сделать подобное. Я пришел к выводу, что библиотека COM, предоставленная Microsoft, является неполной. Я не использую его, потому что в документе упоминалось, что «Примечание: этот раздел является предварительной версией документации и может быть изменен в будущих выпусках».

Итак, я решил посмотреть, что делает IISExpressTray.exe. Кажется, он делает похожие вещи.

Я разобрал IISExpressTray.dll и обнаружил, что нет магии при перечислении всех процессов IISexpress и остановке процесса IISexpress.

Это не называется COM-библиотекой. Он не ищет ничего из реестра.

Итак, решение, которое я закончил, очень простое. Чтобы запустить процесс IIS express, я просто использую Process.Start() и передаю все необходимые мне параметры.

Чтобы остановить процесс экспресс-доставки IIS, я скопировал код из IISExpressTray.dll с помощью отражателя. Я видел, что он просто отправляет сообщение WM_QUIT в целевой процесс IISExpress.

Вот класс, который я написал, чтобы начать и остановить процесс 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. Если вам это нужно, то, что я видел в рефлекторе, то, что 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.
+2

Вау! Большое спасибо за этот подробный ответ! Я думаю, что до тех пор, пока не будет полной/правильной документации COM на MSDN, это, кажется, идеальное решение. Я попробую, как только вернусь на компьютер. – Mike

+3

Хорошо, я пробовал, отлично работает.Еще раз спасибо :-) – Mike

+0

Отлично, сообщение остановки работает как шарм. Я запускаю IIS Express из командной строки, но это был недостающий кусок. Благодаря! – jpierson

3

Я чувствую, что вы делаете это с трудом. Возьмите намек на этот вопрос Automatically stop/restart ASP.NET Development Server on Build и посмотрите, можете ли вы принять тот же процесс.

Отвечая на ваш вопрос, я думаю, pinvoke.net может вам помочь. У них также много примеров, которые помогут вам построить свое решение.

+0

Pradeep, спасибо за ваш ответ. Решение из первой ссылки не работает для меня, потому что они убивают процесс с помощью proc.Kill(); pinvoke.net очень интересен, спасибо за подсказку. К сожалению, я не нашел информации о том, как использовать IIS Express COM API – Mike

+0

+1 для pinvoke.net – tomfanning

1

Нет, вы не наследуете интерфейс. Вы можете создать экземпляр IISVersionManager с ключевым словом . То, как вы получаете ссылку на экземпляр IIISExpressProcessUtility, совершенно неясно. Документы MSDN ужасны. Может быть, вы можете новинка, но это не похоже, что это поддерживает.

+0

Прочтите http://stackoverflow.com/questions/1093536/how-does-the-c-compiler-detect- com-types, если вы хотите знать, как работает новинка в интерфейсе. – Pradeep

+1

Это COM специфичный, все методы доступны через интерфейсы. –

+0

Большое спасибо за вашу поддержку! Я сделал несколько тестов и обновил свой пост. – Mike

13

Хотя, уже слишком поздно, я дам ответ на этот вопрос.

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; 

Всё. Затем вы можете вызвать метод StopProcess для объекта util.

Однако вы должны получить уведомление от Microsoft.

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

Примечание: API IIS Диспетчер версий поддерживает IIS Express инфраструктуры и не предназначен для использования непосредственно из вашего кода. "

+0

Спасибо (+1). Компилятор жаловался и сказал мне изменить первую строку: IISVersionManagerLibrary.IISVersionManager mgr = new IISVersionManagerLibrary.IISVersionManager(); – ilmatte

+0

Спасибо! Этот код отлично подходит для меня, но, я думаю, я немного потерял, что делать дальше. Как я могу выяснить, в каком порту он работает? Как я могу использовать это для запуска сайта, который я разрабатываю? –

+0

Увы, это не работает с последними (4.5+) .NET .... Ошибка CS0656: Отсутствует компилятор требуется член 'Microsoft.CSharp.RuntimeBinder.Binder.Convert' –

1

Если вы изменили файл web.config веб-приложения, IIS (включая Express) перезапустит a pp. Это позволит вам развернуть обновленные сборки.

Один из способов изменения web.config - скопировать его в новый файл, а затем переместить его обратно.

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

Возможно, вам потребуется больше контроля над IIS Express, чем просто перезапуск пула приложений. Но если это все, что вам нужно, это сработает.

1

Я принял другое решение. Вы можете просто убить дерево процессов, используя «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); 
} 
6

Эта реализация работает для запуска/остановки 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(); 
     } 
    } 
} 
+2

Это замечательно! Я нашел более полную реализацию этого [здесь] (https://github.com/roryprimrose/Headless/blob/9caacee22acf9b24a6880dff695fb1cfb552f5fe/Headless.IntegrationTests/IisExpress.cs). – orad

+0

'Environment.Is64BitOperatingSystem' - неправильный выбор здесь. Это действительно зависит от битности двоичных файлов, для которых создан сайт. – jpmc26

3

Харвьте Квки обеспечили хороший намек, так как я хочу, чтобы разорвать и разрушить службу при запуске тестовых случаев интеграции. Но коды Harvey слишком длинны с PInvoke и обмен сообщениями.

Вот альтернатива.

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

Вы можете найти обычный шаблон IDisposable, который здесь не используется. Тем не менее, ресурс процесса фактически выпущен в методе ClassCleanup/teardown, а процесс тестового костюма короткий. – ZZZ

1

рис я бросить мое решение здесь тоже. Выводится из решения SeongTae Jeong и другого сообщения (не помню, где сейчас).

  1. Установить Microsoft.Web.Administrationnuget.
  2. Ссылка на библиотеку COM типа IIS Installed Versions Manager Interface, как указано выше.
  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\<User>\Documents\IISExpress\config\. Его нужно скопировать на соответствующий путь пользователя, на котором работает ваш сервер сборки. Если он работает как система, то путь будет C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\.