CI CD Pipeline - IIS .NET Web API Application On An Azure VM using GitHub Workflows & Power Shell Script Automation

Greetings, in this post, we will be delving into the process of setting up a CI CD pipeline via GitHub workflow actions for a .NET Web API application running on an Azure Virtual Machine remotely using IIS. Some reasons you may want to host a web API on a VM rather than Azure’s default web API option include having full control over the environment, advanced networking and enhanced security controls. 

This post will outline the following;

  1. Creating a SSH key pair to connect to the Azure virtual machine. 
  2. Creating an Azure VM
  3. Creating a GitHub workflow file to deploy a .NET web API project to the virtual machine running IIS

Deployment of the virtual machine and its necessary dependencies will be executed via a windows power shell script to facilitate automation.

Let’s get started!

If you would like to follow this tutorial, you will need to install the Microsoft Azure CLI and login in to your Azure account sign the az login command: 

az login
    

1. Creating an SSH key pair

Before we begin, we will generate an SSH key pair on a local machine, this key pair will be used in order to securely transfer files during any CI CD processes when changes are made to the .NET web API code. In order to generate this key pair, use the following command: 

ssh-keygen -t rsa -b 2048
    

After entering this command, you will be prompted to save the key pair to a file. For now, this can be left empty by simply pressing enter. This will save the key pair to the default location which is the .ssh directory. As well as this, you will be requested to enter a password, this can also be left empty.

After the key pair has been generated, we can now create a Windows powershell script that will deploy a virtual machine within Microsoft Azure. Let’s start writing the script.

2. Powershell Automation Virtual Machine Deployment

Create a new Windows powers script with the file extension .ps1 and each of the following code snippets to the script: 

2.1.1. Create a New Resource Group:

New-AzResourceGroup -Name 'RgAzVMWebAPI' -Location 'ukwest'
    

The above line creates a new Azure Resource Group named 'RgAzVMWebAPI' in the 'UK West' Azure region. Resource groups in Azure are containers that hold related resources for an Azure solution—in this case, the VM and associated resources.

2.1.2 Secure Password Creation:

powershell
    
       $securePassword = ConvertTo-SecureString "Password!234" -AsPlainText -Force
    

Here, we're converting a plain text password into a secure string. This secure string is encrypted, making it safer to use in scripts and processes. The "-Force" parameter is used to bypass the confirmation prompt that would typically appear for this kind of operation.Obviously in an production environment, use a much stronger password!

2.1.3. Credential Object Creation:

powershell
    
       $credential = New-Object System.Management.Automation.PSCredential ("azureuser", $securePassword)
    

Using the secure password, a "PSCredential" object is created with the username 'azureuser'. This credential object is used to authenticate with Azure services securely.

2.1.4. Virtual Machine Deployment:

$result1 = New-AzVm 
     -ResourceGroupName 'RgAzVMWebAPI' 
     -Name 'AzVMWebAPI' 
     -Location 'ukwest' 
     -Image 'MicrosoftWindowsServer:WindowsServer:2022-datacenter-azure-edition:latest' 
     -Size 'Standard_B2s' 
     -VirtualNetworkName 'AzVMWebAPIVNet' 
     -SubnetName 'AzVMWebAPISubnet' 
     -SecurityGroupName 'AzVMWebAPINSG' 
     -PublicIpAddressName 'AzVMWebAPIPublicIP' 
     -OpenPorts 80,3389,22 
    
    

This multi-line command ("New-AzVm") initiates the creation of a new VM. It specifies various parameters like the resource group name, VM name, location, and the image to use (Windows Server 2022 Datacenter Azure Edition). It also defines the VM size ("Standard_B2s"), networking details (like VNet and subnet names), and security settings, including a network security group and public IP address. The "-OpenPorts" parameter is used to ensure that the VM can receive traffic on specific ports: 80 (HTTP), 3389 (RDP for remote desktop access), and 22 (SSH).

The backtick at the end of each line is the line continuation character in PowerShell, allowing the command to span multiple lines for better readability.

The result of the VM creation command is stored in the variable "$result1", which can be used for further processing or validation checks in subsequent script sections.

The next part of the script will consist of various commands that will install SSH onto the VM along with specifying the file location of the public key of the Ssh key we earlier generated on our local machine. This public key will be used later when verifying known SSH hosts on our remote VM. Please note, in an productuion environment, it is better practice to use port 443 over 80 for a secure connection. Port 80 is used in this article for demonstration purposes.

2.2 Open SSH Installation

After the VM is provisioned, the next step is to configure Secure Shell (SSH) access. SSH is a protocol that provides a secure channel over an unsecured network in a client-server architecture, allowing for secure login from one computer to another.

2.2.1. Public Key Retrieval:

$publicKeyPath = 'C:\Users\kieroncairns\.ssh\id_rsa.pub'
    $publicKey = Get-Content $publicKeyPath -Raw | Out-String | ForEach-Object { $_.TrimEnd() }
    

The script first defines the path to the public SSH key file. It then reads the content of this file and stores it in the "$publicKey" variable. This public key will be used for SSH authentication, enabling secure passwordless access to the VM.

2.2.2. OpenSSH Installation Script:

$installSsh = "@
        Add-WindowsCapability -Online -Name OpenSSH.Server
        Start-Service sshd
        Set-Service -Name sshd -StartupType 'Automatic'
    "@

Here, a multi-line string (a here-string in PowerShell terminology) is assigned to the $installSsh variable. This string contains commands to install the OpenSSH Server feature on the Windows VM, start the SSH service ("sshd"), and set it to start automatically with the system boot.

2.2.3. Executing the Installation Script on the VM:

try {
        Write-Output "Installing Open SSH on VM"
        Invoke-AzVMRunCommand -ResourceGroupName 'RgAzVMWebAPI' -VMName 'AzVMWebAPI' -CommandId 'RunPowerShellScript' -ScriptString $installSsh
        Write-Output "Open SSH Installation Successful"
    }
    catch {
        Write-Output "Error Installing Open SSH"
    }

The script then tries to execute the OpenSSH installation commands on the VM using the "Invoke-AzVMRunCommand" cmdlet. This cmdlet allows you to run PowerShell scripts directly on Azure VMs. If the installation succeeds, it prints a success message. If any error occurs during this process, the catch block captures the error and prints an error message.

The use of "try-catch" blocks in PowerShell is an effective way to handle exceptions and errors in scripts. It ensures that the script can gracefully handle any issues that might occur during execution and provides clear output about the success or failure of the operation.

2.3 Logging onto the VM to create necessary admin user account directory 

After the VM is provisioned, the next step is to configure Secure Shell (SSH) access. SSH is a protocol that provides a secure channel over an unsecured network in a client-server architecture, allowing for secure login from one computer to another.

2.2.1. Public Key Retrieval:

$publicKeyPath = 'C:\Users\kieroncairns\.ssh\id_rsa.pub'
    $publicKey = Get-Content $publicKeyPath -Raw | Out-String | ForEach-Object { $_.TrimEnd() }
    

The script first defines the path to the public SSH key file. It then reads the content of this file and stores it in the "$publicKey" variable. This public key will be used for SSH authentication, enabling secure passwordless access to the VM.

2.2.2. OpenSSH Installation Script:

$installSsh = "@
        Add-WindowsCapability -Online -Name OpenSSH.Server
        Start-Service sshd
        Set-Service -Name sshd -StartupType 'Automatic'
    "@

Here, a multi-line string (a here-string in PowerShell terminology) is assigned to the "$installSsh" variable. This string contains commands to install the OpenSSH Server feature on the Windows VM, start the SSH service ("sshd"), and set it to start automatically with the system boot.

2.2.3. Executing the Installation Script on the VM:

try {
        Write-Output "Installing Open SSH on VM"
        Invoke-AzVMRunCommand -ResourceGroupName 'RgAzVMWebAPI' -VMName 'AzVMWebAPI' -CommandId 'RunPowerShellScript' -ScriptString $installSsh
        Write-Output "Open SSH Installation Successful"
    }
    catch {
        Write-Output "Error Installing Open SSH"
    }

The script then tries to execute the OpenSSH installation commands on the VM using the "Invoke-AzVMRunCommand" cmdlet. This cmdlet allows you to run PowerShell scripts directly on Azure VMs. If the installation succeeds, it prints a success message. If any error occurs during this process, the catch block captures the error and prints an error message.

The use of "try-catch" blocks in PowerShell is an effective way to handle exceptions and errors in scripts. It ensures that the script can gracefully handle any issues that might occur during execution and provides clear output about the success or failure of the operation.

2.3 Logging onto the VM to create necessary admin user account directory

The next part of the script will log in the admin user of the VM via SSH in order to create the user account upon the VM’s first use. As well as this, the public key that was generated earlier will be added to an authorized_keys SSH file that is present within the admin accounts user directory. This step will allow for key-based authentication via SSH that will be executed in the next segment of the script after this:

2.3.1. Retrieve Public IP Address:

$publicIp = Get-AzPublicIpAddress -ResourceGroupName 'RgAzVMWebAPI' -Name 'AzVMWebAPIPublicIP'
    $ipAddress = $publicIp.IpAddress
    

The script uses the "Get-AzPublicIpAddress" cmdlet to fetch the public IP address assigned to the VM. This IP address is crucial for making remote connections.

2.3.2. Check IP Address Availability:

if (-not $ipAddress) {
        throw "Failed to retrieve IP Address."
    }
    

It checks if the IP address was successfully retrieved. If not, it throws an exception, effectively stopping the script and indicating a problem in the process.

2.3.3. Display IP Address:

Write-Output "VM Public IP Address: $ipAddress"
    

If the IP address is successfully retrieved, it is displayed to the user.

2.3.4. Configure SSH for Remote Login:

$sshCommands = "@
    echo 'Creating user profile on VM and adding public key to authorized_keys file...'
    mkdir C:\Users\azureuser\.ssh
    echo $publicKey > C:\Users\azureuser\.ssh\authorized_keys
    exit
    "@

The script prepares a string of SSH commands to create a ".ssh" directory in the Azure user's home directory and add the previously retrieved public key to the "authorized_keys" file. This setup allows for secure key-based authentication.

2.3.5. Establish SSH Connection and Execute Commands:

$sshCommand = "ssh -o StrictHostKeyChecking=no azureuser@$ipAddress"
    $fullCommand = "echo `"$sshCommands`" | $sshCommand"
    Invoke-Expression $fullCommand
    

The script then constructs an SSH command to connect to the VM and pipes the SSH commands to be executed upon login. The use of "StrictHostKeyChecking=no" bypasses the host key verification; this is not recommended for production environments as it could make the connection vulnerable to man-in-the-middle attacks.

2.3.6. Error Handling:

} catch {
        Write-Error "An error occurred: $_"
    }
    

The script is enclosed in a try-catch block to handle any potential errors during the SSH setup. If an error occurs, it is captured and printed out, and the script can perform any necessary cleanup.

2.4 Disabling Password Authentication & Enabling Key Based Authentication Via SSH

This part of the script is essential for ensuring that the VM can be securely managed via SSH without the need for password authentication, which is an important aspect of automating VM management. The script not only automates the deployment of the VM but also its initial configuration, reducing the manual steps required to get a VM up and running which will be a key aspect in the GitHub actions workflow CI CD yaml file.

2.4.1. Defining the SSH Configuration File Path:

$filePath = "C:\ProgramData\ssh\sshd_config"
    

The script sets the path to the SSH server configuration file, "sshd_config", which contains settings that dictate how the SSH server behaves.

2.4.2. Disabling Password Authentication:

$disablePwdAuthentication = "@
    (Get-Content $filePath) -replace '#PasswordAuthentication yes', 'PasswordAuthentication no' | Set-Content $filePath
    Restart-Service sshd
    "@

This block of code is designed to enhance security by disabling password authentication for SSH, forcing all users to connect using SSH keys instead. It does this by modifying the SSH configuration file to set "PasswordAuthentication" to "no", and then it restarts the SSH service to apply the changes.

2.4.3. Enabling Public Key Authentication:

$enablePubKeyAuthentication = "@
    (Get-Content $filePath) -replace '#PubkeyAuthentication yes', 'PubkeyAuthentication yes' | Set-Content $filePath
    Restart-Service sshd
    "@

This code ensures that public key authentication is enabled, allowing users to authenticate using their SSH keys. It modifies the corresponding setting in the configuration file and restarts the SSH service.

2.4.4. Disabling Match Group Admins:

$disableMatchGroupAdmins = "@
    (Get-Content $filePath) -replace 'Match Group administrators', '#Match Group administrators' | Set-Content $filePath
    Restart-Service sshd
    "@

In this step, the script comments out any specific settings that apply only to users in the "administrators" group. This is a way of ensuring that SSH configurations apply uniformly to all users.

2.4.5. Disabling Admin Authorized Keys File:

$disableAdminAuthorizedKeysFile = "@
    (Get-Content $filePath) -replace 'AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys', '#AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys' | Set-Content $filePath
    Restart-Service sshd
    "@

This section of the script comments out the line that specifies a separate "authorized_keys" file for administrator users, ensuring that the same "authorized_keys" file is used for all users.

2.4.6. Executing Configuration Changes:

Each block of configuration changes is executed on the VM using the "Invoke-AzVMRunCommand" cmdlet. This cmdlet allows the script to run PowerShell commands on the remote VM. After each command block is run, there is an output to the user indicating the action that has been taken (e.g., "Disabled password authentication in sshd_config").

2.4.7. Error Handling:

} catch {
        Write-Output "Error Creating SSH Directory: $_"
    }
    

The "try-catch" block encapsulates the commands to handle any errors that occur during the execution of the configuration changes. If an error occurs, it is caught, and a message is displayed to the user.

This segment is critical for securing the SSH server by implementing key-based authentication and disabling less secure password authentication. The script streamlines the process of hardening the SSH configuration, which is an important aspect of deploying a secure and production-ready VM in the cloud.

2.5 .NET 8 Installation

With the Azure VM now set up and secured, the next step in our deployment process is to install the necessary software for the .NET web API to run. In this case, we're focusing on installing the .NET 8 runtime, which is essential for running the web API project.

2.5.1. Starting the Installation Process:

Write-Output "Starting .NET 8 Runtime Installation"
    

This line simply outputs a message to the console indicating that the .NET 8 runtime installation is about to start.

2.5.2. Specifying the Installer URL:

$dotnetRuntimeInstallerUrl = "https://download.visualstudio.microsoft.com/download/pr/ab5e947d-3bfc-4948-94a1-847576d949d4/bb11039b70476a33d2023df6f8201ae2/dotnet-sdk-8.0.201-win-x64.exe"
    

Here we define the URL where the .NET 8 runtime installer can be downloaded. This URL points to the official Microsoft server hosting the installer package.

2.5.3. Defining the Local Installer Path:

{$installerPath = "C:\dotnet-sdk-8.0.201-win-x64.exe"
    

The script sets a local path on the VM where the .NET 8 runtime installer will be downloaded to.

2.5.4. Downloading and Installing the .NET 8 Runtime:

$installDotNetRuntime = "@
    Invoke-WebRequest -Uri $dotnetRuntimeInstallerUrl -OutFile $installerPath
    Start-Process -FilePath $installerPath -ArgumentList '/quiet', '/norestart' -Wait
    Remove-Item -Path $installerPath -Force
    "@

This block of code is a multi-line PowerShell command that handles three tasks: downloading the installer, executing it with silent and no-restart arguments, and cleaning up by deleting the installer file.

2.5.5. Executing the Installation on the VM:

Invoke-AzVMRunCommand -ResourceGroupName 'RgAzVMWebAPI' -VMName 'AzVMWebAPI' -CommandId 'RunPowerShellScript' -ScriptString $installDotNetRuntime
    

This line runs the installation commands on the remote VM using the "Invoke-AzVMRunCommand" cmdlet, which allows us to execute PowerShell scripts on Azure VMs.

2.5.6. Confirmation of Success:

Write-Output ".NET 8 Runtime Installation Successful"
    

Once the installation commands have been successfully executed, the script outputs a confirmation message.

2.5.7. Error Handling:

catch
    {
        Write-Output "Error Installing .NET 8 Runtime: $_"
    }
    

The "try-catch" block is used here to handle any exceptions that might occur during the installation process. If an error occurs, it is captured and an error message is displayed.

This segment ensures that the server environment is prepared with the necessary runtime for the .NET web API application. By scripting this installation, we automate what would otherwise be a manual and potentially error-prone process, resulting in a more efficient and reliable setup.

2.6 .NET 8 Hosting Bundle Installation

The script's last task is to install the .NET 8 Hosting Bundle, which is a set of components needed to host .NET applications on Windows servers, particularly in IIS (Internet Information Services). This is a critical step if the VM will serve as a web server for .NET web applications.

2.6.1. Initiating Hosting Bundle Installation:

Write-Output "Starting .NET 8 Hosting Bundle Installation"
    

A message is displayed to indicate that the installation process for the .NET 8 Hosting Bundle is beginning.

2.6.2. Hosting Bundle Installer URL:

$dotnetHostingBundleInstallerUrl = "https://download.visualstudio.microsoft.com/download/pr/98ff0a08-a283-428f-8e54-19841d97154c/8c7d5f9600eadf264f04c82c813b7aab/dotnet-hosting-8.0.2-win.exe"
    

The URL from which the .NET 8 Hosting Bundle installer can be downloaded is specified. This URL is typically obtained from the official Microsoft download page.

2.6.3. Installer Path Definition:

$installerPath = "C:\dotnet-hosting-8.0.2-win.exe"
    

The script sets the path on the VM where the Hosting Bundle installer will be saved.

2.6.4. Download and Installation Commands:

$installDotNetHostingBundle = "@
    Invoke-WebRequest -Uri $dotnetHostingBundleInstallerUrl -OutFile $installerPath
    Start-Process -FilePath $installerPath -ArgumentList '/install', '/quiet', '/norestart' -Wait
    Remove-Item -Path $installerPath -Force
    "@

This block of PowerShell script is responsible for downloading the Hosting Bundle installer to the VM, executing it silently, and cleaning up by removing the installer file after completion.

2.6.5. Execution on the VM:

Invoke-AzVMRunCommand -ResourceGroupName 'RgAzVMWebAPI' -VMName 'AzVMWebAPI' -CommandId 'RunPowerShellScript' -ScriptString $installDotNetHostingBundle
    

Similar to previous steps, the script runs the Hosting Bundle installation commands on the remote VM.

2.6.6. Confirming Successful Installation:

Write-Output ".NET 8 Hosting Bundle Installation Successful"
    

If the installation commands execute without errors, a success message is displayed.

2.6.7. Error Handling:

catch
    {
        Write-Output "Error Installing .NET 8 Hosting Bundle: $_"
    }
    

A "try-catch" block encapsulates the installation process to catch any errors. If an error occurs, an error message is outputted.

Completing the installation of the .NET 8 Hosting Bundle is a key step in preparing the Azure VM to host .NET web applications. This automation exemplifies the power of infrastructure as code, leading to more efficient, reproducible, and reliable deployments.

With everything now setup, we can execute the PowerShell script. Open a terminal prompt, navigate to the directory where the PowerShell script is located, and execute it. After a few moments, you will be required to enter the password for the admin user on the machine we earlier configured. Upon successful completion, the public IP address of the VM will be outputted in the terminal. This will be needed for our GitHub actions workflows CI CD yaml file

3. Create .NET Web API Project

In this step, we will create a boilerplate .NET 8 web API project. To do this, open a command prompt or PowerShell window, and enter the following:

dotnet new webapi -n AzWebAPI
    

4. Create GitHub Repository

After creating the boilerplate web API project, create a new GitHub repository for the web API either via command line or via Visual Studio’s GUI interface. After doing so, we need to set some repository variables that will be used throughout the workflows CI/CD pipeline file.

4.1 VM Public IP Address Variable

In GitHub, navigate to the repository settings & under the "Secrets and variables" section in the secutiry settings, add the IP address in the actions section

4.2 Private SSH Key Variable

Now do the same for your prviate SSH key, this can be found in ./ssh/id_rsa

5. GitHub Actions CI/CD Workflow File

In this section of the blog, we delve into the GitHub Actions workflow file that automates the build and deployment phases for a .NET Web API project. Automation of running unit tests can also be facilitated within this YAML file. Once any changes are committed to the .NET web API repository on the master branch, a series of sequenced operations are triggered that run on the latest Windows environments.

From restoring dependencies and compiling code, to optionally running unit tests and publishing artifacts, and finally deploying the application to a previously provisioned Azure VM— the workflow codifies the steps required to ensure that your .NET application is production-ready at every commit. Let's explore the nuances of this workflow file and how it integrates with the Azure VM setup we previously established through PowerShell scripting, creating a seamless pipeline from code commit to live deployment.

5.1 CI/CD Pipeline

name: CI/CD Pipeline
    
    on:
      push:
        branches:
          - master
    

This setup defines the initiation of our CI/CD process, ready to act on updates to the master branch, ensuring continuous integration with each code push.

5.2 Build & Deploy Config

We then define a build-and-deploy job set to run on the latest Windows environment. This job is the orchestrator, guiding the steps required to transform the code into a deployable state.

jobs:
      build-and-deploy:
        runs-on: windows-latest
    

The process kicks off with the checkout action, fetching the repository's code into the GitHub Actions runner, making the latest commits ready for the subsequent steps.

steps:
    - uses: actions/checkout@v2
    

5.3 Setup .NET & Build Project

Following this, the setup-dotnet action prepares the environment with the .NET version (8.0.x), ensuring the runner has the necessary runtime and SDK for building the .NET application.

- name: Setup .NET
      uses: actions/setup-dotnet@v2
      with:
        dotnet-version: '8.0.x'
    

The dotnet restore command comes next, restoring the project's dependencies to ensure all necessary packages are available for the build process.

- name: Restore dependencies
      run: dotnet restore
    

With dependencies in place, dotnet build compiles the code into executable artifacts. The --no-restore flag signifies that dependency restoration is already handled, focusing the process solely on compilation.

- name: Build Application
      run: dotnet build --no-restore
    

To verify the application's integrity, dotnet test runs the test suite, ensuring the code performs as intended.

- name: Run Tests
      run: dotnet test --no-build
    

As the build and test phases conclude, dotnet publish packages the application into a deployable format, readying it for deployment.

- name: Publish Application
      run: dotnet publish AzWebAPI/AzWebAPI.csproj --configuration Release --output publish/AzWebAPI
    

5.4 File Transfer & Deployment to VM

Before deployment, an SSH Agent is set up, utilizing the securely stored SSH private key from GitHub Secrets, establishing a secure connection to the Azure VM.

- name: Create SSH Agent
      uses: webfactory/ssh-agent@v0.5.3
      with:
        ssh-private-key: $secrets.SSH_PRIVATE_KEY
    

A simple command is executed to confirm the SSH connection's readiness for deployment, marking the preparation for the final deployment steps.

- name: Test SSH Connection
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.VM_IP_ADDRESS "echo Connection successful"
      shell: bash
    

Continuing the narrative of our CI/CD journey, we delve into the deployment phase, where the prepared artifacts are seamlessly transitioned to the live environment. This phase is marked by critical steps that ensure the application is not only deployed but also backed up and verified, embodying a holistic approach to continuous delivery.

- name: Backup Current wwwroot
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY 'powershell -Command "& {$timestamp = Get-Date -Format ''yyyyMMddHHmmss''; $destination = "C:/inetpub/wwwroot_backup_$timestamp"; if (!(Test-Path $destination)) {New-Item -ItemType Directory -Path $destination}; Copy-Item -Path C:/inetpub/wwwroot/* -Destination $destination -Recurse -Force}"'
      shell: bash
    

This step ensures that the current state of the "wwwroot" directory is preserved by creating a timestamped backup. This safeguard allows for recovery in unforeseen circumstances, providing a snapshot of the application before the new deployment.

- name: Delete Current wwwroot Content and Ensure Directory Exists
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "Get-ChildItem -Path C:/inetpub/wwwroot -Exclude wwwroot_backup_* | Remove-Item -Recurse -Force; if (-not (Test-Path -Path C:/inetpub/wwwroot)) { New-Item -Path C:/inetpub/wwwroot -ItemType Directory }""
      shell: bash
    

Following the backup, the "wwwroot" directory is cleansed of its contents, ensuring a pristine environment for the new deployment. This step meticulously avoids disrupting the backup directories, maintaining the integrity of the backups.

- name: List Files in Publish Directory
      run: ls -l publish/AzWebAPI
      shell: bash
    

Before proceeding with the file transfer, a listing of the files in the publish directory provides visibility into the artifacts ready for deployment, offering a checkpoint for verification.

- name: Transfer Published Files to VM
      run: |
        scp -o StrictHostKeyChecking=no -r ./publish/AzWebAPI azureuser@$secrets.SSH_PRIVATE_KEY:C:/inetpub/wwwroot/AzWebAPI
      shell: bash
    

The prepared artifacts are then securely transferred to the Azure VM, placing them within the "wwwroot" directory. This step utilizes secure copy protocol (SCP) to ensure the integrity and security of the file transfer.

- name: Copy New WebAPI to wwwroot
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "if (Test-Path -Path C:/inetpub/wwwroot/AzWebAPI) { Get-ChildItem -Path C:/inetpub/wwwroot/AzWebAPI -Recurse | ForEach-Object { Copy-Item -Path $_.FullName -Destination (Join-Path C:/inetpub/wwwroot $_.Name) -Force } } else { Write-Output 'C:/inetpub/wwwroot/AzWebAPI does not exist, skipping copy.' }""
      shell: bash
    

Upon successful transfer, the contents of the "AzWebAPI" directory are meticulously integrated into the "wwwroot" directory, ensuring that the new version of the application is ready for access.

- name: Move AzWebAPI contents to wwwroot
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "Get-ChildItem -Path C:/inetpub/wwwroot/AzWebAPI -Recurse | Move-Item -Destination C:/inetpub/wwwroot""
      shell: bash
    

This step further ensures that the contents of the "AzWebAPI" are not just copied but also moved to the root of the "wwwroot" directory, aligning with the desired structure for the live application.

- name: Restart IIS
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "Restart-Service -Name W3SVC -Force""
      shell: bash
    

The culmination of the deployment phase is marked by restarting the Internet Information Services (IIS), ensuring that all updates are loaded and the application is served with its newest features. This final step breathes life into the deployed application, making it accessible to users in its most updated form.

Through these meticulously crafted steps, the GitHub Actions workflow encapsulates the essence of continuous delivery, ensuring that every aspect of the deployment is handled with care, from backing up the current state to seamlessly integrating the new version into the live environment. This approach not only streamlines the deployment process but also embeds safeguards and verifications, ensuring the highest level of reliability and integrity in delivering updates to the live application.

In a web browser, if you now navigate to your Azure’s Virtual Machine public IP address followed by the default .NET web API weather forecast endpoint (/WeatherForecast), you will see the boilerplate JSON response.

And with that, we draw the curtains on this installment of our journey through the automation landscape. But rest assured, the adventure doesn't end here. In an upcoming post, I'll be diving into the realms of either Azure Virtual Machines or Docker containers to craft a bespoke iteration of GitHub Actions tailored to our unique needs. So, keep your eyes on this space and stay tuned for more insights and explorations in the ever-evolving world of DevOps. Your continued engagement fuels our exploration into these technological frontiers. Until next time, happy coding!

Following the backup, the "wwwroot" directory is cleansed of its contents, ensuring a pristine environment for the new deployment. This step meticulously avoids disrupting the backup directories, maintaining the integrity of the backups.

- name: List Files in Publish Directory
      run: ls -l publish/AzWebAPI
      shell: bash
    

Before proceeding with the file transfer, a listing of the files in the publish directory provides visibility into the artifacts ready for deployment, offering a checkpoint for verification.

- name: Transfer Published Files to VM
      run: |
        scp -o StrictHostKeyChecking=no -r ./publish/AzWebAPI azureuser@$secrets.SSH_PRIVATE_KEY:C:/inetpub/wwwroot/AzWebAPI
      shell: bash
    

The prepared artifacts are then securely transferred to the Azure VM, placing them within the "wwwroot" directory. This step utilizes secure copy protocol (SCP) to ensure the integrity and security of the file transfer.

- name: Copy New WebAPI to wwwroot
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "if (Test-Path -Path C:/inetpub/wwwroot/AzWebAPI) { Get-ChildItem -Path C:/inetpub/wwwroot/AzWebAPI -Recurse | ForEach-Object { Copy-Item -Path $_.FullName -Destination (Join-Path C:/inetpub/wwwroot $_.Name) -Force } } else { Write-Output 'C:/inetpub/wwwroot/AzWebAPI does not exist, skipping copy.' }""
      shell: bash
    

Upon successful transfer, the contents of the "AzWebAPI" directory are meticulously integrated into the "wwwroot" directory, ensuring that the new version of the application is ready for access.

- name: Move AzWebAPI contents to wwwroot
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "Get-ChildItem -Path C:/inetpub/wwwroot/AzWebAPI -Recurse | Move-Item -Destination C:/inetpub/wwwroot""
      shell: bash
    

This step further ensures that the contents of the "AzWebAPI" are not just copied but also moved to the root of the "wwwroot" directory, aligning with the desired structure for the live application.

- name: Restart IIS
      run: |
        ssh -o StrictHostKeyChecking=no azureuser@$secrets.SSH_PRIVATE_KEY "powershell -Command "Restart-Service -Name W3SVC -Force""
      shell: bash
    

The culmination of the deployment phase is marked by restarting the Internet Information Services (IIS), ensuring that all updates are loaded and the application is served with its newest features. This final step breathes life into the deployed application, making it accessible to users in its most updated form.

Through these meticulously crafted steps, the GitHub Actions workflow encapsulates the essence of continuous delivery, ensuring that every aspect of the deployment is handled with care, from backing up the current state to seamlessly integrating the new version into the live environment. This approach not only streamlines the deployment process but also embeds safeguards and verifications, ensuring the highest level of reliability and integrity in delivering updates to the live application.

In a web browser, if you now navigate to your Azure’s Virtual Machine public IP address followed by the default .NET web API weather forecast endpoint (/WeatherForecast), you will see the boilerplate JSON response.

And with that, we draw the curtains on this installment of our journey through the automation landscape. But rest assured, the adventure doesn't end here. In an upcoming post, I'll be diving into the realms of either Azure Virtual Machines or Docker containers to craft a bespoke iteration of GitHub Actions tailored to our unique needs. So, keep your eyes on this space and stay tuned for more insights and explorations in the ever-evolving world of DevOps. Your continued engagement fuels our exploration into these technological frontiers. Until next time, happy coding!