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