Launching IIS Express
Start a .NET Framework ASP.NET web project with IIS Express from the terminal by generating a repo-local launch script from the template in this skill.
Workflow
Verify the installed release: Resolve the installed launching-iisexpress skill directory, then run its verifier before using its template:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File `
"<installed-skill-dir>\Verify-LaunchingIISExpress.ps1" `
-SkillDirectory "<installed-skill-dir>"
Stop if this fails. The verifier requires the installed SKILL.md version to match this release and the behavioral template's normalized SHA-256 fingerprint to match the canonical v1.1.2 contract. It intentionally does not hash SKILL.md, so repo-local workflow clarification can coexist with an exact canonical template.
Verify the generated script: Look for Start-IISExpress.ps1 in the solution root or a scripts/ directory.
If no script exists, generate one in step 6.
If a script exists, validate its machine-readable marker:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File `
"<installed-skill-dir>\Verify-LaunchingIISExpress.ps1" `
-SkillDirectory "<installed-skill-dir>" `
-GeneratedScript "<path-to-Start-IISExpress.ps1>"
The verifier requires both a valid provenance marker and the v1.1.2 safety behavior: conditional non-root detection, a dedicated blank root directory, conditional blank-root creation and selection, root mapping only through the selected physical path (never directly to the web project), virtual-app mapping to the web project, and hidden IIS Express launch with separate stdout and stderr logs. A current-looking marker on an older unsafe body does not pass.
If the marker is absent or its version is older than the installed skill, or if a required safety invariant is missing or ambiguous, do not launch or patch the stale script. Replace it by regenerating from the installed template in step 6.
If the generated script reports a newer version than the installed skill, stop and update the installed skill instead of downgrading the script.
Reuse an exact-version, safety-verified script only after confirming its generated project settings still match the target project.
Discover project settings from the target .csproj:
- Site name: the web project name.
- URL parts: scheme, host, port, and path from
ProjectExtensions > VisualStudio > FlavorProperties > WebProjectProperties > IISUrl.
- Web project path: the absolute path to the folder containing
Web.config.
- Solution root: the absolute path to the folder containing the
.sln.
Check the port before launch:
- Use
Get-NetTCPConnection -LocalPort <port> -State Listen to detect local listeners. Do not use Test-NetConnection; it tests reachability, not bind availability.
- Treat any listener on the same port as a conflict, including
0.0.0.0, 127.0.0.1, ::1, or another local address.
- If the listener is
iisexpress.exe, only treat it as this skill's instance when its command line matches both /config:"<solution-root>\.vs\config\applicationhost.config" and /site:"<site-name>".
- For unrelated listeners, report the current port, PID, process name, and command line when available.
Resolve port conflicts with approval:
- Ask before changing
.csproj or stopping unrelated processes.
- Propose a free replacement port and re-check it immediately before writing.
- For
https://localhost:<port>, scan the IIS Express SSL-friendly 44300..44399 range unless the user chooses otherwise.
- For
http://localhost:<port>, scan nearby ports first, then a practical local range such as 5000..65000.
- A candidate port is free when
Get-NetTCPConnection -LocalPort <candidate> -State Listen returns no listeners.
- Update only the port in
ProjectExtensions > VisualStudio > FlavorProperties > WebProjectProperties > IISUrl; preserve the scheme, host, path, encoding, BOM, and line endings. Legacy .csproj files usually use the MSBuild XML namespace, so namespace-aware XML handling is required if editing structurally.
Generate or replace the script: Read <installed-skill-dir>/references/Start-IISExpress.template.ps1, replace every placeholder in memory, and write the complete result to the solution root as Start-IISExpress.ps1. Do not modify a stale generated script in place. The template's IISExpressSkill-Provenance marker is literal release metadata and must remain unchanged.
| Placeholder |
Replace with |
{{SITE_NAME}} |
Project/site name |
{{SITE_PORT}} |
Port from IISUrl, or the approved replacement port |
{{SITE_SCHEME}} |
Scheme from IISUrl, usually http or https |
{{SITE_HOST}} |
Host from IISUrl, usually localhost |
{{APPLICATION_PATH}} |
Path from IISUrl, or / when omitted |
{{WEB_PROJECT_PATH}} |
Absolute web project directory |
{{SOLUTION_ROOT}} |
Absolute solution root |
Use exact literal placeholder replacement so Windows paths and the application path are preserved:
$template = Get-Content "<installed-skill-dir>\references\Start-IISExpress.template.ps1" -Raw
function ConvertTo-PowerShellSingleQuotedContent {
param([AllowEmptyString()][string]$Value)
$Value.Replace("'", "''")
}
$replacements = [ordered]@{
"{{SITE_NAME}}" = (ConvertTo-PowerShellSingleQuotedContent "<site-name>")
"{{SITE_PORT}}" = "<port>"
"{{SITE_SCHEME}}" = (ConvertTo-PowerShellSingleQuotedContent "<scheme>")
"{{SITE_HOST}}" = (ConvertTo-PowerShellSingleQuotedContent "<host>")
"{{APPLICATION_PATH}}" = (ConvertTo-PowerShellSingleQuotedContent "<application-path>")
"{{WEB_PROJECT_PATH}}" = (ConvertTo-PowerShellSingleQuotedContent "<absolute-web-project-path>")
"{{SOLUTION_ROOT}}" = (ConvertTo-PowerShellSingleQuotedContent "<absolute-solution-root>")
}
$rendered = [regex]::Replace(
$template,
"\{\{[A-Z_]+\}\}",
{
param($match)
if (-not $replacements.Contains($match.Value)) {
throw "Unknown IIS Express template placeholder: $($match.Value)"
}
[string]$replacements[$match.Value]
}
)
[System.IO.File]::WriteAllText(
(Join-Path "<absolute-solution-root>" "Start-IISExpress.ps1"),
$rendered,
[System.Text.UTF8Encoding]::new($false)
)
Re-run the generated-script verifier from step 2 after writing the file.
7. Restore and build before launch:
msbuild <solution-path> /t:Restore /p:RestorePackagesConfig=true
msbuild <solution-path> /p:Configuration=Debug
- Launch IIS Express:
.\Start-IISExpress.ps1
The generated script writes .vs/config/applicationhost.config with the selected port, starts IIS Express in a hidden window with stdout and stderr redirected to separate files under .vs/config, and returns control to the terminal after confirming the site responds over HTTP. IIS Express reads the binding from applicationhost.config; .csproj IISUrl is the durable project setting for future runs and Visual Studio alignment.
- Verify with the discovered or approved URL:
curl <scheme>://<host>:<port><path>
For HTTPS localhost URLs, certificate trust warnings can be expected on some machines; use the equivalent curl option for ignoring certificate validation only when the user accepts that local-only tradeoff.
- Stop the launched site:
.\Start-IISExpress.ps1 -Stop
Notes
- IIS Express is usually installed at
C:\Program Files\IIS Express\iisexpress.exe.
- For ASP.NET Core or another Kestrel-hosted app, do not use this template. Run
dotnet run with the terminal tool's async mode so the server remains persistent without opening another console window.
- The template writes
.vs/config/applicationhost.config under the solution root and configures Clr4IntegratedAppPool.
- The template launches IIS Express with a hidden window and writes
.vs/config/iisexpress.stdout.log and .vs/config/iisexpress.stderr.log.
- Re-running
Start-IISExpress.ps1 automatically stops the IIS Express instance previously launched for the same generated config/site, so the new run can take over. It does not affect IIS Express processes from other sites or solutions.
- Stop only the IIS Express process launched for the generated config/site. Do not kill all
iisexpress.exe processes by name.
- IIS Express HTTP listeners can appear as
OwningProcess = 4 (System) because of HTTP.SYS. Do not use Get-NetTCPConnection ownership (OwningProcess == iisexpress PID) as a readiness check; use an HTTP-level probe to confirm startup.
- The template probes readiness for up to 60 seconds to accommodate slow startup work (for example, database seeding) and still fail within a bounded time.
- For HTTPS readiness probing, the template handles local development certificate trust differences across Windows PowerShell and PowerShell 7 so startup does not false-fail when the app is up.
- Visual Studio can regenerate
.vs/config/applicationhost.config; keep .csproj IISUrl aligned so future generated configs use the approved port.
- If multiple web projects exist, scope edits and process checks to the single target project/site.
- IIS Express rejects non-
localhost host headers by default unless run elevated and explicitly configured; keep IISUrl host as localhost unless the user has set up otherwise.
- For HTTPS, the template enforces ports in the IIS Express SSL-friendly
44300-44399 range. Other HTTPS ports require a manual netsh http add sslcert binding and will be rejected at launch.
- When
IISUrl includes a virtual path (for example http://localhost:6001/MyApp), the template adds a second application at the virtual path pointing at the real project folder, and maps the root / application to a separate, blank folder (.vs/config/empty-root) so both applications can coexist without a physical-path collision.
Troubleshooting: HTTP 500.19 on non-root virtual paths
Symptom: IIS Express fails to start (or a request fails) with HTTP 500.19 immediately after launch, even though the app code itself is fine, and only reproduces when IISUrl includes a virtual path segment (for example /MyApp).
Root cause: the site root (/) and the non-root virtual path both resolved to the same physical folder. IIS Express then loads that folder's Web.config for two different configuration scopes (/ and /MyApp), which collide and produce a 500.19 configuration error.
Detection: inspect the generated .vs/config/applicationhost.config for the site and confirm the application path="/" and application path="<virtual path>" elements point at different physicalPath values. If they match, that's the bug.
Remediation: regenerate Start-IISExpress.ps1 from the current template in this skill (references/Start-IISExpress.template.ps1, version 1.1.1+). The template points the root application at a blank, empty folder and only maps the virtual path to the real project folder. Do not hand-edit an existing generated config or stale generated script to "fix" this in place - regenerate from the installed template so the fix persists across future regenerations.
Versioning
This skill (SKILL.md + references/Start-IISExpress.template.ps1) is the canonical upstream copy. Other locations that keep their own copy of this skill should treat this repository as the source of truth and sync from it rather than diverging independently.
- The
version field in the frontmatter follows semantic versioning and must be bumped whenever the skill's workflow or template behavior changes.
- Before copying this skill elsewhere, run
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .github/skills/launching-iisexpress/Verify-LaunchingIISExpress.ps1. PowerShell 7 users may substitute pwsh for powershell.exe. The verifier checks both release version and the normalized SHA-256 fingerprint of references/Start-IISExpress.template.ps1; version equality alone is not sufficient.
- Downstream repositories may customize
SKILL.md instructions while retaining the canonical version frontmatter. The verifier hashes the behavioral template, not the customizable prose.
- After generation, pass
-GeneratedScript <path> to require valid provenance plus the v1.1.2 mapping and hidden-launch safety invariants before launch. Missing or older provenance and missing or ambiguous safety behavior require regeneration; newer provenance requires updating the installed skill.
- When porting a fix here, bump the version and summarize the change in the pull request description.
1---2name: launching-iisexpress3description: Launches .NET Framework ASP.NET projects with IIS Express from the command line, mirroring Visual Studio local debugging. Use when the user needs to start, run, debug, test, or verify a .NET Framework ASP.NET MVC, Web API, or Web Forms app locally, especially during migration. Do not use for ASP.NET Core or modern .NET apps; run dotnet run with the terminal tool in async mode instead.4license: MIT5---67# Launching IIS Express89Start a .NET Framework ASP.NET web project with IIS Express from the terminal by generating a repo-local launch script from the template in this skill.1011## Workflow12131. **Verify the installed release**: Resolve the installed `launching-iisexpress` skill directory, then run its verifier before using its template:1415 ```powershell16 powershell.exe -NoProfile -ExecutionPolicy Bypass -File `17 "<installed-skill-dir>\Verify-LaunchingIISExpress.ps1" `18 -SkillDirectory "<installed-skill-dir>"19 ```2021 Stop if this fails. The verifier requires the installed `SKILL.md` version to match this release and the behavioral template's normalized SHA-256 fingerprint to match the canonical v1.1.2 contract. It intentionally does not hash `SKILL.md`, so repo-local workflow clarification can coexist with an exact canonical template.222. **Verify the generated script**: Look for `Start-IISExpress.ps1` in the solution root or a `scripts/` directory.23 - If no script exists, generate one in step 6.24 - If a script exists, validate its machine-readable marker:2526 ```powershell27 powershell.exe -NoProfile -ExecutionPolicy Bypass -File `28 "<installed-skill-dir>\Verify-LaunchingIISExpress.ps1" `29 -SkillDirectory "<installed-skill-dir>" `30 -GeneratedScript "<path-to-Start-IISExpress.ps1>"31 ```3233 - The verifier requires both a valid provenance marker and the v1.1.2 safety behavior: conditional non-root detection, a dedicated blank root directory, conditional blank-root creation and selection, root mapping only through the selected physical path (never directly to the web project), virtual-app mapping to the web project, and hidden IIS Express launch with separate stdout and stderr logs. A current-looking marker on an older unsafe body does not pass.34 - If the marker is absent or its version is older than the installed skill, or if a required safety invariant is missing or ambiguous, do not launch or patch the stale script. Replace it by regenerating from the installed template in step 6.35 - If the generated script reports a newer version than the installed skill, stop and update the installed skill instead of downgrading the script.36 - Reuse an exact-version, safety-verified script only after confirming its generated project settings still match the target project.373. **Discover project settings** from the target `.csproj`:38 - Site name: the web project name.39 - URL parts: scheme, host, port, and path from `ProjectExtensions > VisualStudio > FlavorProperties > WebProjectProperties > IISUrl`.40 - Web project path: the absolute path to the folder containing `Web.config`.41 - Solution root: the absolute path to the folder containing the `.sln`.424. **Check the port before launch**:43 - Use `Get-NetTCPConnection -LocalPort <port> -State Listen` to detect local listeners. Do not use `Test-NetConnection`; it tests reachability, not bind availability.44 - Treat any listener on the same port as a conflict, including `0.0.0.0`, `127.0.0.1`, `::1`, or another local address.45 - If the listener is `iisexpress.exe`, only treat it as this skill's instance when its command line matches both `/config:"<solution-root>\.vs\config\applicationhost.config"` and `/site:"<site-name>"`.46 - For unrelated listeners, report the current port, PID, process name, and command line when available.475. **Resolve port conflicts with approval**:48 - Ask before changing `.csproj` or stopping unrelated processes.49 - Propose a free replacement port and re-check it immediately before writing.50 - For `https://localhost:<port>`, scan the IIS Express SSL-friendly `44300..44399` range unless the user chooses otherwise.51 - For `http://localhost:<port>`, scan nearby ports first, then a practical local range such as `5000..65000`.52 - A candidate port is free when `Get-NetTCPConnection -LocalPort <candidate> -State Listen` returns no listeners.53 - Update only the port in `ProjectExtensions > VisualStudio > FlavorProperties > WebProjectProperties > IISUrl`; preserve the scheme, host, path, encoding, BOM, and line endings. Legacy `.csproj` files usually use the MSBuild XML namespace, so namespace-aware XML handling is required if editing structurally.546. **Generate or replace the script**: Read `<installed-skill-dir>/references/Start-IISExpress.template.ps1`, replace every placeholder in memory, and write the complete result to the solution root as `Start-IISExpress.ps1`. Do not modify a stale generated script in place. The template's `IISExpressSkill-Provenance` marker is literal release metadata and must remain unchanged.5556| Placeholder | Replace with |57|---|---|58| `{{SITE_NAME}}` | Project/site name |59| `{{SITE_PORT}}` | Port from `IISUrl`, or the approved replacement port |60| `{{SITE_SCHEME}}` | Scheme from `IISUrl`, usually `http` or `https` |61| `{{SITE_HOST}}` | Host from `IISUrl`, usually `localhost` |62| `{{APPLICATION_PATH}}` | Path from `IISUrl`, or `/` when omitted |63| `{{WEB_PROJECT_PATH}}` | Absolute web project directory |64| `{{SOLUTION_ROOT}}` | Absolute solution root |6566 Use exact literal placeholder replacement so Windows paths and the application path are preserved:6768 ```powershell69 $template = Get-Content "<installed-skill-dir>\references\Start-IISExpress.template.ps1" -Raw70 function ConvertTo-PowerShellSingleQuotedContent {71 param([AllowEmptyString()][string]$Value)72 $Value.Replace("'", "''")73 }74 $replacements = [ordered]@{75 "{{SITE_NAME}}" = (ConvertTo-PowerShellSingleQuotedContent "<site-name>")76 "{{SITE_PORT}}" = "<port>"77 "{{SITE_SCHEME}}" = (ConvertTo-PowerShellSingleQuotedContent "<scheme>")78 "{{SITE_HOST}}" = (ConvertTo-PowerShellSingleQuotedContent "<host>")79 "{{APPLICATION_PATH}}" = (ConvertTo-PowerShellSingleQuotedContent "<application-path>")80 "{{WEB_PROJECT_PATH}}" = (ConvertTo-PowerShellSingleQuotedContent "<absolute-web-project-path>")81 "{{SOLUTION_ROOT}}" = (ConvertTo-PowerShellSingleQuotedContent "<absolute-solution-root>")82 }83 $rendered = [regex]::Replace(84 $template,85 "\{\{[A-Z_]+\}\}",86 {87 param($match)88 if (-not $replacements.Contains($match.Value)) {89 throw "Unknown IIS Express template placeholder: $($match.Value)"90 }91 [string]$replacements[$match.Value]92 }93 )94 [System.IO.File]::WriteAllText(95 (Join-Path "<absolute-solution-root>" "Start-IISExpress.ps1"),96 $rendered,97 [System.Text.UTF8Encoding]::new($false)98 )99 ```100101 Re-run the generated-script verifier from step 2 after writing the file.1027. **Restore and build** before launch:103104```powershell105msbuild <solution-path> /t:Restore /p:RestorePackagesConfig=true106msbuild <solution-path> /p:Configuration=Debug107```1081098. **Launch IIS Express**:110111```powershell112.\Start-IISExpress.ps1113```114115The generated script writes `.vs/config/applicationhost.config` with the selected port, starts IIS Express in a hidden window with stdout and stderr redirected to separate files under `.vs/config`, and returns control to the terminal after confirming the site responds over HTTP. IIS Express reads the binding from `applicationhost.config`; `.csproj` `IISUrl` is the durable project setting for future runs and Visual Studio alignment.1161179. **Verify** with the discovered or approved URL:118119```powershell120curl <scheme>://<host>:<port><path>121```122123For HTTPS localhost URLs, certificate trust warnings can be expected on some machines; use the equivalent curl option for ignoring certificate validation only when the user accepts that local-only tradeoff.12412510. **Stop the launched site**:126127```powershell128.\Start-IISExpress.ps1 -Stop129```130131## Notes132133- IIS Express is usually installed at `C:\Program Files\IIS Express\iisexpress.exe`.134- For ASP.NET Core or another Kestrel-hosted app, do not use this template. Run `dotnet run` with the terminal tool's `async` mode so the server remains persistent without opening another console window.135- The template writes `.vs/config/applicationhost.config` under the solution root and configures `Clr4IntegratedAppPool`.136- The template launches IIS Express with a hidden window and writes `.vs/config/iisexpress.stdout.log` and `.vs/config/iisexpress.stderr.log`.137- Re-running `Start-IISExpress.ps1` automatically stops the IIS Express instance previously launched for the same generated config/site, so the new run can take over. It does not affect IIS Express processes from other sites or solutions.138- Stop only the IIS Express process launched for the generated config/site. Do not kill all `iisexpress.exe` processes by name.139- IIS Express HTTP listeners can appear as `OwningProcess = 4` (`System`) because of HTTP.SYS. Do not use `Get-NetTCPConnection` ownership (`OwningProcess == iisexpress PID`) as a readiness check; use an HTTP-level probe to confirm startup.140- The template probes readiness for up to 60 seconds to accommodate slow startup work (for example, database seeding) and still fail within a bounded time.141- For HTTPS readiness probing, the template handles local development certificate trust differences across Windows PowerShell and PowerShell 7 so startup does not false-fail when the app is up.142- Visual Studio can regenerate `.vs/config/applicationhost.config`; keep `.csproj` `IISUrl` aligned so future generated configs use the approved port.143- If multiple web projects exist, scope edits and process checks to the single target project/site.144- IIS Express rejects non-`localhost` host headers by default unless run elevated and explicitly configured; keep `IISUrl` host as `localhost` unless the user has set up otherwise.145- For HTTPS, the template enforces ports in the IIS Express SSL-friendly `44300-44399` range. Other HTTPS ports require a manual `netsh http add sslcert` binding and will be rejected at launch.146- When `IISUrl` includes a virtual path (for example `http://localhost:6001/MyApp`), the template adds a second application at the virtual path pointing at the real project folder, and maps the root `/` application to a separate, blank folder (`.vs/config/empty-root`) so both applications can coexist without a physical-path collision.147148## Troubleshooting: HTTP 500.19 on non-root virtual paths149150**Symptom:** IIS Express fails to start (or a request fails) with HTTP 500.19 immediately after launch, even though the app code itself is fine, and only reproduces when `IISUrl` includes a virtual path segment (for example `/MyApp`).151152**Root cause:** the site root (`/`) and the non-root virtual path both resolved to the same physical folder. IIS Express then loads that folder's `Web.config` for two different configuration scopes (`/` and `/MyApp`), which collide and produce a 500.19 configuration error.153154**Detection:** inspect the generated `.vs/config/applicationhost.config` for the site and confirm the `application path="/"` and `application path="<virtual path>"` elements point at **different** `physicalPath` values. If they match, that's the bug.155156**Remediation:** regenerate `Start-IISExpress.ps1` from the current template in this skill (`references/Start-IISExpress.template.ps1`, version 1.1.1+). The template points the root application at a blank, empty folder and only maps the virtual path to the real project folder. Do not hand-edit an existing generated config or stale generated script to "fix" this in place - regenerate from the installed template so the fix persists across future regenerations.157158## Versioning159160This skill (`SKILL.md` + `references/Start-IISExpress.template.ps1`) is the canonical upstream copy. Other locations that keep their own copy of this skill should treat this repository as the source of truth and sync from it rather than diverging independently.161162- The `version` field in the frontmatter follows semantic versioning and must be bumped whenever the skill's workflow or template behavior changes.163- Before copying this skill elsewhere, run `powershell.exe -NoProfile -ExecutionPolicy Bypass -File .github/skills/launching-iisexpress/Verify-LaunchingIISExpress.ps1`. PowerShell 7 users may substitute `pwsh` for `powershell.exe`. The verifier checks both release version and the normalized SHA-256 fingerprint of `references/Start-IISExpress.template.ps1`; version equality alone is not sufficient.164- Downstream repositories may customize `SKILL.md` instructions while retaining the canonical `version` frontmatter. The verifier hashes the behavioral template, not the customizable prose.165- After generation, pass `-GeneratedScript <path>` to require valid provenance plus the v1.1.2 mapping and hidden-launch safety invariants before launch. Missing or older provenance and missing or ambiguous safety behavior require regeneration; newer provenance requires updating the installed skill.166- When porting a fix here, bump the version and summarize the change in the pull request description.