Get the version information of an installed service? - c#

I want to check programmatically that the latest version of my Windows Service is installed. I have:
var ctl = ServiceController.GetServices().Where(s => s.ServiceName == "MyService").FirstOrDefault();
if (ctl != null) {
// now what?
}
I don't see anything on the ServiceController interface that will tell me the version number. How do I do it?

I am afraid there is no way other than getting the executable path from the registry as ServiceController does not provide that information.
Here is a sample I had created before:
private static string GetExecutablePathForService(string serviceName, RegistryView registryView, bool throwErrorIfNonExisting)
{
string registryPath = #"SYSTEM\CurrentControlSet\Services\" + serviceName;
RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView).OpenSubKey(registryPath);
if(key==null)
{
if (throwErrorIfNonExisting)
throw new ArgumentException("Non-existent service: " + serviceName, "serviceName");
else
return null;
}
string value = key.GetValue("ImagePath").ToString();
key.Close();
if(value.StartsWith("\""))
{
value = Regex.Match(value, "\"([^\"]+)\"").Groups[1].Value;
}
return Environment.ExpandEnvironmentVariables(value);
}
After getting the exe path, just use FileVersionInfo.GetVersionInfo(exePath) class to get the version.

If you own the service, you can put version information into the DisplayName, e.g. DisplayName="MyService 2017.06.28.1517". This allows you to find an existing installation of your service and parse the version information:
var ctl = ServiceController
.GetServices()
.FirstOrDefault(s => s.ServiceName == "MyService");
if (ctl != null) {
// get version substring, you might have your own style.
string substr = s.DisplayName.SubString("MyService".Length);
Version installedVersion = new Version(substr);
// do stuff, e.g. check if installed version is newer than current assembly.
}
This may be useful if you want to avoid the registry. The problem is, that service entries can go to different parts of the registry depending on the installation routine.

If you are talking about getting the current version of your service automatically from the assembly properties then you can set up a property such as below in your ServiceBase class.
public static string ServiceVersion { get; private set; }
Then in your OnStart method add the following...
ServiceVersion = typeof(Program).Assembly.GetName().Version.ToString();
Full Example
using System.Diagnostics;
using System.ServiceProcess;
public partial class VaultServerUtilities : ServiceBase
{
public static string ServiceVersion { get; private set; }
public VaultServerUtilities()
{
InitializeComponent();
VSUEventLog = new EventLog();
if (!EventLog.SourceExists("Vault Server Utilities"))
{
EventLog.CreateEventSource("Vault Server Utilities", "Service Log");
}
VSUEventLog.Source = "Vault Server Utilities";
VSUEventLog.Log = "Service Log";
}
protected override void OnStart(string[] args)
{
ServiceVersion = typeof(Program).Assembly.GetName().Version.ToString();
VSUEventLog.WriteEntry(string.Format("Vault Server Utilities v{0} has started successfully.", ServiceVersion));
}
protected override void OnStop()
{
VSUEventLog.WriteEntry(string.Format("Vault Server Utilities v{0} has be shutdown.", ServiceVersion));
}
}
In the example above my event log displays the current version of my service...

Related

Adding solution items from VisualStudio extension

I am making a test VisualStudio extension that adds new items to solution. I found the way of doing it through trial and error, but I am not satisfied with the complexity of this solution and have some opened questions.. Here is the code:
using EnvDTE;
using EnvDTE80;
//..
namespace MyFirstExtension
{
internal sealed class AddFileToSolution
{
public const int CommandId = PackageIds.CmdCreateVaultHelperConfig;
public static readonly Guid CommandSet = new Guid(PackageGuids.guidVaultHelperVsExtensionPackageCmdSetString);
private readonly AsyncPackage package;
private AddFileToSolution(AsyncPackage package, OleMenuCommandService commandService)
{
this.package = package ?? throw new ArgumentNullException(nameof(package));
commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
var menuCommandID = new CommandID(CommandSet, CommandId);
var menuItem = new MenuCommand(this.Execute, menuCommandID);
commandService.AddCommand(menuItem);
}
public static AddFileToSolution Instance { get; private set; }
private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider { get { return this.package; } }
public static async Task InitializeAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
OleMenuCommandService commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
Instance = new AddFileToSolution(package, commandService);
}
private async void Execute(object sender, EventArgs e)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
string fileFullPath = #"C:\Users\myusr\source\repos\VsExtensionTesting2\0.txt";
var dte = await package.GetServiceAsync(typeof(DTE));
var dte2 = dte as DTE2;
var solution2 = (Solution2)dte2.Solution;
// following line throws Exception!
//solution2.AddFromFile(fileFullPath);
bool isSolutionItemsFolderExists = IsSolutionItemsFolderExists(solution2);
if (!isSolutionItemsFolderExists)
{
solution2.AddSolutionFolder("Solution Items");
}
AddToSolutionItems(solution2, fileFullPath);
}
public bool IsSolutionItemsFolderExists(Solution2 solution2)
{
foreach (var solutionItemObj in solution2)
{
var solutionItem = solutionItemObj as Project;
string name = solutionItem.Name;
if (name == "Solution Items")
{
return true;
}
}
return false;
}
public void AddToSolutionItems(Solution2 solution2, string fileFullPath)
{
foreach (var solutionItemObj in solution2)
{
var solutionItem = solutionItemObj as Project;
string name = solutionItem.Name;
if (name == "Solution Items")
{
solutionItem.ProjectItems.AddFromFile(fileFullPath);
}
}
}
}
}
Here I am using the Visual Studio 2019 VSIX project template. It uses AsyncPackage by default. What I am trying to accomplish is to execute command Project.AddExistingItem on solution level.
Questions are:
In my solution - why does calling the command solution2.AddFromFile(fileFullPath); directly throws exception?
System.Runtime.InteropServices.COMException: 'The template specified cannot be found. Please check that the full path is correct.'
The file 100% exists in the directory.. Ideally I want to avoid creating Solution Items folder myself and let the API handle it..
When iterating the solution items/projects I am casting the items to type Project, however, during the debugging I can see that the item is also represented by type System.__ComObject and Microsoft.VisualStudio.ProjectSystem.VS.Implementation.Package.Automation.OAProject however it's not possible to cast the item to either of the 2 types.. What are these other 2 types (or the correlation between 3 types) and how do I know exactly which type I am supposed to cast the solution items to?
What is the use-case for finding the commands executed by VS? I have downloaded the command explorer and captured the Project.AddExistingItem command, but how do I actually use this knowledge to execute the command from code? Here is how the command looks like:
I would be glad to hear some explanations about the questions I mentioned above.. My experience with the VS extension building so far - it's lots of googling and trial and error.. (as opposed to knowing actually what to do :) )

Check whether OleDb 12.0 driver for MS Access is installed or not

I have a C# application which uses OleDb 12.0 driver to connect to an MS Access database. If the OleDb 12.0 driver is not installed, the application throws an exception which is not relevantly explanatory.
public static class Program
{
private static Mutex mutex = null;
[STAThread]
static void Main()
{
try
{
InMemoryValues.CorrectnessRepetition = 15;
InMemoryValues.MultipleChoiceCount = 6;
}
catch(Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
... ...
... ...
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new CollectionForm());
}
}
I want to let users know about the specific problem of not having a driver. I don't want to hard-code the information.
How should I do that?
Relevant Source Code
public static class InMemoryValues
{
private static ApplicationData _appData = null;
public static int CorrectnessRepetition { get; set; }
public static int MultipleChoiceCount { get; set; }
static InMemoryValues()
{
_appData = ApplicationDataBLLL.Get();
if (_appData == null)
{
_appData = new ApplicationData();
}
}
public static ApplicationData ApplicationData
{
set
{
_appData = value;
}
get
{
return _appData;
}
}
public static void Save()
{
ApplicationDataBLLL.Save(_appData);
}
}
Using OleDbEnumerator.GetElements() you can get a DataTable which contains list of all visible OLE DB providers.
The first column of the returned data table, is SOURCES_NAME which is the invariant name of the native OLEDB data source or enumerator.
So you can use the following code to find out if Microsoft.ACE.OLEDB.12.0 is installed and visible to the executing process:
var oledb12Installed = new System.Data.OleDb.OleDbEnumerator()
.GetElements().AsEnumerable()
.Any(x => x.Field<string>("SOURCES_NAME") ==
"Microsoft.ACE.OLEDB.12.0");
Bitness
Keep in mind, if you are compiling the application for X86 while you have installed X64 versions of the Microsoft.ACE.OLEDB.12.0, then above code will return false which means your application cannot use Microsoft.ACE.OLEDB.12.0.
You need to use the package with the same bitness as your application. You can download the provider from the following link:
Microsoft Access Database Engine 2016 Redistributable
After clicking on Download button, you have the choice to download x86 or x64.

C# Code Contracts with Builder Pattern - "Possibly calling a method on a null reference"

I'm investigating Code Contracts and I'm implementing the Builder Pattern like this:
public class PersonCaution
{
private PersonCaution()
{
}
public string CautionType { get; private set; }
public string Remarks { get; private set; }
public class Builder
{
private string _cautionType;
private string _remarks;
public Builder WithCautionType(string value)
{
Contract.Ensures(Contract.Result<Builder>() != null);
_cautionType = value;
return this;
}
public Builder WithRemarks(string value)
{
Contract.Ensures(Contract.Result<Builder>() != null);
_remarks = value;
return this;
}
public PersonCaution Build()
{
Contract.Ensures(Contract.Result<PersonCaution>() != null);
return new PersonCaution
{
CautionType = _cautionType,
Remarks = _remarks
};
}
}
}
Here's a snippet showing how I use the Builder class:
if (row != null)
{
var builder = new PersonCaution.Builder()
.WithCautionType((string)row.Element("PersonCaution__Type1G"))
.WithRemarks((string)row.Element("PersonCaution__Remarks"));
if (builder != null)
{
personCautions.Add(builder.Build());
}
}
However, the Code Contracts static checker fails with this error:
Possibly calling a method on a null reference. Do you expect that NWP.PointServices.Domain.Model.People.PersonCaution+Builder.WithCautionType(System.String) returns non-null?
Q. I thought that the Contract.Ensures postcondition would satisfy the static checker, but it doesn't. What do I need to do to remove the error? Thanks v much.
Note. I only see the issue if the Builder class is in a separate Project to the code which calls it.
More info:
Visual Studio Professional 2015 14.0.25424.00 Update 3
All projects target .Net 4.6.1
Code Contracts installed via Visual Studio Extension, v 1.8
I'm successfully using Code Contracts in other (non Builder) areas of the project
As we have found out, all you need is to enable build for contract references from CC project tab to enable cross-project analysis ("Contract Reference Assembly" = "Build")

Using InstallUtil to install a Windows service with startup parameters

I am using InstallUtil to install my service and I just cannot figure out how to specify the startup parameters for it!
Here is my Installer subclass:
[RunInstaller(true)]
public class ServerHostInstaller : Installer
{
private ServiceInstaller m_serviceInstaller;
private ServiceProcessInstaller m_serviceProcessInstaller;
private static string s_usage = "Usage:\ninstallutil /i /username=<user_name> /password=<user_password> NCStub.Server.Host.exe";
public ServerHostInstaller()
{
m_serviceInstaller = new ServiceInstaller();
m_serviceInstaller.ServiceName = Program.ServiceName;
m_serviceInstaller.DisplayName = Program.ServiceName;
m_serviceInstaller.StartType = ServiceStartMode.Automatic;
m_serviceProcessInstaller = new ServiceProcessInstaller();
m_serviceProcessInstaller.Account = ServiceAccount.User;
Installers.Add(m_serviceInstaller);
Installers.Add(m_serviceProcessInstaller);
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
string userName = this.Context.Parameters["username"];
if (userName == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'username'");
}
string userPass = this.Context.Parameters["password"];
if (userPass == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'password'");
}
m_serviceProcessInstaller.Username = userName;
m_serviceProcessInstaller.Password = userPass;
}
}
Can anyone indicate how do I specify the service startup parameters?
Found it.
I have rewritten the Install method like so:
public override void Install(IDictionary stateSaver)
{
string userName = this.Context.Parameters["username"];
if (userName == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'username'");
}
string userPass = this.Context.Parameters["password"];
if (userPass == null)
{
Console.WriteLine(s_usage);
throw new InstallException("Missing parameter 'password'");
}
m_serviceProcessInstaller.Username = userName;
m_serviceProcessInstaller.Password = userPass;
var path = new StringBuilder(Context.Parameters["assemblypath"]);
if (path[0] != '"')
{
path.Insert(0, '"');
path.Append('"');
}
path.Append(" --service");
Context.Parameters["assemblypath"] = path.ToString();
base.Install(stateSaver);
}
Although, I give the predefined command line parameters (--service), the code is easily adaptable to pass real command line arguments, just use the same pattern for passing the username and password parameters.
I know this is an old post but thought I'd post my response. I did this in a .net 4 service using the BeforeInstall event.
The ServiceProcessInstaller's BeforeInstall event:
private void serviceProcessInstaller1_BeforeInstall(object sender, InstallEventArgs e)
{
System.ServiceProcess.ServiceProcessInstaller installer = sender as System.ServiceProcess.ServiceProcessInstaller;
if (installer != null)
{
//Get the existing assembly path parameter
StringBuilder sbPathWIthParams = new StringBuilder(installer.Context.Parameters["assemblypath"]);
//Wrap the existing path in quotes if it isn't already
if (!sbPathWIthParams[0].Equals("\""))
{
sbPathWIthParams.Insert(0, "\"");
sbPathWIthParams.Append("\"");
}
//Add desired parameters
sbPathWIthParams.Append(" test");
//Set the new path
installer.Context.Parameters["assemblypath"] = sbPathWIthParams.ToString();
}
}
The installed service looks as follows:
It executes fine, and I can examine the parameters from within the main function of the service.

Starting and stopping IIS Express programmatically

I am trying to build a small application in C# which should start/stop an IIS Express worker process. For this purpose I want to use the official "IIS Express API" which is documented on MSDN: http://msdn.microsoft.com/en-us/library/gg418415.aspx
As far as I understand, the API is based (only) on COM interfaces. To use this COM interfaces I've added a reference to the COM library in VS2010 via Add Reference -> COM -> "IIS Installed Versions Manager Interface":
So far so good, but what's next? There is an IIISExprProcessUtility interface available which includes the the two "methods" to start/stop an IIS process. Do I have to write a class which implements this 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();
}
}
As you can see, I'm not a professional developer. Can someone point me in the right direction.
Any help is greatly appreciated.
Update 1:
According to the suggestions I've tried the following code which unfortunately doesn't work:
Ok, it can be instantiated but I cannot see how to use this object...
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)).
I was trying to do similar thing. I concluded that the COM library provided by Microsoft is incomplete. I don't use it because the doc mentioned that "Note: This topic is pre-release documentation and is subject to change in future releases".
So, I decided to take a look at what IISExpressTray.exe is doing. It seems to be doing similar things.
I disassemble the IISExpressTray.dll and found that there is no magic in listing out all the IISexpress processes and stoping the IISexpress process.
It doesn't call that COM library. It doesn't lookup anything from registry.
So, the solution I ended up is very simple. To start an IIS express process, I just use Process.Start() and pass in all the parameters I need.
To stop an IIS express process, I copied the code from IISExpressTray.dll using reflector. I saw it simply sends a WM_QUIT message to the target IISExpress process.
Here is the class I wrote to start and stop an IIS express process. Hope this can help somebody else.
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();
}
}
I don't need to list all the existing IIS express process. If you need that, from what I saw in the reflector, what IISExpressTray.dll does is to call Process.GetProcessByName("iisexpress", ".")
To use the class I provided, here is a sample program I used to test it.
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();
}
}
This may not be an answer to your question but I think people interesting in your question may find my work useful. Feel free to improve the codes. There are some places that you might want to enhance.
Instead of hardcoding the iisexpress.exe location, you can fix my code to read from the registry.
I didn't include all the arguments supported by iisexpress.exe
I didn't do error handling. So, if the IISExpress process failed to start for some reasons (e.g. port is in used), I don't know. I think the easiest way to fix it is to monitor the StandardError stream and throw exception if I get anything from StandardError stream
Although, it's too late, I'll provide an answer to this 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;
That's it. Then you can call StopProcess method on util object.
However, you have to get notice from Microsoft.
" Version Manager API (IIS Express) ;
http://msdn.microsoft.com/en-us/library/gg418429(v=VS.90).aspx
Note: The IIS Version Manager API
supports the IIS Express
infrastructure and is not intended
to be used directly from your code.
"
This implementation works for starting/stopping IIS Express programmatically, can be used from 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();
}
}
}
I feel you are doing it in a hard way. Take a hint from this question Automatically stop/restart ASP.NET Development Server on Build and see if you can adopt the same process.
Answering your question, I think pinvoke.net might help you. They have lot of examples as well which can help you build your solution.
Harvey Kwok had provided a good hint, since I want to tear up and tear down the service when running integration test cases. But Harvey codes is too long with PInvoke and messaging.
Here's an 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();
}
}
And in my integration test suit with MS Test, I have
[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();
}
Here is my solution too. It runs IIS Express with hidden windows. Manager class controls several IIS Express instances.
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();
}
}
}
I need several instances. Designed manager class to control them.
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();
}
}
}
Figure I'd throw my solution in here too. Derived from the SeongTae Jeong's solution and another post (Can't remember where now).
Install the Microsoft.Web.Administration nuget.
Reference the IIS Installed Versions Manager Interface COM type library as noted above.
Add the following class:
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();
}
}
}
}
Setup your test fixture as follows. The path is relative to the bin folder of your test suite.
[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");
}
}
And one more point if this is going to also run on a build server. First you will need to install IIS Express on the build server. Second, you'll have to create an applicationhost.config on the build server. You can copy one from your dev box under C:\Users\<User>\Documents\IISExpress\config\. It needs to be copied to the corresponding path of the user your build server is running as. If it is running as system then the path would be C:\Windows\System32\config\systemprofile\Documents\IISExpress\config\.
No, you don't inherit the interface. You can create an instance of IISVersionManager with the new keyword. How that gets you a reference to an IIISExpressProcessUtility instance is completely unclear. The MSDN docs are awful. Maybe you can new one but it doesn't look like it supports that.
If you modify the web.config file of the web application, IIS (including Express) will restart the app pool. This will allow you to deploy updated assemblies.
One way to modify web.config is to copy it to a new file, and then move it back.
copy /Y path/web.config path/web_touch.config
move /Y path/web_touch.config path/web.config
You may want more control over IIS Express than simply restarting the app pool. But if that's all you need, this will work.
I have adopted a different solution. You can simply kill the process tree using "taskkill" and the name of the process.
This works perfectly locally and on TFS 2013
public static void FinalizeIis()
{
var startInfo = new ProcessStartInfo
{
UseShellExecute = false,
Arguments = string.Format("/F /IM iisexpress.exe"),
FileName = "taskkill"
};
Process.Start(startInfo);
}

Categories

Resources