During my job I’ve to maintain several projects generally written in .NET Core or Angular project.
Each time that me or someone of the team I belong have to create a new software version, looses a lot of time to manually update the various projects versions.
So this is a little powershell script that allows you to update .csproj and package.json version.

If you just want to jump to the solution look here, otherwise there are a few thing I’d like to tell you!

How version is composed?

Typically a version number is composed by 3 or 4 digits separated by a dot (.), like this: 1.20.2.3589.
looking to this example:

  • 1 = major version
    Typically you have to change this if your project has very significative changes that prevent all backward compatibility.
    A new project that improve an existing one built with different technologies/languages (for example the “version 1” was written in VB6, the “version 2” is written in .NET Core)
  • 20 = minor version
    This version indicates that there is some new changes in your software or you have developed a new functionality.
  • 2 = patch version
    With the same version, some problems have been corrected
  • 3589 = build version
    This number could be not exists. If exist typically is a reference to the build process that has make the package. Generally is useful if you have some automatic build procedure (such as Azure DevOps build pipeline or Bitbicket build pipeline), which provide you an identifier to the agent that ha generate you package.
    If you have it, my suggestion is to use it: in this way you can identify wich process has generated you package and easily detect if there is some problem with a specific build. Or, simply, you can differentiate two packages with the same version.

What is .csproj and package.json file?

csproj

.csproj” is a Visual Studio .NET C# Project file extension. This file will have information about the files included in that project, assemblies used in that project, project GUID and project version etc. This file is related to your project. It will be automatically generated when we create a new project

https://social.msdn.microsoft.com/Forums/vstudio/en-US/e8e9a55e-6e43-4c4d-93f6-5a89157b12f1/what-is-csproj-file?forum=csharpgeneral

A csproj file looks like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <Authors>Davide Zoccarato</Authors>
    <Version>0.0.1.7</Version>
    <VersionPrefix>0.0.1.7</VersionPrefix>
    <AssemblyVersion>0.0.1.7</AssemblyVersion>
    <FileVersion>0.0.1.7</FileVersion>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DocumentationFile>bin\Debug\net461\Industry40.Monitoring.PLC.xml</DocumentationFile>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DocumentationFile>bin\Release\net461\Industry40.Monitoring.PLC.xml</DocumentationFile>
    <NoWarn>1701;1702;1591</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Dapper" Version="1.60.6" />
    <PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.3" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.6" />
    <PackageReference Include="S7netplus" Version="0.3.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\Industry40.Monitoring.Common\Industry40.Monitoring.Common.csproj" />
  </ItemGroup>

  <ItemGroup>
    <None Update="Dependencies\x64\SQLite.Interop.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Update="Dependencies\x86\SQLite.Interop.dll">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

  <ItemGroup>
    <Folder Include="Migrations\" />
  </ItemGroup>

</Project>

There are several tag in here, but the tags below is the only interested in this article.

  • Version
    The Version property is the value most commonly set when building .NET Core applications. It controls the default values of all the version numbers embedded in the build output, such as PackageVersion and AssemblyVersion so it’s often used as the single source of the app/library version.
  • VersionPrefix
    You can use VersionPrefix to set the “base” version number for your library/app. It indirectly controls all of the other version numbers generated by your app (though you can override it for other specific versions).
  • AssemblyVersion
    Every assembly you produce as part of your build process has a version number embedded in it, which forms an important part of the assembly’s identity. It’s stored in the assembly manifest and is used by the run time to ensure correct versions are loaded etc.
  • FileVersion
    The file version is literally the version number exposed by the DLL to the file system. It’s the number displayed in Windows explorer, which often matches the AssemblyVersion, but it doesn’t have to. The FileVersion number isn’t part of the assembly identity as far as the .NET Framework or run time are concerned.

https://andrewlock.net/version-vs-versionsuffix-vs-packageversion-what-do-they-all-mean/

packages.json

package.json” is similar to csproj file, but is used for npm packages, angular project, etc.. is contains the dependencies to other npm packages, info about project name, version etc..

http://npm.github.io/using-pkgs-docs/package-json/using-a-packagejson.html

A package.json file looks like this:

{
  "name": "gestionale.axel",
  "version": "2.0.1",
  "license": "GNU AGPLv3",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "electron": "ng build --base-href ./ && electron ."
  },
  "private": true,
  "dependencies": {
    "@agm/core": "^1.0.0-beta.5",
    "@angular/animations": "^7.0.2",
    "@angular/cdk": "^7.0.2",
    "@angular/common": "^7.0.2",
    "@angular/compiler": "^7.0.2",
    "@angular/core": "^7.0.2",
    "@angular/flex-layout": "^7.0.0-beta.19",
    "@angular/forms": "^7.0.2",
    "@angular/http": "^7.0.2",
    "@angular/material": "^7.0.2",
    "@angular/material-moment-adapter": "^7.0.2",
    "@angular/platform-browser": "^7.0.2",
    "@angular/platform-browser-dynamic": "^7.0.2",
    "@angular/router": "^7.0.2",
    "@types/jspdf": "^1.1.31",
    "@types/lodash": "^4.14.117",
    "core-js": "^2.5.7",
    "crypto-js": "^3.1.9-1",
    "file-saver": "^2.0.0-rc.4",
    "hammerjs": "^2.0.8",
    "jspdf": "^1.4.1",
    "jspdf-autotable": "^2.3.5",
    "moment": "^2.22.2",
    "node-sass": "^4.9.4",
    "rxjs": "^6.3.3",
    "rxjs-compat": "^6.3.3",
    "stream": "0.0.2",
    "timers": "^0.1.1",
    "xlsx": "^0.14.0",
    "zone.js": "^0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.10.4",
    "@angular/cli": "7.0.4",
    "@angular/compiler-cli": "^7.0.2",
    "@angular/language-service": "^7.0.2",
    "@types/jasmine": "~2.8.9",
    "@types/jasminewd2": "~2.0.5",
    "@types/node": "~10.12.2",
    "codelyzer": "^4.5.0",
    "electron": "^4.1.4",
    "jasmine-core": "~3.3.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~3.1.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^2.0.4",
    "karma-jasmine": "~1.1.2",
    "karma-jasmine-html-reporter": "^1.4.0",
    "protractor": "~5.4.1",
    "ts-node": "~7.0.1",
    "tslint": "~5.11.0",
    "typescript": "3.1.6"
  }
}

The script

The idea is to have a tool able to read this files, extract the version information contents into them, change them it and re-write the file with the new version.
You can copy and paste this code into your “updateVersion.ps1” and use it!

For example:
updateVersion.ps1: +1 on patch version
updateVersion.ps1-MINOR: + 1 on minor version
updateVersion.ps1 -BUILD {BUILD_NUMBER}: set the build number

For csproj file

param (
    [Parameter(Mandatory=$false)][switch]$HELP,
    [Parameter(Mandatory=$false)][switch]$MAJOR,
    [Parameter(Mandatory=$false)][switch]$MINOR,
    [Parameter(Mandatory=$false)][switch]$PATCH,
    [Parameter(Mandatory=$false)][string]$BUILD,
    [Parameter(Mandatory=$false)][switch]$TAG_AND_PUSH,
    [Parameter(Mandatory=$false)][switch]$SHOW
)
if($HELP) {            
    echo "# version updater for csproj files";
    echo "bump version +1 of all *.csproj file in subfolder";
    echo "# params:";
    echo " -HELP (not mandatory) => call this help";
    echo " -SHOW (not mandatory) => shows versions. DOES NOT MODIFY THEM";
    echo " -MAJOR (not mandatory) => +1 on major version. reset minor and patch to 0";
    echo " -MINOR (not mandatory) => +1 on minor version. reset patch to 0";
    echo " -PATCH (not mandatory) => +1 on patch version.";
    echo " none of above => +1 on patch version";
    echo " -BUILD (not mandatory) => exact version. If not specified, default is 0";
    echo " -TAG_AND_PUSH (not mandatory) => automatically tag with new version and push the *.csproj to repo";
    echo "# example:";
    echo ".\updateVersion.ps1";
    echo ".\updateVersion.ps1 -TAG_AND_PUSH";
    echo ".\updateVersion.ps1 -MINOR -TAG_AND_PUSH";
    echo ".\updateVersion.ps1 -MAJOR -TAG_AND_PUSH";
    echo ".\updateVersion.ps1 -BUILD 1.2.3.4 -TAG_AND_PUSH";
    return;
}
$projectInfos = ( Get-ChildItem -Recurse -Filter *.csproj ).FullName
# tags
$VersionPrefixTag = "VersionPrefix";
$VersionTag = "Version";
$AssemblyVersionTag = "AssemblyVersion";
$FileVersionTag = "FileVersion";
$newVersion = "";
foreach ($info in $projectInfos) {
  # version info
  [int]$_major = 0;
  [int]$_minor = 0;
  [int]$_patch = 0;
  [int]$_build = 0;
  $version = "";  
  # look for version
  foreach($line in Get-Content $info) {
    if($line -match "(?<=<$FileVersionTag>)(.*)(?=<\/$FileVersionTag>)"){        
        $version = $matches[1].Split("{.}");
        $_major = [int]$version[0];
        $_minor = [int]$version[1];
        $_patch = [int]$version[2];
        $_build = [int]$version[3];
        if ($SHOW) {
        
            $file = Split-Path $info -leaf;
            Write-Host "${file}: " -NoNewline -ForegroundColor DarkGray
            Write-Host "${version}" -ForegroundColor Gray
            continue;           
        }
        
        if($MAJOR) {
            $_major = $_major + 1;
            $_minor = 0;
            $_patch = 0;
            $_build = 0;
        }
        if($MINOR) {
            $_minor = $_minor + 1;
            $_patch = 0;
            $_build = 0;
        }
        if($PATCH) {
            $_patch = $_patch + 1;
            $_build = 0;
        }
        if(-not $MAJOR -and -not $MINOR -and -not $PATCH -and -not [string]::IsNullOrWhiteSpace($BUILD)) {            
            $_build = $BUILD;
        }
        else {
            $_build = 0;
        }
        $version = "$_major.$_minor.$_patch.$_build";
    }
  }
  # replace version where needed
  if (-not $SHOW) {
      (Get-Content $info) -replace "(?<=<$VersionPrefixTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$VersionPrefixTag>)", "$version" | Set-Content $info
      (Get-Content $info) -replace "(?<=<$VersionTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$VersionTag>)", "$version" | Set-Content $info
      (Get-Content $info) -replace "(?<=<$AssemblyVersionTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$AssemblyVersionTag>)", "$version" | Set-Content $info
      (Get-Content $info) -replace "(?<=<$FileVersionTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$FileVersionTag>)", "$version" | Set-Content $info
      #display version inforation to output
      echo "BUMP VERSION TO: $version ($info)";
      $newVersion = $version;
  }
}
if($TAG_AND_PUSH -and -not $SHOW) {    
    git pull
    git add *.csproj
    git commit -m "bump version to $newVersion"
    git push
    git pull
    git tag -a "$newVersion" -m "$newVersion"    
    git push origin --tags
} 

For package.json file

param (
    [Parameter(Mandatory=$false)][switch]$HELP,
    [Parameter(Mandatory=$false)][switch]$MAJOR,
    [Parameter(Mandatory=$false)][switch]$MINOR,
    [Parameter(Mandatory=$false)][switch]$PATCH,
    [Parameter(Mandatory=$false)][string]$BUILD,
    [Parameter(Mandatory=$false)][switch]$TAG_AND_PUSH,
    [Parameter(Mandatory=$false)][switch]$SHOW
)
if($HELP) {            
    echo "# version updater for package.json files";
    echo "bump version +1 in package.json";
    echo "# params:";
    echo " -HELP (not mandatory) =&amp;amp;gt; call this help";
    echo " -SHOW (not mandatory) =&amp;amp;gt; shows versions. DOES NOT MODIFY THEM";
    echo " -MAJOR (not mandatory) =&amp;amp;gt; +1 on major version. reset minor and patch to 0";
    echo " -MINOR (not mandatory) =&amp;amp;gt; +1 on minor version. reset patch to 0";
    echo " -PATCH (not mandatory) =&amp;amp;gt; +1 on patch version.";
    echo " none of above =&amp;amp;gt; +1 on patch version";
    echo " -BUILD (not mandatory) =&amp;amp;gt; exact version. If not specified, default is 0";
    echo " -TAG_AND_PUSH (not mandatory) =&amp;amp;gt; automatically tag with new version and push the *.csproj to repo";
    echo "# example:";
    echo ".\updateVersion.ps1";
    echo ".\updateVersion.ps1 -TAG_AND_PUSH";
    echo ".\updateVersion.ps1 -MINOR -TAG_AND_PUSH";
    echo ".\updateVersion.ps1 -MAJOR -TAG_AND_PUSH";
    echo ".\updateVersion.ps1 -BUILD 1.2.3.4 -TAG_AND_PUSH";
    return;
}
$projectInfos = ( Get-ChildItem -Filter package.json ).FullName
$newVersion = "";
foreach ($info in $projectInfos)
{
    # version info
    [int]$_major = 0;
    [int]$_minor = 0;
    [int]$_patch = 0;
    [int]$_build = 0;
    $version = "";
    # look for version
    foreach($line in Get-Content $info) {
        if($line -match """Version""\:\s*""(.*)"","){        
            $version = $matches[1].Split("{.}");
            $_major = [int]$version[0];
            $_minor = [int]$version[1];
            $_patch = [int]$version[2];
            $_build = [int]$version[3];
            if ($SHOW) {
            
                $file = Split-Path $info -leaf;
                Write-Host "${file}: " -NoNewline -ForegroundColor DarkGray
                Write-Host "${version}" -ForegroundColor Gray
                continue;           
            }
            
            if($MAJOR) {
                $_major = $_major + 1;
                $_minor = 0;
                $_patch = 0;
                $_build = 0;
            }
            if($MINOR) {
                $_minor = $_minor + 1;
                $_patch = 0;
                $_build = 0;
            }
            if($PATCH) {
                $_patch = $_patch + 1;
                $_build = 0;
            }
            if(-not $MAJOR -and -not $MINOR -and -not $PATCH -and -not [string]::IsNullOrWhiteSpace($BUILD)) {            
                $_build = $BUILD;
            }
            else {
                $_build = 0;
            }
            $version = "$_major.$_minor.$_patch.$_build";
        }
  }
  # replace version where needed
  (Get-Content $info) -replace "(?<=""Version""\:\s*"")([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?="",)", "$version" | Set-Content $info
  #display version inforation to output
  echo "BUMP VERSION TO: $version ($info)";
  $newVersion = $version;
}
if($TAG_AND_PUSH) {
    git pull
    git add package.json
    git commit -m "bump version to $newVersion"
    git push
    git pull
    git tag -a "v$newVersion" -m "v$newVersion"    
    git push origin --tags
}

Looking at the script

$projectInfos = ( Get-ChildItem -Recurse -Filter *.csproj ).FullName

Get all csproj file in folder and subfolder

foreach ($info in $projectInfos)
{
    # version info
    [int]$_major = 0;
    [int]$_minor = 0;
    [int]$_patch = 0;
    [int]$_build = 0;
    $version = "";
    # look for version
    foreach($line in Get-Content $info) {
        if($line -match "(?<=<$FileVersionTag>)(.*)(?=<\/$FileVersionTag>)"){

For each csproj file look for <FileVersionTag> tag, via regular expression and read the version contains into it

$version = $matches[1].Split("{.}");
$_major = [int]$version[0];
$_minor = [int]$version[1];
$_patch = [int]$version[2];
$_build = [int]$version[3];

Split the version into major, minor, patch and build number, then manipulate according the given option

if (-not $SHOW) {
      (Get-Content $info) -replace "(?<=<$VersionPrefixTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$VersionPrefixTag>)", "$version" | Set-Content $info
      (Get-Content $info) -replace "(?<=<$VersionTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$VersionTag>)", "$version" | Set-Content $info
      (Get-Content $info) -replace "(?<=<$AssemblyVersionTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$AssemblyVersionTag>)", "$version" | Set-Content $info
      (Get-Content $info) -replace "(?<=<$FileVersionTag>)([\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)([\.]{1}[\d]+)*(?=<\/$FileVersionTag>)", "$version" | Set-Content $info
      #display version inforation to output
      echo "BUMP VERSION TO: $version ($info)";
  }

Replace the new version via regular expression and write to original csproj file

if($TAG_AND_PUSH -and -not $SHOW) {    
    git pull
    git add *.csproj
    git commit -m "bump version to $newVersion"
    git push
    git pull
    git tag -a "$newVersion" -m "$newVersion"    
    git push origin --tags
}

Last, but not the least, this allows you to push the version update to your repo and add a “version tag” in format “major.minor.patch.build”