web-dev-qa-db-fra.com

Départ et arrêt IIS Express par programmation 

J'essaie de créer une petite application en C # qui devrait démarrer/arrêter un processus de travail IIS Express. Pour ce faire, je souhaite utiliser "l'API IIS Express" officielle, documentée sur MSDN: http://msdn.Microsoft.com/en-us/library/gg418415.aspx

Autant que je sache, l'API est basée (uniquement) sur les interfaces COM. Pour utiliser ces interfaces COM, j'ai ajouté une référence à la bibliothèque COM dans VS2010 via Ajouter une référence -> COM -> "Interface du gestionnaire de versions installées IIS":

Jusqu'ici tout va bien, mais quelle est la prochaine? Il existe une interface IIISExprProcessUtility qui inclut les deux "méthodes" pour démarrer/arrêter un processus IIS. Dois-je écrire une classe qui implémente cette 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();
    }
} 

Comme vous pouvez le constater, je ne suis pas un développeur professionnel. Quelqu'un peut-il me diriger dans la bonne direction ... Toute aide est grandement appréciée.

Mise à jour 1: Suivant les suggestions, j'ai essayé le code suivant qui ne fonctionne malheureusement pas:

alt textOk, il peut être instancié mais je ne vois pas comment utiliser cet objet ...

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
Mike

J'essayais de faire la même chose. J'ai conclu que la bibliothèque COM fournie par Microsoft est incomplète. Je ne l'utilise pas parce que le document mentionnait "Remarque: cette rubrique est une documentation préliminaire pouvant être modifiée dans les prochaines versions".

J'ai donc décidé de jeter un coup d'œil à ce que fait IISExpressTray.exe. Il semble faire des choses similaires. 

Je démonte IISExpressTray.dll et constate qu’il n’ya pas de magie dans la liste de tous les processus IISexpress et que l’arrêt du processus IISexpress.

Il n'appelle pas cette bibliothèque COM. Il ne cherche rien dans le registre.

Donc, la solution que j'ai trouvée est très simple. Pour démarrer un processus express IIS, il suffit d'utiliser Process.Start () et de lui transmettre tous les paramètres nécessaires.

Pour arrêter un processus express IIS, j'ai copié le code à partir de IISExpressTray.dll à l'aide de réflecteur. J'ai vu qu'il envoie simplement un message WM_QUIT au processus IISExpress cible.

Voici le cours que j'ai écrit pour démarrer et arrêter un processus express IIS. J'espère que cela peut aider quelqu'un d'autre.

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

Je n'ai pas besoin de répertorier tous les processus express IIS existants. Si vous avez besoin de cela, d'après ce que j'ai vu dans le réflecteur, IISExpressTray.dll appelle la fonction Process.GetProcessByName("iisexpress", ".").

Pour utiliser le cours que j'ai fourni, voici un exemple de programme que j'ai utilisé pour le tester.

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

Ce n'est peut-être pas une réponse à votre question mais je pense que les personnes intéressantes dans votre question pourraient trouver mon travail utile. N'hésitez pas à améliorer les codes. Vous voudrez peut-être améliorer certains endroits.

  1. Au lieu de coder en dur l'emplacement iisexpress.exe, vous pouvez corriger le code à lire dans le registre.
  2. Je n'ai pas inclus tous les arguments pris en charge par iisexpress.exe
  3. Je n'ai pas traité d'erreur. Ainsi, si le processus IISExpress n'a pas pu démarrer pour certaines raisons (par exemple, le port est utilisé), je ne le sais pas. Je pense que le moyen le plus simple de résoudre ce problème est de surveiller le flux StandardError et de lancer une exception si le flux StandardError est généré
58
Harvey Kwok

Bien qu'il soit trop tard, je vais répondre à cette 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;

C'est tout. Ensuite, vous pouvez appeler la méthode StopProcess sur un objet util.

Cependant, vous devez recevoir une notification de Microsoft.

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

Remarque: l'API IIS Version Manager prend en charge le IIS Express infrastructure et n’est pas destiné à utiliser directement à partir de votre code. "

13
SeongTae Jeong

Cette implémentation fonctionne pour démarrer/arrêter IIS Express par programmation, peut être utilisée à partir de tests.

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

Harvey Kwok avait fourni un bon indice, car je souhaite mettre fin au service lors de la réalisation de scénarios de test d'intégration. Mais les codes Harvey sont trop longs avec PInvoke et la messagerie.

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

Et dans ma combinaison de test d'intégration avec MS Test, j'ai

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

Je pense que vous le faites de manière difficile. Inspirez-vous de cette question Arrêtez/redémarrez automatiquement le serveur de développement ASP.NET sur Build et voyez si vous pouvez adopter le même processus.

En réponse à votre question, je pense que pinvoke.net pourrait vous aider. Ils ont également de nombreux exemples qui peuvent vous aider à élaborer votre solution.

3
Pradeep

Figure je jetterais ma solution ici aussi. Dérivé de la solution de SeongTae Jeong et d'un autre article (je ne me rappelle plus où maintenant).

  1. Installez le Microsoft.Web.Administrationnuget .
  2. Référencez la bibliothèque de types COM IIS Installed Versions Manager Interface comme indiqué ci-dessus.
  3. Ajouter la classe suivante:

    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. Configurez votre appareil de test comme suit. Le chemin est relatif au dossier bin de votre suite de tests.

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

Et un autre point si cela doit également être exécuté sur un serveur de compilation. Vous devez d’abord installer IIS Express sur le serveur de construction . Deuxièmement, vous devrez créer un applicationhost.config sur le serveur de construction. Vous pouvez en copier un de votre boîte de dev sous C:\Users\<User>\Documents\IISExpress\config\. Il doit être copié dans le chemin correspondant à l'utilisateur sous lequel votre serveur de génération est exécuté. S'il fonctionne en tant que système, le chemin serait C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\.

1
hcoverlambda

Si vous modifiez le fichier web.config de l'application Web, IIS (y compris Express) redémarrera le pool d'applications. Cela vous permettra de déployer des assemblys mis à jour.

Une façon de modifier web.config consiste à le copier dans un nouveau fichier, puis à le déplacer à nouveau.

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

Vous souhaiterez peut-être davantage de contrôle sur IIS Express que le simple redémarrage du pool d'applications. Mais si c'est tout ce dont vous avez besoin, cela fonctionnera.

1
Michael L Perry

Non, vous n'héritez pas de l'interface. Vous pouvez créer une instance de IISVersionManager avec le mot clé new . Comment cela vous permet-il d'obtenir une référence à une instance IIISExpressProcessUtility? Les documents MSDN sont affreux. Peut-être que vous pouvez nouveau un mais cela ne semble pas le supporter.

1
Hans Passant

J'ai adopté une solution différente. Vous pouvez simplement supprimer l’arbre de processus en utilisant "taskkill" et le nom du processus . Cela fonctionne parfaitement localement et sur TFS 2013

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

    Process.Start(startInfo);
}
1
Raffaeu

Voici ma solution aussi. Il exécute IIS Express avec des fenêtres cachées. La classe Manager contrôle plusieurs instances 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();
        }            
    }        
}

J'ai besoin de plusieurs instances. Conçu la classe de gestionnaire pour les contrôler.

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