Created
December 11, 2012 13:42
-
-
Save jrusbatch/4258651 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Represents an IIS Express site.</summary> | |
public class IISExpress : IDisposable | |
{ | |
private readonly string applicationPath; | |
private readonly Uri address; | |
private Process process; | |
private bool disposed; | |
public static IISExpress CreateSite(string sitePath) | |
{ | |
return CreateSite(sitePath, GetRandomUnusedPort()); | |
} | |
public static IISExpress CreateSite(string sitePath, int port) | |
{ | |
if (!IsIISExpressInstalled) | |
{ | |
var msg = string.Format("IIS Express could not be found at the expected location ('{0}').", IISExpressPath); | |
throw new NotSupportedException(msg); | |
} | |
if (sitePath == null) | |
{ | |
throw new ArgumentNullException("sitePath"); | |
} | |
if (sitePath.Length == 0) | |
{ | |
throw new ArgumentException("Parameter is empty.", "sitePath"); | |
} | |
if (port < ushort.MinValue || port > ushort.MaxValue) | |
{ | |
var msg = string.Format("Port number was invalid. Port must be a value between {0} and {1}.", uint.MinValue, uint.MaxValue); | |
throw new ArgumentOutOfRangeException("port", port, msg); | |
} | |
if (!Directory.Exists(sitePath)) | |
{ | |
var msg = string.Format("The path specified could not be found: '{0}'.", sitePath); | |
throw new DirectoryNotFoundException(msg); | |
} | |
return new IISExpress(sitePath, port); | |
} | |
private IISExpress(string sitePath, int port) | |
{ | |
applicationPath = sitePath; | |
address = new Uri(string.Format("http://localhost:{0}/", port)); | |
} | |
public static string IISExpressPath | |
{ | |
get | |
{ | |
var programsPath = Environment.Is64BitOperatingSystem | |
? Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) | |
: Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); | |
return Path.Combine(programsPath, @"IIS Express\iisexpress.exe"); | |
} | |
} | |
public static bool IsIISExpressInstalled | |
{ | |
get { return File.Exists(IISExpressPath); } | |
} | |
public string ApplicationPath | |
{ | |
get { return applicationPath; } | |
} | |
public int ProcessId | |
{ | |
get { return process != null ? process.Id : 0; } | |
} | |
public Uri Address | |
{ | |
get { return address; } | |
} | |
/// <summary> | |
/// Starts IIS Express using the specified directory path and port.</summary> | |
public void Start() | |
{ | |
VerifyApplicationPathExists(); | |
if (process == null) | |
{ | |
process = CreateProcess(); | |
} | |
var startThread = new Thread(StartProcess) { IsBackground = true }; | |
using (var resetEvent = new ManualResetEvent(false)) | |
{ | |
DataReceivedEventHandler onOutputReceived = null; | |
onOutputReceived = (s, e) => | |
{ | |
if (e != null && string.Equals(e.Data, "IIS Express is running.")) | |
{ | |
process.OutputDataReceived -= onOutputReceived; | |
resetEvent.Set(); | |
} | |
}; | |
process.OutputDataReceived += onOutputReceived; | |
startThread.Start(); | |
resetEvent.WaitOne(); | |
} | |
Debug.WriteLine("PID #{0}: IIS Express is ready.", process.Id); | |
} | |
public void Stop() | |
{ | |
Process iisProcess; | |
if ((iisProcess = Interlocked.Exchange(ref process, null)) == null) | |
{ | |
Debug.WriteLine("Stop was called, but IIS Express was already stopped."); | |
return; | |
} | |
Debug.WriteLine("PID #{0}: Stopping IIS Express.", (object)iisProcess.Id); | |
var reset = new ManualResetEvent(false); | |
try | |
{ | |
EventHandler onExit = null; | |
onExit = (s, e) => | |
{ | |
iisProcess.Exited -= onExit; | |
reset.Set(); | |
}; | |
iisProcess.Exited += onExit; | |
IISExpressHelper.SendStopMessageToProcess(iisProcess.Id); | |
reset.WaitOne(); | |
} | |
finally | |
{ | |
reset.Dispose(); | |
iisProcess.OutputDataReceived -= OnOutputDataReceived; | |
iisProcess.ErrorDataReceived -= OnErrorDataReceived; | |
try { iisProcess.Kill(); } catch (Exception e) { Debug.Write(e.ToString()); } | |
try { iisProcess.Dispose(); } catch (Exception e) { Debug.Write(e.ToString()); } | |
} | |
} | |
public bool IsRunning() | |
{ | |
var iisProcess = process; | |
return iisProcess != null && IISExpressHelper.IsProcessRunning(iisProcess.Id); | |
} | |
public Process GetParentProcess() | |
{ | |
var currentProcessId = Process.GetCurrentProcess().Id; | |
int parentProcessId; | |
var options = new ObjectGetOptions(null, TimeSpan.FromSeconds(30.0), true); | |
using (var managementObject = new ManagementObject("win32_process.handle='" + currentProcessId + "'", options)) | |
{ | |
managementObject.Get(); | |
int.TryParse(managementObject["ParentProcessId"].ToString(), out parentProcessId); | |
} | |
if (parentProcessId != 0) | |
{ | |
return Process.GetProcessById(parentProcessId); | |
} | |
return null; | |
} | |
public void Dispose() | |
{ | |
Dispose(true); | |
GC.SuppressFinalize(this); | |
} | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (disposed) | |
{ | |
return; | |
} | |
disposed = true; | |
if (disposing) | |
{ | |
Stop(); | |
} | |
process = null; | |
} | |
private Process CreateProcess() | |
{ | |
var processArgs = BuildArguments(applicationPath, address.Port); | |
Debug.WriteLine("Starting IIS Express with the following arguments: {0}", (object)processArgs); | |
var info = new ProcessStartInfo(IISExpressPath) | |
{ | |
LoadUserProfile = true, | |
UseShellExecute = false, | |
RedirectStandardOutput = true, | |
RedirectStandardError = true, | |
RedirectStandardInput = true, | |
Arguments = processArgs | |
}; | |
return new Process { StartInfo = info, EnableRaisingEvents = true }; | |
} | |
private void StartProcess() | |
{ | |
process.OutputDataReceived += OnOutputDataReceived; | |
process.ErrorDataReceived += OnErrorDataReceived; | |
process.Start(); | |
process.BeginOutputReadLine(); | |
process.BeginErrorReadLine(); | |
process.WaitForExit(); | |
} | |
private static void OnErrorDataReceived(object sender, DataReceivedEventArgs e) | |
{ | |
if (e != null && e.Data != null) | |
{ | |
Console.Error.WriteLine("IIS Express: {0}", e.Data); | |
} | |
} | |
private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) | |
{ | |
if (e != null && e.Data != null) | |
{ | |
Console.Out.WriteLine("IIS Express: {0}", e.Data); | |
} | |
} | |
private void VerifyApplicationPathExists() | |
{ | |
Debug.WriteLine("Checking that the application exists in {0}.", (object)applicationPath); | |
if (!Directory.Exists(applicationPath)) | |
{ | |
var msg = string.Format("The application path specified could not be found: '{0}'.", applicationPath); | |
throw new DirectoryNotFoundException(msg); | |
} | |
} | |
private static int GetRandomUnusedPort() | |
{ | |
var listener = new TcpListener(IPAddress.Any, 0); | |
listener.Start(); | |
var port = ((IPEndPoint)listener.LocalEndpoint).Port; | |
listener.Stop(); | |
return port; | |
} | |
private static string BuildArguments(string appPath, int port) | |
{ | |
return string.Format(CultureInfo.InvariantCulture, "/path:\"{0}\" /port:{1} /systray:false", appPath, port); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal class IISExpressHelper | |
{ | |
internal static bool IsProcessRunning(int pid) | |
{ | |
if (pid <= 0) | |
{ | |
return false; | |
} | |
bool result; | |
try | |
{ | |
using (var processById = Process.GetProcessById(pid)) | |
{ | |
if (!processById.HasExited) | |
{ | |
return true; | |
} | |
} | |
result = false; | |
} | |
catch (ArgumentException) | |
{ | |
result = false; | |
} | |
catch (InvalidOperationException) | |
{ | |
result = false; | |
} | |
catch (Win32Exception) | |
{ | |
result = false; | |
} | |
catch (NotSupportedException) | |
{ | |
result = false; | |
} | |
return result; | |
} | |
public static void SendStopMessageToProcess(int processId) | |
{ | |
try | |
{ | |
var intPtr = NativeMethods.GetTopWindow(IntPtr.Zero); | |
while (intPtr != IntPtr.Zero) | |
{ | |
uint num; | |
NativeMethods.GetWindowThreadProcessId(intPtr, out num); | |
if (processId == num) | |
{ | |
var hWnd = new HandleRef(null, intPtr); | |
NativeMethods.PostMessage(hWnd, 18u, IntPtr.Zero, IntPtr.Zero); | |
break; | |
} | |
intPtr = NativeMethods.GetWindow(intPtr, 2u); | |
} | |
} | |
catch (ArgumentException) | |
{ | |
} | |
} | |
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment