# .NET & Windows Deployments

Source: https://www.jawsdeploy.net/solutions/dotnet-windows-deployments

Most CI/CD tools were built assuming Linux, containers, and a flat Kubernetes deployment. If your stack is IIS, Windows services, PowerShell, and a fleet of Server 2019 boxes - this page is for you. Jaws Deploy is built around exactly that stack.

## 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.

> **// The unstated reason this matters - Your stack is not legacy. It is the stack that runs production.**
> 
> There is an unhelpful narrative in the deployment-tooling world that says 'real teams ship containers'. Real teams ship whatever their customers paid for. A huge amount of the enterprise software running today is .NET on Windows Server, and it is not going anywhere this decade. A deployment tool that pretends otherwise is the wrong tool for the job - regardless of how shiny its homepage is.

## What 'first-class Windows' actually means

We use that phrase a lot. Here is what it costs out to.

## Windows-native deployment building blocks

- **IIS sites and app pools**: Built-in step templates for creating sites, swapping bindings, recycling app pools, applying `web.config` transforms - the things Octopus made common and Linux-first tools never bothered to copy.
- **Windows services**: Install, configure, start, stop, restart, change recovery action, change RunAs user. Real `sc.exe` mechanics, with proper error handling, exposed as a step type.
- **PowerShell-first**: Steps are PowerShell. Script modules are PowerShell. The SDK is PowerShell. If your team already writes PowerShell, you already write Jaws Deploy.
- **Windows agent**: The agent is a native Windows service. It runs as a configurable service account, handles credentials properly, and does not require WSL or Docker Desktop to function.

## 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.config` transforms 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
```

> **// The Octopus refugee question - If you came from Octopus, you will find this familiar - on purpose.**
> 
> Jaws Deploy's product model owes a lot to the conventions Octopus established for Windows deployment. Projects, environments, variables, deployment processes, releases - the nouns are the same. We did this on purpose because the Windows world spent a decade learning that model and it actually works. We just rebuilt the product around it without the enterprise bloat. See the [Octopus Deploy Alternative](https://www.jawsdeploy.net/solutions/octopus-deploy-alternative) page if that is your specific path.

## 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.

**Real cmdlets**

`New-JawsDeployRelease`, `Push-JawsDeployPackage`, `Invoke-JawsDeployDeployment`, `Get-JawsDeployVariableSet`. They pipe. They take credentials properly. They behave like cmdlets.

**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.config` for Framework, `appsettings.json` for 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.

> **// One small but persistent annoyance we fixed - Long file paths actually work.**
> 
> Windows deployment tools have a long history of breaking on paths over 260 characters - usually inside a deeply nested `node_modules` or a TypeScript build output. Jaws Deploy's package extraction explicitly handles long paths on supported Windows versions. It sounds boring. It is boring. It used to ruin entire deployments at 11pm on a Friday.

## 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.

