Запуск и остановка программного обеспечения IIS Express
Я пытаюсь создать небольшое приложение на С#, которое должно запускать/останавливать рабочий процесс IIS Express. Для этого я хочу использовать официальный "API-интерфейс IIS Express", который документирован в MSDN: http://msdn.microsoft.com/en-us/library/gg418415.aspx
Насколько я понимаю, API основан (только) на интерфейсах COM. Чтобы использовать эти COM-интерфейсы, я добавил ссылку на библиотеку COM в VS2010 через ссылку "Добавить ссылку" → "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();
}
}
Как вы можете видеть, я не профессиональный разработчик. Может кто-то указать мне в правильном направлении.
Любая помощь приветствуется.
Обновление 1:
В соответствии с предложениями я пробовал следующий код, который, к сожалению, не работает:
Ок, он может быть создан, но я не вижу, как использовать этот объект...
![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)).
Ответы
Ответ 1
Я пытался сделать подобное. Я пришел к выводу, что библиотека 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();
}
}
Это не может быть ответом на ваш вопрос, но я думаю, что люди, интересные в вашем вопросе, могут найти мою работу полезной. Не стесняйтесь улучшать коды. Есть некоторые места, которые вы можете улучшить.
- Вместо жесткого кодирования местоположения iisexpress.exe вы можете исправить мой код для чтения из реестра.
- Я не включил все аргументы, поддерживаемые iisexpress.exe
- Я не занимался обработкой ошибок. Итак, если процесс IISExpress не запускался по некоторым причинам (например, порт используется), я не знаю. Я думаю, что самый простой способ исправить это - контролировать поток StandardError и исключать исключение, если я получаю что-либо из потока StandardError.
Ответ 2
Хотя, слишком поздно, я дам ответ на этот вопрос.
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-интерфейс диспетчера версий (IIS Express); http://msdn.microsoft.com/en-us/library/gg418429(v=VS.90).aspx
Примечание. API-интерфейс диспетчера версий IIS поддерживает IIS Express инфраструктура и не предназначены для использования непосредственно из вашего кода."
Ответ 3
Эта реализация работает для запуска/остановки 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();
}
}
}
Ответ 4
Я чувствую, что вы делаете это с трудом. Возьмите намек на этот вопрос Автоматически остановить/перезапустить ASP.NET Development Server на сборке и посмотреть, можете ли вы принять тот же процесс.
Отвечая на ваш вопрос, я думаю, pinvoke.net может вам помочь. У них также много примеров, которые могут помочь вам в создании вашего решения.
Ответ 5
Харви Квок дал хороший намек, так как я хочу разорвать и разорвать службу при запуске тестов интеграции. Но коды 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();
}
Ответ 6
Нет, вы не наследуете интерфейс. Вы можете создать экземпляр IISVersionManager с новым ключевым словом. То, как вы получаете ссылку на экземпляр IIISExpressProcessUtility, совершенно неясно. Документы MSDN ужасны. Возможно, вы можете новый, но не похоже, что он поддерживает это.
Ответ 7
Если вы измените файл 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, чем просто перезапуск пула приложений. Но если это все, что вам нужно, это будет работать.
Ответ 8
Я принял другое решение. Вы можете просто убить дерево процессов, используя "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);
}
Ответ 9
Рисунок. Я тоже брошу свое решение. Получается из решения SeongTae Jeong и другого сообщения (не помню, где сейчас).
- Установите
Microsoft.Web.Administration
nuget.
- Ссылка на библиотеку типов
IIS Installed Versions Manager Interface
COM, как указано выше.
-
Добавьте следующий класс:
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();
}
}
}
}
-
Установите тестовое устройство следующим образом. Путь относится к папке 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");
}
}
И еще один момент, если он также будет запущен на сервере сборки. Сначала вам нужно установить IIS Express на сервере сборки. Во-вторых, вам нужно создать applicationhost.config
на сервере сборки. Вы можете скопировать его из своего окна dev в C:\Users\<User>\Documents\IISExpress\config\
. Его нужно скопировать на соответствующий путь пользователя, на котором работает ваш сервер сборки. Если он работает как система, тогда путь будет C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\
.