How to Deploy a Windows Service on AWS Elastic Beanstalk using Terraform

How to Deploy a Windows Service on AWS Elastic Beanstalk using Terraform

Hosting our applications on Windows Servers will lead us to consider using Windows Services for running background processes or applications without user interaction. In a previous post, we learned How to Deploy a .NET App on AWS Elastic Beanstalk using Terraform. Today we'll expand this deployment to include a Windows Service.

So, download or clone this repository. Before adding the new project, we will move the WeatherAPI project to its folder under src. Next, we will run the following command:

dotnet new worker --name WeatherWs -o src/WeatherWs
dotnet sln add --in-root src/WeatherWs
dotnet add src/WeatherWs package Microsoft.Extensions.Hosting.WindowsServices

Open the Program.cs file and replace the content with:

using WeatherWs;

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
    })
    .UseWindowsService()
    .Build();

host.Run();

The Windows Service is ready. Create an install.ps1 file at the solution level with the following content:

$serviceName="WeatherWs"
$serviceFolder="C:\services\WeatherWs"
$exe="$serviceFolder\WeatherWs.exe" 
$bin="$PSScriptRoot\ws"
mkdir $serviceFolder -Force
$exists = Get-WmiObject -Class Win32_Service -Filter "Name='$serviceName'"
if($exists)
{
    Stop-Service -Name $serviceName -Force 
    Start-Sleep -s 5
    sc.exe delete $serviceName
    Start-Sleep -s 5
}
Copy-Item "$bin\*" $serviceFolder -Recurse -Force
New-Service -Name $serviceName -BinaryPathName $exe
Start-Service -Name $serviceName

PSScriptRoot: Contains the full path to the script that invoked the current command. The value of this property is populated only when the caller is a script.

Modify the deployment manifest (aws-windows-deployment-manifest.json) with the following content:

{
    "manifestVersion": 1,
    "deployments": {
        "aspNetCoreWeb": [
        {
            "name": "dotnet-api",
            "parameters": {
              "appBundle": "site.zip",
              "iisPath": "/",
              "iisWebSite": "Default Web Site"
            },
            "scripts": {
              "preInstall": {
                "file": "install.ps1"
              }
          }
        }
        ]
    }
}

With this configuration, we'll run our script before the deploy the API. Further information about the deployment manifest can be found here. Now, the most important part, the bundle creation:

  • Publish the API in a folder.

  • Zip the published API and copy the file to the bundle folder.

  • Publish the Windows Service directly in the bundle folder.

  • Copy the install.ps1 and the aws-windows-deployment-manifest.json to the bundle folder

  • Zip the bundle folder.

dotnet publish ./src/WeatherApi/WeatherApi.csproj --output "terraform/publish/webapi" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime win-x64 --no-self-contained

Compress-Archive -Path terraform/publish/webapi/* -DestinationPath terraform/bundle/site.zip

dotnet publish ./src/WeatherWs/WeatherWs.csproj --output "terraform/bundle/ws" --configuration "Release" --framework "net6.0" /p:GenerateRuntimeConfigurationFiles=true --runtime win-x64 --no-self-contained

copy .\install.ps1 .\terraform\bundle
copy .\aws-windows-deployment-manifest.json .\terraform\bundle

Compress-Archive -Path terraform/bundle/* -DestinationPath terraform/app.zip

The contents of the bundle (app.zip) will appear as follows:

|-- ws
|-- aws-windows-deployment-manifest.json
|-- install.ps1
`-- site.zip

Run the terraform scripts with the following commands:

cd terraform
terraform init
terraform plan -out app.tfplan -var="health_check_path=/swagger/index.html" -var="bucket=app-tf-001" -var="keypair=<MY_KEY_PAIR>" -var="instance_type=t2.medium" -var="application=app-tf-001" -var="vpc_id=<MY_VPC>" -var="ec2_subnets=<MY_SUBNETS>" -var="elb_subnets=<MY_SUBNETS>" -var="platform=64bit Windows Server 2019 v2.11.3 running IIS 10.0"
terraform apply 'app.tfplan'

And deploy the application version with the following command:

aws --region us-east-2 elasticbeanstalk update-environment --environment-name <OUTPUT_ENV_NAME> --version-label <OUTPUT_APP_VERSION>

And that's it. There is an alternative method for deploying Windows Services using .ebextensions (you can check the video tutorial and find further information here), but we believe this approach is simpler. The code and scripts are available here. Thanks, and happy coding.