IIS Express /protocol:https

IIS Express is awesome, a great upgrade from Cassini.  It supports https, Windows Authentication, persistent sites even if Visual Studio is closed, command-line launching, it’s great.  However combining them sometimes doesn’t work too well.

In Cassini (Visual Web Developer) I could launch it like this:

WebDev.WebServer40.EXE /port:1234 /path:C:pathtosite

This works wonderfully in IIS Express as well:

iisexpress.exe /port:1234 /path:C:pathtosite

This gets awkward when moving to https.  This doesn’t work:

iisexpress.exe /port:1234 /path:C:pathtosite /protocol:https

Why doesn’t it work?  Because iisexpress has no /protocol switch.  Bummer.  I flung a request for this and got a great response from Robert McMurray.

A Step Back Robert McMurray summarizes the two modes of IIS Express nicely as “Personal Web Server Mode” (e.g. “IIS Lite”) and “Application Server Mode” (e.g. “just launch this”).

“Personal Web Server Mode” is a durable version of (almost all of) IIS launched in the user’s context.  There’s a great system tray icon that centralizes all the running sites, and the applicationHost.config file is identical to regular IIS.  One can rig Windows Authentication, HTTPS, tracing, classic ASP, the works.  (Why “almost”?  Because there’s no tooling for configuration.  There are a few settings in Visual Studio, but nothing of consequence.  The perfect solution would be a way to point IIS Manager at it.  Unfortunately, typically the easiest way to configure IIS Express is to configure IIS proper, then diff their config files.  But I digress.)

“Application Server Mode” is much like Cassini was.  When you launch it, you specify /port: and /path:.  The unfortunate part of this mode is it presumes http.  To their credit, this mode is likely solely for backwards compatibility with Cassini, and Cassini didn’t support https at all.

Now What? My goal is to run IIS Express in “just do it” mode, pass in a

/port: and /path:, and test my https site.  All else being equal, a /protocol: parameter to iisexpress.exe would be easiest.  In its absence, let’s go hacking.  Back to Robert McMurray.  He proposes a batch file that uses appcmd.exe to rig adjustments to a cloned applicationHost.config, passing in IIS Express 8’s new /apphostconfig: flag, which specifies the config to modify.  It’s a bit of a long way around, but ok.  In theory it’s easier than brute-forcing some XPath.

A bit of tinkering, a lot of elbow grease, and an extra quarter or two into the command-line meter, and I have a batch file that allows me to launch IIS Express like this:

iisexpress.bat /port:1234 /path:C:pathtosite /protocol:https

The batch file takes care of shimming the difference between me using it like “Application Server Mode” and it running in “Personal Web Server Mode” (with a temporary config).  Thanks to Robert McMurray for pointing me in the right direction.

For compatibility’s sake, it has a PowerShell XPath mechanism for users of IIS Express 7.x, though for those with IIS Express 8, flip over into appcmd and you’re golden.

The Batch File

@echo off
setlocal enabledelayedexpansion

pushd "%~dp0"

REM because we're shifting this off the grid soon
set script_name=%0

if '%1'==" GOTO help

:defaults
set site_port=8080
set site_protocol=http


:get_args
if '%1'==" GOTO :args_done

set arg="%1″
set argv=%arg:/path:=%
if "%argv%"=="%arg%" goto not_path

set site_path=%argv:"=%
shift
goto get_args

:not_path

set argv=%arg:/port:=%
if "%argv%"=="%arg%" goto not_port

set site_port=%argv:"=%
shift
goto get_args

:not_port

set argv=%arg:/protocol:=%
if "%argv%"=="%arg%" goto not_https

set site_protocol=%argv:"=%
shift
goto get_args

:not_https

if '%1'=='/?' GOTO help
if '%1'=='/h' GOTO help
if '%1'=='-?' GOTO help
if '%1'=='-h' GOTO help

echo Invalid parameter: %1
goto help


:args_done


if "%site_path%"=="" GOTO no_path
if "%site_port%"=="" GOTO no_port
if not exist "%site_path%" GOTO invalid_path


:get_tempdir

set sitename=%TIME::=.%-%RANDOM%
set tempdir=%TEMP%iisexpress-temp%sitename%
if exist "%tempdir%" GOTO :get_tempdir
mkdir "%tempdir%"


:get_iisdir

if "%PROGRAMFILES(x86)%"=="" goto x86
set iis_dir=%PROGRAMFILES(x86)%IIS Express
goto iis_done
:x86
set iis_dir=%PROGRAMFILES%IIS Express
:iis_done
if not exist "%iis_dir%iisexpress.exe" GOTO iis_missing


:setup_iis

copy "%iis_dir%AppServerapplicationHost.config" "%tempdir%"

REM Mangle 'Development Web Site' to match what we're after

REM These need IIS Express 8:
REM "%iis_dir%appcmd.exe" set config -section:system.applicationHost/sites /"[name='Development Web Site'].[path='/'].[path='/'].physicalPath:%site_path%" /commit:apphost /apphostconfig:%tempdir%applicationHost.config
REM "%iis_dir%appcmd.exe" set config -section:system.applicationHost/sites /~"[name='Development Web Site'].bindings" /commit:apphost /apphostconfig:%tempdir%applicationHost.config
REM "%iis_dir%appcmd.exe" set config -section:system.applicationHost/sites /+"[name='Development Web Site'].bindings.[protocol='%site_protocol%',bindingInformation='127.0.0.1:%site_port%:']" /commit:apphost /apphostconfig:%tempdir%applicationHost.config
REM This doesn't
Powershell.exe -Command "& {$a=New-Object System.Xml.XmlDocument;$a.Load("%tempdir%applicationHost.config");$b=$a.SelectSingleNode("//sites/site[@id='1′]//virtualDirectory");$b.physicalPath="%site_path%";$c=$a.SelectSingleNode("//sites/site[@id='1′]//binding");$c.protocol="%site_protocol%";$c.bindingInformation=":%site_port%:localhost";$a.Save("%tempdir%applicationHost.config")}"


:run_iis

echo Running IIS Express: /port:%site_port% /protocol:%site_protocol% /path:%site_path%, temp-config: %tempdir%applicationHost.config
"%iis_dir%iisexpress.exe" /config:%tempdir%applicationHost.config /siteid:1 /systray:false

 
:cleanup
 
rd /q /s %tempdir%


goto exit


:no_port
echo /port not specified
goto help

:no_path
echo /path not specified
goto help

:invalid_path
echo /path doesn't exist: %site_path%
exit /b 2

:iis_missing
echo can't find iisexpress.exe in %iis_dir%
exit /b 3


:help

echo %script_name% [/port:1234] [/protocol:https] "/path:C:pathtositedir"
echo default: /port:8080 /protocol:http
exit /b 1

GOTO exit

:exit

popd
endlocal

exit /b

Here’s hoping this solution works as well for you as it does for me.  Enjoy!

Rob