2011-01-23 23 views
52

Estoy intentando crear una pequeña aplicación en C# que debería iniciar/detener un proceso de trabajo de IIS Express. Para este propósito, quiero utilizar el oficial "IIS Express API" que está documentado en MSDN: http://msdn.microsoft.com/en-us/library/gg418415.aspxIniciar y detener IIS Express mediante programación

Por lo que tengo entendido, la API se basa (solo) en las interfaces COM. Para utilizar estos interfaces COM He añadido una referencia a la biblioteca COM en VS2010 a través de Agregar referencia -> COM -> "IIS versiones instaladas Interface Manager":

Hasta aquí todo bien, pero lo que se viene ? Existe una interfaz IIISExprProcessUtility disponible que incluye los dos "métodos" para iniciar/detener un proceso IIS. ¿Debo escribir una clase que implemente esta interfaz?

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

Como puedes ver, no soy un desarrollador profesional. Alguien me puede apuntar en la dirección correcta. Cualquier ayuda es muy apreciada.

Actualización 1: De acuerdo con las sugerencias que he probado el siguiente código que desafortunadamente no funciona:

alt text Ok, se puede crear instancias, pero no puedo ver cómo utilizar este objeto. ..

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

Respuesta

53

Estaba tratando de hacer algo similar. Llegué a la conclusión de que la biblioteca COM proporcionada por Microsoft está incompleta. No lo uso porque el documento menciona que "Nota: este tema es una documentación preliminar y está sujeto a cambios en versiones futuras".

Por lo tanto, decidí echar un vistazo a lo que está haciendo IISExpressTray.exe. Parece estar haciendo cosas similares.

Desmonte el IISExpressTray.dll y descubrí que no es mágico enumerar todos los procesos de IISexpress y detener el proceso de IISexpress.

No llama a esa biblioteca COM. No busca nada desde el registro.

Entonces, la solución que terminé es muy simple. Para iniciar un proceso express de IIS, solo uso Process.Start() y paso todos los parámetros que necesito.

Para detener un proceso de IIS express, copié el código de IISExpressTray.dll usando el reflector. Vi que simplemente envía un mensaje WM_QUIT al proceso IISExpress de destino.

Aquí está la clase que escribí para iniciar y detener un proceso de IIS express. Espero que esto pueda ayudar a alguien más.

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

No es necesario que enumere todos los procesos existentes de IIS express. Si lo necesita, por lo que vi en el reflector, lo que hace IISExpressTray.dll es llamar al Process.GetProcessByName("iisexpress", ".")

Para usar la clase que proporcioné, aquí hay un programa de muestra que utilicé para probarlo.

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

Puede que esta no sea una respuesta a su pregunta, pero creo que a las personas interesantes en su pregunta les puede resultar útil. Siéntase libre de mejorar los códigos. Hay algunos lugares que quizás desee mejorar.

  1. En lugar de codificar en duro la ubicación de iisexpress.exe, puede corregir mi código para leerlo en el registro.
  2. No incluí todos los argumentos soportados por iisexpress.exe
  3. No hice el manejo de errores. Por lo tanto, si el proceso de IISExpress no se pudo iniciar por algún motivo (por ejemplo, el puerto está en uso), no lo sé. Creo que la forma más fácil de solucionarlo es monitorear la transmisión StandardError y lanzar una excepción si obtengo algo de la corriente StandardError
+2

¡Guau! ¡Muchas gracias por esta respuesta detallada! Creo que siempre que no haya una documentación COM completa/correcta en MSDN, esta parece ser la solución perfecta. Lo intentaré tan pronto como vuelva a la PC. – Mike

+3

Ok, lo probé, funciona perfectamente.Gracias de nuevo :-) – Mike

+0

Excelente, el mensaje de parada funcionó como un amuleto. Estoy ejecutando IIS Express desde la línea de comandos, pero esta era la pieza que faltaba. ¡Gracias! – jpierson

3

Siento que lo estás haciendo de una manera difícil. Dé una pista de esta pregunta Automatically stop/restart ASP.NET Development Server on Build y vea si puede adoptar el mismo proceso.

Respondiendo a tu pregunta, creo que pinvoke.net podría ayudarte. También tienen muchos ejemplos que pueden ayudarlo a construir su solución.

+0

Pradeep, gracias por su respuesta. La solución del primer enlace no funciona para mí porque matan el proceso con proc.Kill(); pinvoke.net es muy interesante, gracias por la sugerencia. Desafortunadamente no he encontrado información sobre cómo usar la API IIS Express COM – Mike

+0

+1 para pinvoke.net – tomfanning

1

No, no hereda la interfaz. Puede crear una instancia de IISVersionManager con la nueva palabra clave . La forma en que obtiene una referencia a una instancia de IIISExpressProcessUtility no está del todo clara. Los documentos de MSDN son horribles. Tal vez puedas nuevo pero parece que no es compatible.

+0

Lea a través de http://stackoverflow.com/questions/1093536/how-does-the-c-compiler-detect- com-types si desea saber cómo funciona la actualización en una interfaz. – Pradeep

+1

Es COM específico, todos los métodos están expuestos a través de interfaces. –

+0

¡Muchas gracias por su apoyo! Hice algunas pruebas y actualicé mi publicación. – Mike

13

Aunque es demasiado tarde, daré una respuesta a esta pregunta.

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; 

Eso es todo. Luego puede llamar al método StopProcess en el objeto util.

Sin embargo, debe ser notificado por Microsoft.

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

Nota: La API de la versión de IIS apoya la infraestructura IIS Express y no pretende para ser utilizado directamente desde el código. "

+0

Gracias (+1). El compilador se quejó y me dijo que modificara la primera fila: IISVersionManagerLibrary.IISVersionManager mgr = new IISVersionManagerLibrary.IISVersionManager(); – ilmatte

+0

Gracias! Este código funciona bien para mí, pero creo que estoy un poco perdido en qué hacer a continuación. ¿Cómo puedo averiguar en qué puerto se está ejecutando? ¿Cómo puedo usar esto para lanzar un sitio que estoy desarrollando? –

+0

Desafortunadamente esto no funciona con el último (4.5+) de .NET .... error CS0656: Falta el compilador requerido miembro 'Microsoft.CSharp.RuntimeBinder.Binder.Convert' –

1

Si modifica el archivo web.config de la aplicación web, IIS (incluido Express) reiniciará el grupo de pp. Esto le permitirá implementar conjuntos actualizados.

Una forma de modificar web.config es copiarlo en un archivo nuevo y luego moverlo de nuevo.

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

Es posible que desee tener más control sobre IIS Express que simplemente reiniciar el grupo de aplicaciones. Pero si eso es todo lo que necesitas, esto funcionará.

1

He adoptado una solución diferente. Simplemente puede matar el árbol de proceso usando "taskkill" y el nombre del proceso. Esto funciona perfectamente a nivel local y en 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

Esta aplicación funciona para iniciar/detener IIS expreso mediante programación, se puede utilizar a partir de pruebas.

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

Esto es genial! Encontré una implementación un poco más completa de esto [aquí] (https://github.com/roryprimrose/Headless/blob/9caacee22acf9b24a6880dff695fb1cfb552f5fe/Headless.IntegrationTests/IisExpress.cs). – orad

+0

'Environment.Is64BitOperatingSystem' es la opción incorrecta aquí. Realmente depende de la fragilidad de los binarios para los que está construido el sitio. – jpmc26

3

Harvey Kwok había proporcionado una buena indirecta, ya que quiero arrancar y derribar el servicio al ejecutar los casos de prueba de integración. Pero los códigos de Harvey son demasiado largos con PInvoke y la mensajería.

Aquí hay una alternativa.

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

Y en mi traje de prueba de integración con MS prueba, tengo

 [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

Puede encontrar que el patrón de IDisposable habitual no se usa aquí. Sin embargo, el recurso de proceso se libera en realidad en el método ClassCleanup/desmontaje, y el proceso de prueba de traje es corto en vivo. – ZZZ

1

Figura I tiraría mi solución aquí también. Derivado de la solución de SeongTae Jeong y otra publicación (No recuerdo dónde ahora).

  1. Instalar Microsoft.Web.Administrationnuget.
  2. Consulte la biblioteca de tipo COM IIS Installed Versions Manager Interface como se indicó anteriormente.
  3. Añadir la siguiente clase:

    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. Configuración de su dispositivo de prueba de la siguiente manera. La ruta es relativa a la carpeta bin de su suite de pruebas.

    [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"); 
        } 
    } 
    

Y un punto más si esto va a funcionar también en un servidor de compilación. Primero necesitará install IIS Express on the build server. En segundo lugar, deberá crear un applicationhost.config en el servidor de compilación. Puede copiar uno de su cuadro dev bajo C:\Users\<User>\Documents\IISExpress\config\. Debe copiarse en la ruta correspondiente del usuario en el que se ejecuta su servidor de compilación. Si se está ejecutando como sistema, la ruta sería C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\.

Cuestiones relacionadas