---
title: "Optimizing the .NET MVC Deployment Process: Automated Builds and Manual Deployment"
description: "How to use Jenkins Pipeline with MSBuild to automatically build legacy .NET MVC projects, and then deploy them after manually comparing differences with Beyond Compare."
canonical_url: "https://blog.markkulab.net/en/post/net-mvc-auto-build-manual-deploy"
author: "Mark Ku"
author_url: "https://blog.markkulab.net/en/author/mark-ku"
site: "Mark Ku's Tech Notes"
date_published: "2024-09-03 01:01:35 +0800"
category: "DevOps"
tags: ["jenkins", "msbuild", "dotnet", "cicd", "devops", "deploy", "powershell"]
language: "en"
license: "CC BY 4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
attribution: "when reusing or quoting, credit the author and link back to the original"
---

# Optimizing the .NET MVC Deployment Process: Automated Builds and Manual Deployment

## Background
Most systems at my company can be containerized, but there are some legacy websites—the oldest, largest, and most profitable—that we can't upgrade technologically for the time being. With many people involved in manual deployments, we often run into issues where someone forgets to commit a binary. Therefore, I wanted to automate the build part of the process to optimize our deployment workflow and reduce the impact of errors.

Because it's a legacy system using the old .NET MVC framework, it can't be containerized. A fully automated deployment would be a bit risky, so the deployment part will remain a manual process.

## Goals
*   When someone pushes any code to the `master` branch, Jenkins will automatically trigger a build and then commit and push the resulting binaries to GitHub.
*   In the production environment, we'll write a script to pull the latest binaries ready for deployment via Git. Then, we'll use Beyond Compare to check the differences in the binaries, and finally, perform the deployment manually.

## Environment Details
Since it's a legacy system, Jenkins is installed on a Windows Server.

## First, Find MSBuild
We all know that .NET MVC projects are built using the MSBuild tool. Based on our version of Visual Studio 2019, the corresponding MSBuild version is 16. However, on Microsoft's website, the download links for older versions of the MSBuild tools are hard to find. I eventually found the download link for the older Visual Studio 2019 from my [MSDN subscription account](https://my.visualstudio.com/Downloads?q=visual%20studio%202019).
![MSDN](https://blog.markkulab.net/content/markku/posts/net-mvc-auto-build-manual-deploy/images/msdn.png)

During installation, remember to check "Desktop development with C++".
![Installation](https://blog.markkulab.net/content/markku/posts/net-mvc-auto-build-manual-deploy/images/installation.png)


## Configure the MSBuild Path in Jenkins
The MSBuild installation path is:
The path for MSBuild.exe installed with Visual Studio 2019 is:
```
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin
```
## Download [nuget.exe](https://www.nuget.org/downloads)

Download nuget.exe to:
```
C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin 
```

## Download and Install the [.NET Framework 4.8 Developer Pack](https://dotnet.microsoft.com/en-us/download/dotnet-framework/net48)

## First, Configure a Webhook in the GitHub Repo to Notify Jenkins on Commits for Automatic Builds
![GitHub webhook setting](https://blog.markkulab.net/content/markku/posts/net-mvc-auto-build-manual-deploy/images/github-webhook.png)
```
http://jks.xxx.com/github-webhook/
```
## Next, Configure the Automatic Build Pipeline in Jenkins
Create a Job > Select "New Item" > "Pipeline", and configure the GitHub Project.

![GitHub project setting](https://blog.markkulab.net/content/markku/posts/net-mvc-auto-build-manual-deploy/images/github-project-setting.png)

```
properties([pipelineTriggers([githubPush()])])

pipeline {
	agent any

	environment {		
	}
	
	options {
		buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '3', daysToKeepStr: '', numToKeepStr: '3')
	}

	stages {
	    stage('Cleanup Directories') {
            steps {
                script {
                    // Delete the 'Api' and 'Artifact' directories
                    dir('Api') {
                        deleteDir()
                    }
                    dir('Artifact') {
                        deleteDir()
                    }
                    
                    dir('Publish') {
                        deleteDir()
                    }
                    echo "Deleted Api and Artifact directories."
                }
            }
        }
	
	    stage("GitHub Pull - Ｎet mvc api") {
            steps {
                dir('Api') {
                    git(
                        branch: 'master',
                        credentialsId: 'xxxx-xxxx-xxxx',
                        url: 'git@github.com:iRobot/MVC.Api.git'
                    )
                }
            }
        }
		
		 stage("GitHub Pull - Artifact") {
            steps {
                dir('Artifact') {
                    git(
                        branch: 'main',
                        credentialsId: 'xxxx-xxxx-xxxx',
                        url: 'git@github.com:iRobot/Artifact.git'
                    )
                }
            }
        }
        
    stage('Restore nuget') {
        steps {
        script {
            bat 'echo Current Directory: %CD%'
            bat 'echo Workspace Directory: %WORKSPACE%'
            bat '"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\MSBuild\\Current\\Bin\\nuget.exe" restore ".\\Api\\IRobot.WebCore\\IBuyPower.WebCore.sln"'
        }
        }
    }
    
    stage('Build') {
            steps {
                script {
                    // Define the MSBuild path and solution file
                    def msBuildPath = '"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\MSBuild\\Current\\Bin\\MSBuild.exe"'
                    def solutionPath = '".\\api\\IRobot.WebCore\\IRobot.WebCore.sln"'

                    // Build command with all necessary parameters
                    def buildCommand = "${msBuildPath} ${solutionPath} /t:Clean,Build /p:Configuration=enUSLive /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:DeleteExistingFiles=True /p:OutDir=\"%WORKSPACE%\\Publish\" /p:ExcludeGeneratedDebugSymbol=True -verbosity:minimal"

                    // Execute the build command
                    bat script: buildCommand, returnStatus: true
                }
            }
    }
    
     stage("Copy to Artifact") {
            steps {
                script {
                    
                    def buildCommand =  "robocopy \"%WORKSPACE%\\Publish\\_PublishedWebsites\\Core.Web\" \"%WORKSPACE%\\Artifact\" /E /COPY:DAT /MT:100"

                    // Execute the build command
                    bat script: buildCommand, returnStatus: true
                }
            }
        
    }
    
    stage('Check for Changes and Commit') {
    steps {
        dir('Artifact') {
            script {
                // Set local git configuration for user identity
                bat "git config user.email \"jenkins@example.com\""
                bat "git config user.name \"Jenkins CI\""

                // Stage all changes
                bat "git add ."

                // Commit with a timestamp
                bat "git commit -m \"Automated commit at ${new Date().format('HH:mm:ss')} by Jenkins\""

                // Attempt to push changes to remote repository
                def pushResult = bat(script: "git push --set-upstream origin main", returnStdout: true, returnStatus: true)
                if (pushResult != 0) {
                    echo "Failed to push changes. Error Code: ${pushResult}"
                }
            }
        }
    }
}	
}
}
```	
	
## Manual Deployment
Because it's a legacy system using the old .NET MVC framework, it can't be containerized. A fully automated deployment would be a bit risky, so the deployment part will remain a manual process.

### Then, Install Beyond Compare
### Write the PowerShell script `deploy.ps1`
```
git pull 

$LOCAL = "C:\Workspace\USApiArtifact\bin"
$REMOTE = "C:\inetpub\Core.Web\bin"

$env:Path = "C:\Program Files\Beyond Compare 4\" 
$filters = "/filters=-web.config"
BCompare.exe /excludefilter="web.config"  "$LOCAL" "$REMOTE" $filters
```
## Finally, after running the script, you can use Beyond Compare to verify that the DLLs for this deployment are as expected. Then, deploy the new version of the application by right-clicking and selecting "Copy to Right".
![Final](https://blog.markkulab.net/content/markku/posts/net-mvc-auto-build-manual-deploy/images/beycond-copare.png)

---

## About this article and its author

Originally published on [Mark Ku's Tech Notes](https://blog.markkulab.net/en/post/net-mvc-auto-build-manual-deploy)

License: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) — when reusing or quoting, credit the author and link back to the original

### About the author

**[Mark Ku](https://blog.markkulab.net/en/author/mark-ku)** — Software Solution Provider

- 10+ years senior software engineer, now an AI Builder
- Focused on large-platform architecture — North-American e-commerce, AI SaaS subscription billing
- Combining AI Agents and automation to build evolvable product foundations

### Free tools built by the author

All of these are free to use:

- [Free PDF Sign Tool](https://blog.markkulab.net/en/tools/pdf-sign): Online PDF sign tool — draw, type, or upload a signature, then drag, resize, and download. Everything runs in your browser; nothing is uploaded.
- [VS Code Refactory](https://blog.markkulab.net/en/tools/refactory): Refactory is a VS Code refactoring extension: 34 actions plus a 37-rule code-smell inspection layer with a Code Health dashboard, across 18 languages, backed by 534 tests. It learns your repo's conventions: where interfaces live, where DI is registered, whether 'use client' belongs. It ranks files by git churn × complexity so you know what to fix first, and hands any smell to the Claude Code already on your machine. Free to use, and your source never leaves your computer.
- [DB-Kit Database Manager](https://blog.markkulab.net/en/tools/db-kit): DB-Kit is a lightweight, cross-platform database manager built with Tauri + Rust + React. Manage MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, SQLite, MongoDB, Redis, Kafka, Elasticsearch and RabbitMQ from one consistent interface: passwords encrypted in the OS keychain, SSH tunnels, full CRUD, a visual query builder, stacked multi-statement result sets, cross-connection data transfer and compare/sync, Excel / CSV import & export, visualized execution plans, ER diagrams, scheduled backups, SQL stress testing with p50–p99 latency percentiles, a 15-rule SQL review engine, Kafka message browsing with monitoring & alerts, a bilingual UI (Traditional Chinese / English), a built-in AI assistant (natural-language SQL, AI review and tuning advice) and the dbk CLI. Free and open source (MIT), with installers for Windows, macOS and Linux.
- [VS Code Super Mermaid](https://blog.markkulab.net/en/tools/super-mermaid): Super Mermaid is a VS Code extension for beautiful Mermaid diagrams out of the box: auto-colored live preview, mouse pan & zoom, high-res PNG / SVG export, 21 templates and multiple themes. Free and open source (MIT).
- [React Super Mermaid](https://blog.markkulab.net/en/tools/react-super-mermaid): react-super-mermaid is an open-source React component library: render beautiful Mermaid diagrams with a single <MermaidViewer>, with built-in colorful / sketch themes, pan & zoom, in-diagram search, and high-res SVG / PNG export. Lightweight, SSR-safe, fully typed. Free and open source (MIT).
- [Jira / Confluence Super Mermaid](https://blog.markkulab.net/en/tools/jira-super-mermaid): An Atlassian Forge app: write Mermaid syntax directly inside a Jira issue or a Confluence page and get flowcharts, sequence diagrams, state machines and Gantt charts. 11 diagram types, SVG / PNG export, light and dark themes, full CJK support. Runs on Atlassian: your diagrams live in your own site and the app calls no third-party service. Free, coming soon to the Atlassian Marketplace.
- [Mermaid Live Preview](https://blog.markkulab.net/en/tools/mermaid-preview): Write Mermaid in your browser, see it render instantly, and share the whole diagram as a single link. No sign-up, nothing uploaded to a server, and mermaid.live share links work as-is.
- [React Intl Phone Number](https://blog.markkulab.net/en/tools/react-intl-phone-number): react-intl-phone-number is an open-source React component: framework-agnostic and antd-free, with E.164 in/out, a searchable flag / country-code dropdown, configurable validation levels (strict / mobile-strict / loose), themeable CSS, and i18n — phone logic powered by google-libphonenumber. Lightweight and fully typed. Free and open source (MIT).
- [Uptime Kuma Cluster](https://blog.markkulab.net/en/tools/uptime-kuma-cluster): Turn single-node Uptime Kuma into a highly available cluster: OpenResty + Lua smart load balancing, shared MariaDB state, health checks and automatic failover, plus cluster-management REST APIs. One Docker Compose command to start. Free and open source (MIT).
- [Special Education](https://blog.markkulab.net/en/education): Learning materials crafted for special education students

### Daily podcasts

- [Mark's Tech Insights — Daily AI News](https://blog.markkulab.net/en/category/tech-news): Daily curated AI and tech trends. Catch the latest developments via audio summaries — covering AI applications, software architecture, DevOps, and engineering practice. — RSS: https://blog.markkulab.net/feed.xml
- [AI股市蝦聊](https://blog.markkulab.net/en/category/ai-stock-chat): Every trading day, an AI-analyzed take on the Taiwan stock market, delivered as a two-host conversation covering the session and the next-day outlook. — RSS: https://blog.markkulab.net/ai-stock-chat/feed.xml
- [開源好物週報](https://blog.markkulab.net/en/category/open-source-weekly): A weekly two-host pick of free open-source tools surfaced from real Hacker News, GitHub, and Reddit buzz — what pain they solve and the fastest way to get started. — RSS: https://blog.markkulab.net/open-source-weekly/feed.xml

### Newsletter

[Subscribe to the newsletter](https://blog.markkulab.net/en/subscribe) — Be the first to know about new posts. No spam, unsubscribe anytime.
