The .NET deployment elephant in the room
If you ship .NET on Windows, you have probably noticed something: a lot of the popular deployment tooling treats your stack as a second-class citizen. The tutorials assume Docker. The plugins assume Linux. The 'easy path' is a container, and your application is a Windows service that absolutely will not be a container any time soon.
Jaws Deploy was built by people who deploy Windows services for a living. PowerShell is the first language. IIS is a first-class concept. Windows services are a step type. The agent runs natively on Windows. None of this is an afterthought.
What 'first-class Windows' actually means
We use that phrase a lot. Here is what it costs out to.
Windows-native deployment building blocks
web.config transforms - the things Octopus made common and Linux-first tools never bothered to copy.
sc.exe mechanics, with proper error handling, exposed as a step type.
The IIS deployment, done correctly
Deploying an IIS site is one of those things that looks simple and gets very fiddly very quickly. Stop the app pool, copy files, swap bindings, recycle, smoke-test. Every one of those steps has a way to go wrong, and the failure modes are usually invisible until users start calling support.
Jaws Deploy ships an IIS Site Deploy template that handles the choreography correctly out of the box. The interesting part is what you don't have to write.
Things teams used to learn the hard way
- Drains the app pool before recycle - waits for active connections to complete with a configurable timeout. No mid-request kills.
- Applies
web.configtransforms in the right order - XDT transforms run against the deployed package, not the source. - Swaps bindings atomically - the old binding stays live until the new site responds to a health check.
- Sets app pool identity correctly - if you need to run as a specific service account, the credential handling does not leak into the log.
- Cleans up old versions - keeps a configurable number of previous deployments on disk for emergency rollback. Older ones are pruned.
From a real project, abbreviated
This step deploys a Windows service that hosts a background processor. Notice what isn't here: any custom plumbing around stopping the service, copying files, or restarting it cleanly.
# Step template: Deploy Windows Service
#
# Inputs (filled per-project, per-environment):
# ServiceName = "Acme.Checkout.Worker"
# PackageName = "Acme.Checkout.Worker"
# StopTimeoutSec = 60
# RunAsAccount = #{Service.Account} (scoped variable)
# RunAsPassword = #{Service.Password} (secret)
# StartAfterDeploy = true
#
# What the template does:
# 1. Stop-Service -Name $ServiceName -Force -Timeout $StopTimeoutSec
# 2. Wait for service to actually stop (not just 'pending stop')
# 3. Extract package to "C:\Services\$ServiceName"
# 4. Apply config transforms scoped to current environment
# 5. sc.exe config $ServiceName obj= $RunAsAccount password= $RunAsPassword
# 6. Start-Service -Name $ServiceName (if requested)
# 7. Wait for service to actually be 'Running' (with timeout)
# 8. Fail the deployment with a meaningful log line if any step fails
PowerShell as a real language, not a punchline
We write a lot of PowerShell. The PowerShell SDK is the same kind of thing - a real cmdlet library, properly typed, with pipeline support and the usual -Verbose, -WhatIf, -Confirm conventions. It is not a thin wrapper around Invoke-RestMethod with weird parameter names.
// What the SDK gives you
Real cmdlets
New-JawsDeployRelease, Push-JawsDeployPackage, Invoke-JawsDeployDeployment, Get-JawsDeployVariableSet. They pipe. They take credentials properly. They behave like cmdlets.
// What this looks like in CI
Idiomatic
TeamCity, Azure DevOps, Jenkins, GitHub Actions - all of them can call PowerShell. The deployment trigger becomes three lines of PowerShell after the build, not a 200-line YAML stanza.
.NET Framework, .NET 6+, and the awkward middle
A lot of teams have a mix - some apps on .NET Framework 4.x, some on .NET 6 or 8, some that nobody wants to talk about. Jaws Deploy treats this as the default case. Steps don't care which runtime your app uses. The package format is the same. The deployment process is the same. The way you describe variables and environments is the same.
Things teams in this position care about
- Side-by-side runtime versions - install the right runtime as a step in your pipeline; don't make the deployment tool care.
- Self-contained .NET 8 publishes - shipped as zips, extracted onto the target, no global install dependency.
web.configfor Framework,appsettings.jsonfor Core - both are handled by the variable substitution layer.- Service identity differs across apps - scoped variables (per environment or per machine tag) handle the matrix without forking pipelines.
The honest scope of this offering
If you are 100% containerized, on Linux, and ship through Helm charts - Jaws Deploy works, but other tools may suit you better. The reason this page exists, and the reason we built the product the way we did, is that the Windows + .NET deployment problem has been underserved for a decade. We are good at the underserved part. The rest of the world has plenty of options.
How to test this in an hour
Install a Jaws Deploy agent on a Windows VM you can afford to break. Point a project at a sample .NET app. Run the IIS site deploy template. Look at the log. If it doesn't feel like the deployment tool was actually built for your stack - tell us. We will be very surprised, and we will want to know what broke.