Code coverage of IIS with the dotCover command-line hasn't been implemented yet, as described in this ticket on the dotCover issue tracker.
However, using tips from a comment by Tony Fabris on the ticket, I was able to create a Powershell script which can produce code coverage of the application. This uses IIS Express and not IIS, which is not ideal, but close enough for now.
I've posted my script here, in case it would be of use to other people.
# This script runs our web-based tests, running IISExpress via TeamCity's
# dotCover, then runing the tests (also in dotCover) and then importing the
# test results and coverage results back into TeamCity.
# Note that some of the following is based on the comments by Tony Fabris on
# 20th Jan 2016 from here: https://youtrack.jetbrains.com/issue/DCVR-5921
# Configuration.
# The path to IIS Express.
$IISExpressPath = "C:\Program Files (x86)\IIS Express\IISExpress.exe"
# The path to dotCover, the TeamCity tool for .NET code coverage.
$DotCoverPath = "C:\TeamCity\buildAgent\tools\dotCover\dotcover.exe"
# The path to the VSTest console.
$VSTestConsolePath = "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe"
# The path to PSExec.
$PSExecPath = "C:\path\to\PSTools\PSExec.exe"
# The path of the website which will be hosted in IIS express.
$WebsitePath = "C:\path\to\webapp"
# The port on which to host the website.
$WebsitePort = 12345
# The path to use as the working directory when running the tests.
# Note that the test results are written to a .trx file in a TestResults
# subdirectory of this directory.
$TestWorkingPath = "c:\path\to\checkout\dir"
# The assembly to run tests for.
$TestAssembly = "C:\path\to\test.dll"
# Get the TEMP folder.
# When run from the command-line, this will be something like the user's AppData\Local\Temp
# When run from TeamCity, this will be something like C:\TeamCity\buildAgent\temp\buildTemp
$tempDir = (Get-Item $Env:TEMP).FullName
Write-Host ("tempDir: " + $tempDir)
# Work out the output directory to write various output files to, in a
# CustomCoverage subdirectory of the temporary directory.
$outputDir = Join-Path $tempDir "CustomCoverage"
Write-Host ("outputDir: " + $outputDir)
# Create that output directory, but only if it doesn't exist.
if (!(Test-Path $outputDir))
{
md $outputDir
}
# Work out the path of the dotcover log file used for IISExpress, and delete it
# if it already exists.
$dotCoverIISExpressLogFilename = "$outputDir\dotcover.iisexpress.log.txt"
If (Test-Path $dotCoverIISExpressLogFilename)
{
del $dotCoverIISExpressLogFilename
}
# Work out the path of the dotCover output file used for IISExpress.
$dotCoverIISExpressOutputFilename = "$outputDir\dotcover.iisexpress.dcvr"
# Work out the arguments that we'll pass to IIS Express.
$IISExpressArgs = "/path:$WebsitePath /port:$WebsitePort /trace:info"
# Work out the arguments to pass to dotCover to start and cover IIS Express.
# Because the arguments to dotCover are long, we write them out to a config file.
$dotCoverIISExpressConfigFilename = "$outputDir\dotcover.iisexpress.config.xml"
"<?xml version=`"1.0`" encoding=`"us-ascii`"?>`n" + `
"<CoverageParams>`n" + `
" <LogFile>$dotCoverIISExpressLogFilename</LogFile>`n" + `
" <Output>$dotCoverIISExpressOutputFilename</Output>`n" + `
" <TargetExecutable>$IISExpressPath</TargetExecutable>`n" + `
" <TargetArguments>$IISExpressArgs</TargetArguments>`n" + `
"</CoverageParams>`n" `
| Out-File -Encoding ASCII $dotCoverIISExpressConfigFilename
# Write the command to execute dotCover out to a .bat file.
# Within the batch file we redirect stdout and stderr to a file, so we can see
# any reasons for failure.
# We put this in a batch file because it is too long for PSExec, which is
# limited to 260 characters for a single argument.
# http://forum.sysinternals.com/psexec-argument-to-long_topic14203.html
$dotCoverIISExpressRunFilename = "$outputDir\dotcover.iisexpress.run.bat"
"`"$DotCoverPath`" cover `"$dotCoverIISExpressConfigFilename`" > `"$outputDir\iisexpress.stdout.txt`" 2> `"$outputDir\iisexpress.stderr.txt`"" | Out-File -Encoding ASCII $dotCoverIISExpressRunFilename
# We need to run IISExpress (hosted in dotCover) in an interactive session, in
# order to be able to send it a WM_QUIT to shut it down gracefully after the
# tests (or else no coverage data will be recorded).
# We do this by running dotCover via PSExec.
# -i : interactive session
# -h : use the account's elevated token
# -accepteula : accept the pstools EULA, as the user running the build agent probably won't have
$psExecArgs = "-i -h -accepteula `"$dotCoverIISExpressRunFilename`""
# Start PSExec -> DotCover -> IISExpress.
# This will block, so we use Start-Process so that's it's done in a separate
# process, and so this script will continue.
# The -PassThru argument is required so that Start-Process will return a process
# handle.
$IISExpressProcess = (Start-Process $PSExecPath $psExecArgs -PassThru)
# Work out the path of the dotcover log file used for VSTest, and delete it
# if it already exists.
$dotCoverVSTestLogFilename = "$outputDir\dotcover.vstest.log.txt"
If (Test-Path $dotCoverVSTestLogFilename)
{
del $dotCoverVSTestLogFilename
}
# Work out the path of the dotCover output file used for VSTest.
$dotCoverVSTestOutputFilename = "$outputDir\dotcover.vstest.dcvr"
# Work out the arguments to pass to VSTest console.
# The /Logger:trx argument outputs the results in MSTest format, which TeamCity
# is able to import.
$vsTestArgs = "`"$TestAssembly`" /Logger:trx"
# Then run dotCover to run VSTest.
# This will run the tests, and also collect code coverage for the tests
# themselves.
# We expect the tests to be making calls to the website, which is running on IIS
# Express and being covered that way.
& $DotCoverPath cover /LogFile="$dotCoverVSTestLogFilename" /Output="$dotCoverVSTestOutputFilename" /TargetExecutable="$VSTestConsolePath" /WorkingDir="$TestWorkingPath" /TargetArguments="$vsTestArgs"
# We now need to shut down IISExpress.
# This has to be done gracefully, or else coverage data won't be collected.
# The core command to do this is 'taskkill /IM IISExpress.exe'.
# Note that there's no /F parameter to force - so what this will do is send a
# WM_QUIT to the process, to tell it to shut down.
# However, this only works if IISExpress was run in an interactive session (-i),
# and also if this taskkill call is as well.
# Both also require the -h parameter (elevated token) to be able to work.
# Note that we also want to log the output of taskkill, to help track down any
# issues.
# If we just do this, then powershell handles the redirection to the file:
# & PSExec taskkill > output.txt
# If we add a ` to escape the >, then powershell doesn't do the redirection, but
# we still end up redirecting the output of PSExec, instead of the output of
# taskkill.
# & PSExec taskkill `> output.txt
# Using 'cmd /C' instead allows us to pass the command in quotes, and therefore
# the redirect applies to the taskkill, instead of to the PSExec:
# & PSExec cmd / C "taskkill `> output.txt"
# We log the output of stdout (via >) and stderr (via 2>).
# So stop IIS Express.
& $PSExecPath -i -h -accepteula cmd /C `"taskkill /IM IISExpress.exe `> "$outputDir\taskkill.stdout.txt" 2`> "$outputDir\taskkill.stderr.txt"`"
# As a record, write out the contents of the stdout and stderr log files, for
# both IIS Express and taskkill, to help track down any issues.
# Note that IIS Express will have been logging during execution of the tests, so
# we can't collect the log until we've killed it.
Write-Host "-- iis express stdout --"
Get-Content "$outputDir\iisexpress.stdout.txt"
Write-Host "------------------------"
Write-Host "-- iis express stderr --"
Get-Content "$outputDir\iisexpress.stderr.txt"
Write-Host "------------------------"
Write-Host "-- taskkill stdout -----"
Get-Content "$outputDir\taskkill.stdout.txt"
Write-Host "------------------------"
Write-Host "-- taskkill stderr -----"
Get-Content "$outputDir\taskkill.stderr.txt"
Write-Host "------------------------"
# IIS Express takes a little while to close, the dotCover which is wrapping it
# then takes a little while to write out the results. This is all wrapped up in
# PSExec. Wait for this chain of processes to exit.
Write-Host "Waiting for IIS Express to close..."
$IISExpressProcess.WaitForExit()
Write-Host "- done"
# Import the test results in the .trx file in MSTest format.
# Note that this path is relative to the checkout directory, and that VSTest
# writes the output into the TestResults subdirectory of the working directory
# it was invoked with.
Write-Host "##teamcity[importData type='mstest' path='TestResults\*.trx']"
# These commands create an XML report from the dotCover coverage files.
# This isn't necessary, as TeamCity will handle merging the coverage files and
# then generating the report.
# However it may be useful to uncomment these for testing.
#& $DotCoverPath report /Source:$dotCoverIISExpressOutputFilename /Output:$outputDir\dotcover.iisexpress.report.xml /ReportType:xml
#& $DotCoverPath report /Source:$dotCoverVSTestOutputFilename /Output:$outputDir\dotcover.vstest.report.xml /ReportType:xml
# Write a service message to make TeamCity import code coverage from IIS express.
# Note that TeamCity will delete this file once it has processed it.
Write-Host "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='$dotCoverIISExpressOutputFilename']"
# Write a service message to make TeamCity import code coverage from VSTest.Console.
# Note that TeamCity will delete this file once it has processed it.
Write-Host "##teamcity[importData type='dotNetCoverage' tool='dotcover' path='$dotCoverVSTestOutputFilename']"
Note that in my case I checkout to a fixed directory, and so the website, working path and test assemblies are all at known absolute paths. The TeamCity default is to checkout to a different temporary directory for each build, and so if you are using the default, you'll need to change the script to take account of that.