Porting IIS ServiceMonitor to .Net
TLDR: IIS ServiceMonitor was ported because of a bug in the C++ implementation. It was a fun exercise and the code can be found on github.
If you don’t know what IIS ServiceMonitor is, then you probably don’t work with Windows Docker containers. IIS ServiceMonitor is the entrypoint of Windows Docker containers that are running IIS for ASP.Net or WCF. It monitors the w3svc service and if it stops, the process, and therefore the container, stops.
There is also one more thing that ServiceMonitor does and that’s update environment variables. You see, an IIS application pool is a segregated process and doesn’t have access to all environment variables present in the system. That means that if you start a Docker container and add environment variables, anything running in IIS won’t have access to those custom environment variables. Since ServiceMonitor isn’t running in IIS, it has access to all environment variable. What it then does is check the environment variables in the system against a blacklist and any environment variable not on the blacklist is put into the applicationHost.config file as a custom environment variable for the Default App Pool.
Let’s talk about environment variables
Environment variables in Windows have some weird properties. Environment variables are case-insensitive. You can test this by going to the Environment Variables dialog in Windows, adding an environment variable called aa with some value. Then you add another new one and call it AA with some other value. AA will overwrite aa. However, when working with environment variables in .Net and get them using Environment.GetEnvironmentVariables(), they’re case-sensitive because they get put in a Hashmap. This can be remedied by using Linq to convert the Hashmap to a Dictionary with a case-insensitive key comparer.
Why am I ranting about environment variables?
Let’s go back to the ServiceMonitor. Like I mentioned before, it copies environment variables that aren’t found on a blacklist to the applicationHost.config file for Default App Pool. The problem is that, as of the time of writing, there is an unresolved bug in IIS ServiceMonitor. Well, it’s marked as resolved, but it’s still present version 18.104.22.168 of ServiceMonitor that is in the latest Windows Docker containers. This but causes the keys of environment variables that are copied to applicationHost.config to be converted to upper case.
Why does this matter?
Well, if you decided to use the ConfigurationBuilder to use environment variables in AppSettings, then you would have to use an upper case key for everything. This would be a problem for established legacy application that are being moved to a Docker container.
Porting to .Net
I decided to fix the bug myself. However, IIS ServiceMonitor is written in C++ and I’m not familiar with the nuances of C++. I got the gist of the code and what was generally happening, but I didn’t have time to figure out the exact reason for the bug. Also, since it was such a small application, I decided to just port it. To me, C# is much more readable.
The program takes in two arguments. The first is the service to be monitored and the second being the app pool to add environment variables to, which is defaulted to DefaultAppPool. The app pool only gets the environment variables if the service being monitored is w3svc. The flow of the application was basically the following.
- if w3svc
- Stop the service being monitored
- Remove all environment variables not on the blacklist
- Re-add all environment variables not on the blacklist
- Start service
- Monitor service
- Stop process when service is stopped or paused
There’s honestly not a lot to say about the code. It’s not that complex and kind of speaks for itself. I did have some fun with this project by adding better logging and newer patterns and practices.
I have uploaded a repo with the service monitor and demos on github. There are three Dockerfiles included. The first two are demos
PS> docker build --rm -t solid/demo/website-with-service-monitor -f Dockerfile_WebsiteWithServiceMonitor . PS> docker build --rm -t solid/demo/website-without-service-monitor -f Dockerfile_WebsiteWithoutServiceMonitor .
Running these containers will enable you to open the websites which list the environment variables available at runtime.
PS> docker run -d --rm -p 8081:80 -e some_environment_variable='value' solid/demo/website-with-service-monitor:latest PS> docker run -d --rm -p 8082:80 -e some_environment_variable='value' solid/demo/website-without-service-monitor:latest
If you navigate to http://localhost:8081 and http://localhost:8082, you will see that some_environment_variable will have it’s key in uppercase on http://localhost:8082.
Using the .Net IIS ServiceMonitor
To create your base image, you can clone the repo and run the Dockerfile that’s included.
FROM microsoft/dotnet-framework:4.7.2-sdk AS base # if your company or enterprise is using ssl interception, this is where you need to install certs for the egress # RUN Import-Certificate -FilePath -CertStoreLocation FROM base as servicemonitor WORKDIR /src COPY ./ServiceMonitor . RUN dotnet build ServiceMonitor.csproj -c Release -o c:\output FROM mcr.microsoft.com/dotnet/framework/aspnet:4.7.2 COPY --from=servicemonitor /output /
You could tag this container with any name you want and use it as a base image for you ASP.Net applications. This ServiceMonitor implementation could also be used in the mcr.microsoft.com/dotnet/framework/wcf:4.7.2 image from Microsoft.