How can I build and deploy a .NET Core worker service to on-premise environment from Azure DevOps

Viewed 1565

I have a worker service app that I am manually installing on a dev server by copying its binaries and using PowerShell to install via the "New-Service" command.

We are looking at CI/CD to automate the build and deploy its artefacts. I can't figure out how to get the "built" files from Azure to the on-premise server, I have looked at things like Deployment groups, but these don't seem to be options in the Release tools for copy. I have looked at the "copy" tools and the build tools, but I am stuck.

Some seem to mention this using "classic", I think I am using YAML, not classic though.

Can anyone point me in the correct direction please?

1 Answers

With the classic UI its a bit more straightforward to me (its also YAML in the end)

What you need is:

  1. Install the Azure Deployment agent onto your machines, so that they will "become members" of a deployment pool. You can generate the install script via the deployment pool menu which you should run on the machine as local administrator.
  2. (I assume you have a project) Create a deployment group on the project level linked to that deployment pool
  3. If you don't have a build already which produces the binaries create one
  4. Create a new release
  5. Create a stage in that release
  6. Open stage and on the top menu next to the stage name you can see three dots. Click it and then "Add deployment group job" -> These are meant to run stuff on your local machine by utilizing the before mentioned deployment agent.
  7. Compose the deployment tasks or choose some from the market. Usually looks like copy files -> extract -> replace some tokens from variables -> run some scripts or use a dedicated task to install the application

And a little help to install windows service (you can make a task of this, but similar ones exist already)

$serviceName = "$(ServiceName)"
$serviceDisplayName = "$(ServiceDisplayName)"
$serviceDescription = "$(ServiceDescription)"
$exePath = "$(ServiceExeFullPath)"
$username = "NT AUTHORITY\NETWORK SERVICE"
$password = convertto-securestring -String "dummy" -AsPlainText -Force  
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $password

Write-Host "====================================="
Write-Host $serviceName
Write-Host $serviceDisplayName
Write-Host $serviceDescription
Write-Host $exePath
Write-Host "====================================="

$existingService = Get-WmiObject -Class Win32_Service -Filter "Name='$serviceName'"

if ($existingService) 
{
  "'$serviceName' exists already. Stopping."
  Stop-Service $serviceName
  "Waiting 5 seconds to allow existing service to stop."
  Start-Sleep -s 5

  "Seting new binpath for the service '$serviceName'"
  sc.exe config $serviceName binpath= $exePath
  "Waiting 5 seconds to allow service to be re-configured."
  Start-Sleep -s 5  
}
else
{
  "Installing the service '$serviceName'"
  New-Service -BinaryPathName $exePath -Name $serviceName -Credential $cred -DisplayName $serviceDisplayName -Description $serviceDescription -StartupType Automatic
  "Service installed"
  "Waiting 5 seconds to allow service to be installed."
  Start-Sleep -s 5
}

"Starting the service."
Start-Service $serviceName
"Completed."

Variables like '$(ServiceName)' are substituted from AzureDevops release variables. You can read more about the usage of variables HERE

It might also be useful to set common service options. I'm usually doing it via a separate powershell task:

$serviceName = "$(ServiceName)"
$failureDelay = [int] $(ServiceFailureDelayMs)
$failureAction = "restart"
$reset = [int] $(ServiceResetSeconds)

$service = Get-Service $serviceName -ErrorAction SilentlyContinue

if(!$service)
{
    Write-Host "##vso[task.LogIssue type=warning;]Directory Windows Service '$serviceName' not found, skip."
    return
}

"Updating '$serviceName' service recovery options."

sc.exe failure $service.Name actions= $failureAction/$failureDelay/$failureAction/$failureDelay/$failureAction/$failureDelay reset= $reset

"Done."
Related