Skip to content

Instantly share code, notes, and snippets.

@jrusbatch
Created December 11, 2012 13:42
Show Gist options
  • Save jrusbatch/4258651 to your computer and use it in GitHub Desktop.
Save jrusbatch/4258651 to your computer and use it in GitHub Desktop.
/// <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);
}
}
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