Deploying and Debugging Raspberry Pi .NET Applications using VS Code

Historically, debugging embedded applications is hard. More often than not, you’ll be developing code on your desktop or laptop and deploying code to an embedded device.

Getting everything set up to allow that to be seamless is often difficult to achieve.

In this guide, I’ll show you how to configure your PC and a Raspberry Pi, to allow you to hit the F5 key in Visual Studio code and have the code shipped to the Raspberry Pi, run and have VS Code attach to the Visual Studio debugger actually on the Pi.

There are alternatives to this methodology. One of them is the Remote SSH extension for VS Code. However, this extension relies on the source code for your application to be stored on the device itself. I’m not a fan of this, as it means that you need to be consistently connected to the remote device to code. If you hop on a train and lose connectivity, you can’t save your work.

This guide (and single line install script!) will configure your system so you can code entirely on your local machine with no reliance on the remote machine until you need to deploy.

The previous version of this guide

This guide is actually a simplified version of a previous post I made about remote debugging .NET Applications on a Raspberry Pi from VS Code.

The previous version had relied on two different items; WSL with OpenSSH (primarily for rsync) and Putty’s Plink.

This version uses the free cwrsync tool to remove the need for WSL entirely. Further, using Scott Hansleman’s instructions here, we can remove the need for plink entirely.

Finally, I’ve created a one line script you can run from an elevated command prompt which makes getting your machine setup to Remote Deploy and Debug from Visual Studio Code a cinch!

Prerequisites

Before you start, you’re going to need to have the following installed;

The Quick Way – Run a script

Instead of following along with a heap of instructions, I’ve created a script that carries out everything you need to prepare your pc to Remote Deploy and Debug .NET code from Visual Studio Code.

Open a command prompt as an Administrator and navigate to your root C drive with “cd /“.

There are multiple versions of this script, depending on whether you are using .NET Core 3, .NET 5, .NET 6 or .NET 7;

For .NET Core 3 run…

curl --output remotedebugsetup.bat https://raw.githubusercontent.com/pjgpetecodes/dotnetcore3pi/master/remotedebugsetup.bat && remotedebugsetup.bat

For .NET 5 run…

curl --output remotedebugsetup.bat https://raw.githubusercontent.com/pjgpetecodes/dotnet5pi/master/remotedebugsetup.bat && remotedebugsetup.bat

For .NET 6 run…

curl --output remotedebugsetup.bat https://raw.githubusercontent.com/pjgpetecodes/dotnet6pi/master/remotedebugsetup.bat && remotedebugsetup.bat

For .NET 7 run…

curl --output remotedebugsetup.bat https://raw.githubusercontent.com/pjgpetecodes/dotnet7pi/master/remotedebugsetup.bat && remotedebugsetup.bat

The first thing the script will do is prompt you for the hostname of the Raspberry Pi you want to deploy your code to.

You’ll also be asked to enter the SSH password for the Pi, so the script can get the Pi setup to allow passwordless SSH – This is often simply “raspberry“.

This script will perform the following functions;

  • Prompts for the hostname of the Pi you’ll be connecting to
  • If they don’t exist, creates a set of SSH Keys using ssh-keygen
  • Adds the Pi at the hostname specified to the trusted hosts, which prevents the prompt to trust the thumbprint when connecting
  • Copies the Generated SSH Keys from the PC to the Pi
  • Downloads the Visual Studio Debugger on the Pi
  • Downloads cwRsync
  • Configures a “home” directory and sub directories for cwRsync and copies the generated SSH Keys to it to allow the cwRsync ssh tool to connect to the Pi without a password.
  • Creates a C# .NET console application in the root named as <Pi Hostname>_test
  • Adds a preconfigured launch.json and tasks.json to allow for the project to be deployed and remote debugged.
  • Launches the new console application in Visual Studio Code.
Remote Deployment and Debugging script

Trying out the Remote Deployment and Debugging from VS Code

Once the process has completed. You should be looking at a VS Code Window with the new Console Application Loaded.

VS Code Launches Autoamtically

Before we can try deploying to the Pi and Debugging remotely, we need to select the correct debugging profile.

Click on the “Run” toolbar button on the left, to show the “Run” options. Drop down the Run Option Dropdown and select the “.NET Core Launch (remote)”

Select the .NET Core Launch (Remote) Option

We’re now ready to test it all out! Go ahead and either press the green “Start Debugging” button, or hit F5 to start the process off.

The predefined tasks.json and launch.json files will run through the following processes on the way to debugging the code on the Pi;

  • Build the Project
  • Create Linux-ARM binaries using the dotnet publish command
  • Copy the necessary published linux-arm binary files over to the Pi’s Home directory using Rsync.
  • Use an SSH session to Launch and attach VS Code to the Visual Studio Debugger installed on the Raspberry Pi.
  • Run the code.

You’ll see the various steps complete, then the “Hello World” application will run with the output showing in the “Debug Console”.

You’ll see various messages reporting which dlls have been loaded. You’ll see “Hello World!” in the Debug Console in blue, before the application ends.

.NET application running on a Raspberry Pi

Congratulations, you’ve now deployed your first .NET application to a Raspberry Pi and debugged it remotely!

Deploying and Debugging a .NET Application from VS Code to a Raspberry Pi

Next Steps

Now that you’ve got everything set up, it’s possible for you to create new applications and also Deploy and Debug them remotely.

You’ll just need to copy the launch.json and tasks.json files to your .vscode directory once the project has been created.

You’ll then need to modify lines where we’ve referenced the Pi Hostname if you intend to deploy to a different Pi perhaps.

Also, if your solution has multiple projects, you’ll need to modify the tasks.json and launch.json files to point to the correct project within your overall solution.


The Step by Step Way (.NET 5)

If you’d like to take a more manual approach to the configuration, or perhaps if you’re interested in what the script is doing, then feel free to follow along from here instead of running the script above.

Generate SSH keys

The first thing we need to do is check if you already have some SSH keys, and if not, then we need to generate some SSH keys so that we can SSH into our Raspberry Pi without using a password.

We can create our keys (or leave them untouched if they already exist by running the following command;

if not exist c:\users\%username%\.ssh\id_rsa ssh-keygen -f c:\users\%username%\.ssh\id_rsa -t rsa -N ''

Copy SSH Files to the Pi

Now that we have the SSH keys, we need to get them on to our Pi.

We can use the windows Type command along with the SSH command to copy the id_rsa.pub file to the Pi. This way, we won’t need to enter a password when we use the SSH command.

Enter the following command, replacing the “<hostname>” placeholder with the hostname for your pi;

type c:\Users\%username%\.ssh\id_rsa.pub | ssh pi@<hostname> "cat >> ~/.ssh/authorized_keys"

You’ll be prompted for the password for your pi of course (In most cases this is “raspberry” of course).

We can test this works now with the following;

ssh pi@<hostname>

You should see the nice SSH Session appear…

Passwordless SSH Session

Copying files to the Pi with cwRsync

We’ll be using a tool called Rsync to copy files to the Pi over SSH. The beauty of Rsync is, not only can it copy files over an SSH connection to the Pi, but it will also compare the files already on the Pi, with the files about to be copied. It will then only copy the files which have changed.

This is great for the development lifecycle, as we’re often only changing the odd file here and there normally, so after the first deployment, only one or two files need to be copied to the Pi.

Rsync is a Linux application, in the previous blog post we use the Windows Subsystem for Linux to use the Rsync application.

However, there’s a Windows implementation based on Cygwin called cwRsync, we can use this instead of needing to configure WSL at all.

Due to an issue with this version of cwRsync, we also need to create a directory structure that cwRsync can use to store it’s known list of hosts. Normally this would be stored in our userdata folder… But for some reason, cwRsync needs a different directory.

We also need to copy the SSH keys we created above to this new directory.

Finally, we need to set the correct permissions on the new “home” folder, otherwise cwRsync will complain (rather misleadingly) that our keys have insufficient security

Assuming you have an up to date version of Windows 10, you can run a set of commands to set everything up.

Open a command prompt as an Administrator and run the following commands;

mkdir c:\home\%USERNAME%\.ssh
cd c:\home
curl --output cwrsync.zip https://itefix.net/dl/free-software/cwrsync_6.2.7_x64_free.zip && tar -xf cwrsync.zip
rename bin cwrsync
move cwrsync c:\
copy %userprofile%\.ssh\* c:\home\%username%\.ssh
icacls c:\home /setowner %username% /R
icacls c:\home /inheritance:r /grant:r %username%:(OI)(CI)F /T

Install the Visual Studio Debugger on the Pi

When we’re debugging from VS Code, we’ll be using the Microsoft Visual Studio Debugger Package to carry out the debugging on the Pi. The VS Debugger will run the code on the pi through an SSH session, and VS code will connect remotely to the debug session allowing us to interact with that session on our local machine.

To install the VS Debugger, run the following commands;

ssh pi@<Pi Host Name> "cd ~/ && curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l ~/vsdbg"

You’ll need to switch out the “<Pi Host Name>” place holder for the Host Name of your Raspberry Pi.

That will begin the process of downloading and installing the VS Debugger on the Raspberry Pi;

Download and install VS Debugger

Create and configure a .NET 5 console application

It’s now time to create a .NET 5 console application and configure it to Remote Deploy and Debug to our Raspberry Pi.

Choose a suitable location on your machine and run the following command, replacing the “<name of project>” placeholder with the name of your project;

dotnet new console -o <name of project>

Navigate to your new project in the command prompt and use the following to open your new project in VS Code;

code .

Once VS Code has finished loading up, if you’ll be prompted by the C# Extension to add the Build and Debug Assets to the project, go ahead and click the “Yes” button;

C# Extension Add Assets

This will add default launch.json and tasks.json files to your project in a directory called “.vscode”;

VS Code launch.json and tasks.json

Adding VS Code Tasks for Publish and Deploy

We now need to add a couple of extra tasks to the tasks.json file to firstly create some binaries which are compatible with the Raspberry Pi.

The Raspberry Pi has an ARM processor and is running Linux. We can create compatible binaries using the following command;

dotnet publish -r linux-arm -o bin\linux-arm\publish --no-self-contained

Next we can deploy these compiled binaries to the Raspberry Pi using our new cwRsync tool which we configured earlier, using the following command;

rsync -rvuz --rsh=\"c:\\cwrsync\\ssh\" --chmod=700 bin/linux-arm/publish/ pi@r<Pi Host Name>:share/${workspaceFolderBasename}/

We can have Visual Studio Code carry out both of these tasks for us by adding them to the tasks.json file.

Open the tasks.json file and add the following two tasks below the “watch” task (remember to place this code immediately after the “watch” task curly brace, and not at the end of the file!);

,
{
    "label": "RaspberryPiPublish",
    "command": "sh",
    "type": "shell",
    "dependsOn": "build",
    "windows": {
        "command": "cmd",
        "args": [
            "/c",
            "\"dotnet publish -r linux-arm -o bin\\linux-arm\\publish --no-self-contained\""
        ],
        "problemMatcher": []
    }
    
},
{
    "label": "RaspberryPiDeploy",
    "type": "shell",
    "dependsOn": "RaspberryPiPublish",
    "presentation": {
        "reveal": "always",
        "panel": "new"
    },
    "windows": {
        "command": "c:\\cwrsync\rsync -rvuz --rsh=\"c:\\cwrsync\\ssh\" --chmod=700 bin/linux-arm/publish/ pi@<Pi Host Name>:share/${workspaceFolderBasename}/"
    },
    "problemMatcher": []
}

You’ll need to switch out the “<Pi Host Name>” placeholder with the correct Host Name for your Raspberry Pi.

Adding VS Code Launch config to Launch and attach the Remote Debugger

Next, we need to configure the launch.json file to connect to the Visual Studio debugger on the Pi, run our code and begin debugging.

Open the launch.json file and add the following section of json below the “.NET Core Attach” launch task;

,
{
    "name": ".NET Core Launch (remote)",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "RaspberryPiDeploy",
    "program": "dotnet",
    "args": ["/home/pi/${workspaceFolderBasename}/${workspaceFolderBasename}.dll"],
    "cwd": "/home/pi/${workspaceFolderBasename}",
    "stopAtEntry": false,
    "console": "internalConsole",
    "pipeTransport": {
        "pipeCwd": "${workspaceFolder}",
        "pipeProgram": "ssh",
        "pipeArgs": [
            "pi@<Pi Host Name>"
        ],
        "debuggerPath": "/home/pi/vsdbg/vsdbg"
    }
}

You’ll again need to switch out the “<Pi Host Name>” placeholder with the correct Host Name for your Raspberry Pi.

Trying out the Remote Deployment and Debugging from VS Code

You’re not ready to Deploy and Debug your .NET 5 application on the Raspberry Pi!

VS Code Launches Autoamtically

Before we can try deploying to the Pi and Debugging remotely, we need to select the correct debugging profile.

Click on the “Run” toolbar button on the left, to show the “Run” options. Drop down the Run Option Dropdown and select the “.NET Core Launch (remote)

Select the .NET Core Launch (Remote) Option

We’re now ready to test it all out! Go ahead and either press the green “Start Debugging” button, or hit F5 to start the process off.

The predefined tasks.json and launch.json files will run through the following processes on the way to debugging the code on the Pi;

  • Build the Project
  • Create Linux-ARM binaries using the dotnet publish command
  • Copy the necessary published linux-arm binary files over to the Pi’s Home directory using Rsync.
  • Use an SSH session to Launch and attach VS Code to the Visual Studio Debugger installed on the Raspberry Pi.
  • Run the code.

You’ll see the various steps complete, then the “Hello World” application will run with the output showing in the “Debug Console”.

You’ll see various messages reporting which dlls have been loaded. You’ll see “Hello World!” in the Debug Console in blue, before the application ends.

.NET application running on a Raspberry Pi

Congratulations, you’ve now deployed your first .NET application to a Raspberry Pi and debugged it remotely!

Deploying and Debugging a .NET Application from VS Code to a Raspberry Pi

Next Steps

Now that you’ve got everything set up, it’s possible for you to create new applications and also Deploy and Debug them remotely.

You’ll just need to copy the launch.json and tasks.json files to your .vscode directory once the project has been created.

You’ll then need to modify lines where we’ve referenced the Pi Hostname if you intend to deploy to a different Pi perhaps.

Also, if your solution has multiple projects, you’ll need to modify the tasks.json and launch.json files to point to the correct project within your overall solution.