146

I have spent the better part of a few hours trying to find a way to auto-increment versions in a .NETCoreApp 1.1 (Visual Studio 2017).

I know the the AssemblyInfo.cs is being created dynamically in the folder: obj/Debug/netcoreapp1.1/

It does not accept the old method of: [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.*")]

If I set the project to package I can set versions there but this seems to be used to build the AssemblyInfo.cs file.

My question is, has anyone figured out how to control version in .NET Core (or .NETStandard for that matter) projects.

Jason H
  • 4,996
  • 6
  • 27
  • 49
  • I don't know how far you got with this, but looks like I asked almost the same question a different way ( http://stackoverflow.com/a/43280282/685341 ) - Maybe the accepted answer to this question will help you out; you can just pass the `/p:` flag to `dotnet msbuild` in your build script and set version, company, copyright... all that good stuff. – Jay Apr 10 '17 at 20:45
  • 2
    Thanks for the info. That just open up additional options. – Jason H Apr 10 '17 at 20:49
  • Previously * was supported for AssemblyVersion, not for AssemblyFileVersion- see [Can I automatically increment the file build version when using Visual Studio?](//stackoverflow.com/a/356556) – Michael Freidgeim Oct 28 '17 at 00:42
  • 4
    FWIW the wildcard in the assembly version is not supported because for these new project, the compiler's "deterministic" mode is active by default. Since auto-increment would break determinism (same input > same output) it is disallowed in that mode. You can set `False` in the csproj to use it. (or use any other MSbuild logic to calculate ``/``) – Martin Ullrich Oct 28 '17 at 09:20

21 Answers21

80

Add <Deterministic>False</Deterministic> inside a <PropertyGroup> section  of .csproj

The workaround to make AssemblyVersion * working is described in “Confusing error message for wildcard in [AssemblyVersion] on .Net Core #22660”

Wildcards are only allowed if the build is not deterministic, which is the default for .Net Core projects. Adding <Deterministic>False</Deterministic> to csproj fixes the issue.

The reasons why .Net Core Developers consider Deterministic Builds beneficial described in http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html and Compilers should be deterministic: same inputs generate same outputs #372

However if you are using TeamCity, TFS or other CI/CD tool, it's probably better to keep the version number controlled and incremented by them and pass to build as a parameter (as it was suggested in other answers) , e.g.

msbuild /t:build /p:Version=YourVersionNumber /p:AssemblyVersion=YourVersionNumber

Package number for NuGet packages

msbuild /t:pack /p:Version=YourVersionNumber   
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
  • Thank you! I knew there was a hidden lever for opening the treasure room! I am migrating an old project to the new .NET SDK, and I really wanted to do this fast, without the hassle of finding automated version increment solutions. In fact, the more compatible to the old ways the better for my case. – Ivaylo Slavov Feb 06 '18 at 19:34
  • 1
    This is the best answer IMO. It allows for the build tooling to work properly. At least I can use an external mechanism to feed the number into the build now. – Michael Yanni Aug 30 '18 at 17:42
  • Also, make sure you don't have 1.0.0 in your .csproj – College Code May 10 '21 at 03:50
68

If you're using Visual Studio Team Services/TFS or some other CI build process to have versioning built-in, you can utilize msbuild's Condition attribute, for example:

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

  <PropertyGroup>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' == '' ">0.0.1-local</Version>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' != '' ">$(BUILD_BUILDNUMBER)</Version>
    <TargetFramework>netcoreapp1.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
    <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />
  </ItemGroup>

</Project>

This will tell the .NET Core compiler to use whatever is in the BUILD_BUILDNUMBER environment variable if it's present, or fallback to 0.0.1-local if you're doing a build on your local machine.

Pang
  • 9,564
  • 146
  • 81
  • 122
joelsand
  • 2,245
  • 2
  • 21
  • 31
29

I have been looking for a version incrementer for a .NET Core app in VS2017 using the csproj configuration format.

I found a project called dotnet bump that worked for the project.json format but struggled to find a solution for the .csproj format. The writer of dotnet bump actually came up with the solution for the .csproj format and it is called MSBump.

There is a project on GitHub for it at:

https://github.com/BalassaMarton/MSBump

where you can see the code and it's available on NuGet too. Just search for MSBump on Nuget.

Pang
  • 9,564
  • 146
  • 81
  • 122
ravetroll
  • 520
  • 5
  • 13
  • 1
    I recommend using the latest 2.1.0 release of MSBump, It supports switching configurations better, and also sets the version for the current build, not the next one (like the previous version). – Márton Balassa Aug 12 '17 at 16:51
  • I see it now also supports MSBuild whereas before it required visual studio. – ravetroll Nov 25 '17 at 18:27
  • 2
    Yes, and it also supports multi-targeting projects. – Márton Balassa Dec 13 '17 at 09:01
  • 4
    Consider using GitVersioning. It may be suitable to run on your CI environment. https://github.com/AArnott/Nerdbank.GitVersioning – Henrique Feb 21 '18 at 20:20
  • 3
    MSBump increments version on every build even if you didn't change anything, this causes a lot of problems in a long term. And sometimes versions go out of sync and one version is behind the other. – Konrad Oct 10 '18 at 11:42
  • Visual Studio 16.5 just broke this for me. :( – Daniel Henry Mar 22 '20 at 05:38
22

You can use a MSBuild property function to set the version suffix based on current date:

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
  <VersionSuffix>pre$([System.DateTime]::UtcNow.ToString(yyyyMMdd-HHmm))</VersionSuffix>
</PropertyGroup>

This will output a package with a name like: PackageName.1.0.0-pre20180807-1711.nupkg.

More details about MSBuild property functions: https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions

The Version is formed from the combination of VersionPrefix and VersionSuffix, or if VersionSuffix is blank, VersionPrefix only.

<PropertyGroup>
  <VersionPrefix>1.0.0</VersionPrefix>
</PropertyGroup>
Pang
  • 9,564
  • 146
  • 81
  • 122
Fabricio Godoy
  • 517
  • 6
  • 8
16

I came up with a solution that worked almost the same as old AssemblyVersion attribute with star (*) - AssemblyVersion("1.0.*")

Values for AssemblyVersion and AssemblyFileVersion is in MSBuild project .csproj file (not in AssemblyInfo.cs) as property FileVersion (generates AssemblyFileVersionAttribute) and AssemblyVersion (generates AssemblyVersionAttribute). In MSBuild process we use our custom MSBuild task to generate version numbers and then we override values of these FileVersion and AssemblyVersion properties with new values from task.

So first, we create our custom MSBuild task GetCurrentBuildVersion:

public class GetCurrentBuildVersion : Task
{
    [Output]
    public string Version { get; set; }
 
    public string BaseVersion { get; set; }
 
    public override bool Execute()
    {
        var originalVersion = System.Version.Parse(this.BaseVersion ?? "1.0.0");
 
        this.Version = GetCurrentBuildVersionString(originalVersion);
 
        return true;
    }
 
    private static string GetCurrentBuildVersionString(Version baseVersion)
    {
        DateTime d = DateTime.Now;
        return new Version(baseVersion.Major, baseVersion.Minor,
            (DateTime.Today - new DateTime(2000, 1, 1)).Days,
            ((int)new TimeSpan(d.Hour, d.Minute, d.Second).TotalSeconds) / 2).ToString();
    }
}

Task class inherit from Microsoft.Build.Utilities.Task class from Microsoft.Build.Utilities.Core NuGet package. It takes BaseVersion property (optional) on input and returns generated version in Version output property. The logic to get version numbers is same as .NET automatic versioning (Build number is days count since 1/1/2000 and Revision is half seconds since midnight).

To build this MSBuild task, we use .NET Standard 1.3 class library project type with this class.

.csproj file can looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.3</TargetFramework>
    <AssemblyName>DC.Build.Tasks</AssemblyName>
    <RootNamespace>DC.Build.Tasks</RootNamespace>
    <PackageId>DC.Build.Tasks</PackageId>
    <AssemblyTitle>DC.Build.Tasks</AssemblyTitle>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="15.1.1012" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.1.1012" />
  </ItemGroup>
</Project>

This task project is also available in my GitHub holajan/DC.Build.Tasks

Now we setup MSBuild to use this task and set FileVersion and AssemblyVersion properties. In .csproj file, it looks like this:

<Project Sdk="Microsoft.NET.Sdk">
  <UsingTask TaskName="GetCurrentBuildVersion" AssemblyFile="$(MSBuildThisFileFullPath)\..\..\DC.Build.Tasks.dll" />
 
  <PropertyGroup>
    ...
    <AssemblyVersion>1.0.0.0</AssemblyVersion>
    <FileVersion>1.0.0.0</FileVersion>
  </PropertyGroup>
 
  ...
 
  <Target Name="BeforeBuildActionsProject1" BeforeTargets="BeforeBuild">
    <GetCurrentBuildVersion BaseVersion="$(FileVersion)">
      <Output TaskParameter="Version" PropertyName="FileVersion" />
    </GetCurrentBuildVersion>
    <PropertyGroup>
      <AssemblyVersion>$(FileVersion)</AssemblyVersion>
    </PropertyGroup>
  </Target>
 
</Project>

Important things here:

  • Mentioned UsingTask imports GetCurrentBuildVersion task from DC.Build.Tasks.dll. It assumes that this dll file is located on parent directory from your .csproj file.
  • Our BeforeBuildActionsProject1 Target that calls task must have unique name per project in case we have more projects in the solution which calls GetCurrentBuildVersion task.

The advantage of this solution is that it works not only from builds on build server, but also in manual builds from dotnet build or Visual Studio.

Pang
  • 9,564
  • 146
  • 81
  • 122
HolaJan
  • 868
  • 9
  • 16
  • 5
    I'd recommend to use `DateTime.UtcNow` instead of `DateTime.Now` in method `GetCurrentBuildVersionString()` in particular if the code is executed on automated build machines. They may run at 2 am or 3 am in the morning when your computer switches to / from daylight savings time. With `DateTime.Now` in that scenario you might be going backwards in terms of version. Admittedly, this is a corner case and I also admit that I'm picky. :-) Also, the problem goes also away if you configure the same timezone on all build machines and not to adjust to daylight savings time. – Manfred Aug 01 '17 at 21:02
  • Is there a NuGet package for this yet? – Jonathan Allen Dec 19 '17 at 22:00
  • @Jonathan Allen No, I have no plan for nuget package, because of different name in each project. You can download compiled build task assemby in https://github.com/holajan/DC.Build.Tasks/tree/master/dist folder – HolaJan Dec 21 '17 at 19:36
15

I accepted the above answer because @Gigi is correct (as of now) but I was annoyed and came up with the following PowerShell Scripts.

First I have the script in my solution folder (UpdateBuildVersion.ps1):

#Get Path to csproj
$path = "$PSScriptRoot\src\ProjectFolder\ProjectName.csproj"

#Read csproj (XML)
$xml = [xml](Get-Content $path)

#Retrieve Version Nodes
$assemblyVersion = $xml.Project.PropertyGroup.AssemblyVersion
$fileVersion = $xml.Project.PropertyGroup.FileVersion

#Split the Version Numbers
$avMajor, $avMinor, $avBuild  = $assemblyVersion.Split(".")
$fvMajor, $fvMinor, $fvBuild = $fileVersion.Split(".")

#Increment Revision
$avBuild = [Convert]::ToInt32($avBuild,10)+1
$fvBuild = [Convert]::ToInt32($fvBuild,10)+1

#Put new version back into csproj (XML)
$xml.Project.PropertyGroup.AssemblyVersion = "$avMajor.$avMinor.$avBuild"
$xml.Project.PropertyGroup.FileVersion = "$fvMajor.$fvMinor.$fvBuild"

#Save csproj (XML)
$xml.Save($path)

I added this to csproj file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <AssemblyVersion>0.0.1</AssemblyVersion>
    <FileVersion>0.0.1</FileVersion>
    <PreBuildEvent>powershell.exe –NonInteractive –ExecutionPolicy Unrestricted -command "& {$(SolutionDir)UpdateBuildVersion.ps1}"</PreBuildEvent>
  </PropertyGroup>
</Project>

Even through its set to be a PreBuildEvent, the fact is the version numbers do not get updated until AFTER the file has been loaded into memory so the version number will not reflect until the next build. In fact, you could change it to a PostBuildEvent and it would have the same effect.

I also created the following two scripts: (UpdateMinorVersion.ps1)

#Get Path to csproj
$path = "$PSScriptRoot\src\ProjectFolder\ProjectName.csproj"

#Read csproj (XML)
$xml = [xml](Get-Content $path)

#Retrieve Version Nodes
$assemblyVersion = $xml.Project.PropertyGroup.AssemblyVersion
$fileVersion = $xml.Project.PropertyGroup.FileVersion

#Split the Version Numbers
$avMajor, $avMinor, $avBuild  = $assemblyVersion.Split(".")
$fvMajor, $fvMinor, $fvBuild = $fileVersion.Split(".")

#Increment Minor Version - Will reset all sub nodes
$avMinor = [Convert]::ToInt32($avMinor,10)+1
$fvMinor = [Convert]::ToInt32($fvMinor,10)+1
$avBuild = 0
$fvBuild = 0

#Put new version back into csproj (XML)
$xml.Project.PropertyGroup.AssemblyVersion = "$avMajor.$avMinor.$avBuild"
$xml.Project.PropertyGroup.FileVersion = "$fvMajor.$fvMinor.$fvBuild"

#Save csproj (XML)
$xml.Save($path)

(UpdateMajorVersion.ps1)

#Get Path to csproj
$path = "$PSScriptRoot\src\ProjectFolder\ProjectName.csproj"

#Read csproj (XML)
$xml = [xml](Get-Content $path)

#Retrieve Version Nodes
$assemblyVersion = $xml.Project.PropertyGroup.AssemblyVersion
$fileVersion = $xml.Project.PropertyGroup.FileVersion

#Split the Version Numbers
$avMajor, $avMinor, $avBuild  = $assemblyVersion.Split(".")
$fvMajor, $fvMinor, $fvBuild = $fileVersion.Split(".")

#Increment Major Version - Will reset all sub nodes
$avMajor = [Convert]::ToInt32($avMajor,10)+1
$fvMajor = [Convert]::ToInt32($fvMajor,10)+1
$avMinor = 0
$fvMinor = 0
$avBuild = 0
$fvBuild = 0

#Put new version back into csproj (XML)
$xml.Project.PropertyGroup.AssemblyVersion = "$avMajor.$avMinor.$avBuild"
$xml.Project.PropertyGroup.FileVersion = "$fvMajor.$fvMinor.$fvBuild"

#Save csproj (XML)
$xml.Save($path)
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jason H
  • 4,996
  • 6
  • 27
  • 49
14

You could do it like below, within the csproj file. I didn't figure out the math. I found that somewhere else on Stack Overflow, but this works and will give you something similiar to 1.0.* for version.

<PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <FileVersion>1.0.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2000-01-01"))).TotalDays).$([System.Math]::Floor($([MSBuild]::Divide($([System.DateTime]::UtcNow.TimeOfDay.TotalSeconds), 1.32))))</FileVersion>
    <Version>1.0.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2000-01-01"))).TotalDays)</Version>
</PropertyGroup>
Pang
  • 9,564
  • 146
  • 81
  • 122
Clarke76
  • 724
  • 1
  • 7
  • 17
  • Doesn't appear to work for `netstandard2.0` in my env. I'm using .NET Core SDK 3.1.403. Is something else need to make this work? When run with `dotnet build` version number doesn't change from default. – Manfred Oct 26 '20 at 04:25
  • 1
    Thank, short and works. – @Manfred: Maybe use AssemblyVersion or something similar instead. That was the problem for me. FileVersion was set. but AssemblyVersion wasn't. Also: should not be used anymore (if I'm nit mistaken). – Martini Bianco Nov 28 '20 at 23:08
  • 1
    Also: Instead of 1.0. … I used $(VersionPrefix). … instead, and then I can set Version-Number correctly in VersionPefix property. – Martini Bianco Nov 28 '20 at 23:10
  • FYI: to match what .NET does with "1.0.*", I changed from using "UtcNow" to "Now" and from "1.32" to "2", although to me UtcNow makes more sense to use. – Mafu Josh Nov 27 '21 at 04:06
  • does this change everyday? or just once on deployment? – Yzak Oct 26 '22 at 07:24
11

These values are now set in the .csproj file:

<PropertyGroup>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <AssemblyVersion>1.0.6.0</AssemblyVersion>
    <FileVersion>1.0.6.0</FileVersion>
    <Version>1.0.1</Version>
</PropertyGroup>

These are the same values you see if you go in the Package tab in the project settings. While I don't think you can use * to autoincrement the version, what you can do is introduce a post-processing step that replaces the versions for you (e.g. as part of your continuous integration).

Pang
  • 9,564
  • 146
  • 81
  • 122
Gigi
  • 28,163
  • 29
  • 106
  • 188
  • 6
    I was afraid this would be the answer. I will see if I can do a pre-build step to increment it. – Jason H Mar 25 '17 at 18:14
  • 3
    As pointed out in another thread, the new csproj format allows you to turn off the auto-generation of the assemblyinfo file and for you to specify your own. I followed the advice of natemcmaster's answer here and used a standard AssemblyInfo.cs file: https://stackoverflow.com/questions/42138418/equivalent-to-assemblyinfo-in-dotnet-core-csproj – James Eby Jun 22 '17 at 19:34
  • 5
    Why did they remove the auto-increment? It worked really well and very simply for me for years. I push master, CI builds and increments, then the version is read directly from the built DLL using some PS script, then use that version as an arg when pushing to NuGet. So simple. Now broken. – Luke Puplett Aug 30 '17 at 11:46
  • @LukePuplett: See [“Confusing error message for wildcard in AssemblyVersion on .Net Core #22660”] (https://github.com/dotnet/roslyn/issues/22660), The reasons why they consider Deterministic Builds beneficial described in http://blog.paranoidcoding.com/2016/04/05/deterministic-builds-in-roslyn.html and Compilers should be deterministic: same inputs generate same outputs #372 – Michael Freidgeim Oct 28 '17 at 01:13
  • 1
    That's pretty awesome and I agree with the idea... but.... why not support a * auto-increment-but-not-unless-the-source-actually-changed feature? #rhetoricalQuestion – Luke Puplett Oct 28 '17 at 20:22
9

has anyone figured out how to control version in .NET Core (or .NETStandard for that matter) projects.

Use:

dotnet build /p:AssemblyVersion=1.2.3.4

I found this question trying to solve this problem in the context of a CI build. I wanted to set the assembly version to the CI build number.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Chris McKenzie
  • 3,681
  • 3
  • 27
  • 36
  • 2
    The title says "Auto Versioning in Visual Studio 2017 (.NET Core)". Where exactly building it manually complies to "Visual Studio 2017"? – JCKödel Aug 02 '17 at 17:31
  • 4
    I was responding to: "has anyone figured out how to control version in .NET Core (or .NETStandard for that matter) projects." I found this question trying to solve this problem in the context of a CI build. I wanted to set the assembly version to the CI build number. I'm sorry if you feel this wasn't relevant to the question at hand. – Chris McKenzie Aug 02 '17 at 19:37
  • It's a helpful component for me thanks. I will use this as part of a CI solution – Mark Adamson Sep 26 '17 at 21:31
  • 1
    @ChrisMcKenzie: your comment should be included in your answer to make your intent clear – Michael Freidgeim Oct 28 '17 at 05:08
  • ** this does not work for me on netstandard projects when assemblyinfo.cs is not specified and the version is in the csproj... – tofutim Jan 02 '18 at 07:04
  • Try removing the version from the csproj? I don't think it's necessary. [See this Example](https://github.com/nbuilder/nbuilder/blob/master/src/FizzWare.NBuilder/Fizzware.NBuilder.csproj) – Chris McKenzie Jan 02 '18 at 22:53
8

To summaries all up above: your can revert to old AssemblyInfo.cs behavior with this:

<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Deterministic>false</Deterministic>

But this approach is not recommended, because turning off GenerateAssemblyInfo can lead to problems with infra, for example. More selective approach:

<Deterministic>false</Deterministic>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<AssemblyVersion>1.2.*</AssemblyVersion>

and you don't need AssemblyInfo.cs any more.

Kirsan
  • 254
  • 3
  • 5
6

I made a simple CLI tool for setting .csproj .NET Core version strings here. You can combine it with tools like GitVersion for automatic version bumping during CI builds, if that's what you're after.

Tagc
  • 8,736
  • 7
  • 61
  • 114
6

Thanks to @joelsand for pointing me in the right direction.

I had to change his answer slightly as when the DevOps Build ran, I got the following exception

The specified version string does not conform to the recommended format - major.minor.build.revision

I had to add the $(BUILD_BUILDNUMBER) at the end of major.minor.build section. To de-duplicate the actual version, I also use a version-prefix:

<PropertyGroup>
    <VersionPrefix>1.0.3</VersionPrefix>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' == '' ">$(VersionPrefix)-local</Version>
    <Version Condition=" '$(BUILD_BUILDNUMBER)' != '' ">$(VersionPrefix)-$(BUILD_BUILDNUMBER)</Version>
</PropertyGroup>
Pang
  • 9,564
  • 146
  • 81
  • 122
Stu Harper
  • 721
  • 2
  • 9
  • 22
6

What worked for me was to define Patch and Revision using a PropertyGroup, then you can just use this variables for version (and prefix if needed). Version numbers must be short numbers so I use YearMonth for Patch, and MinutesOfDay for Revision. Add this lines to your csproj file:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <PropertyGroup>
        <VersionMajor>0</VersionMajor>
        <VersionMinor>9</VersionMinor>
        <VersionPatch Condition="'$(VersionPatch)' == ''">$([System.DateTime]::UtcNow.ToString("yyMM"))</VersionPatch>
        <VersionRevision Condition="'$(VersionRevision)' == ''">$([System.DateTime]::UtcNow.TimeOfDay.TotalMinutes.ToString("0"))</VersionRevision>
    </PropertyGroup>

    <PropertyGroup>
        <OutputType>...</OutputType>
        <TargetFramework>net5.0</TargetFramework>
        <Title>Software Title</Title>
        <Description>...</Description>
        <Authors>...</Authors>
        <Version>$(VersionMajor).$(VersionMinor).$(VersionPatch).$(VersionRevision)</Version>
    </PropertyGroup>

    ....

</Project>

It can be achive in a generic way making use of Directory.build.props file. More info here: https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2019

Just add a file with this name in the project folder and place there these lines.


I came across here searching for a solution for shared projects. In my case, I solved it adding a Version.build.props file in my shared project with the structure shown above, and just one new line at any csproj file for projects using my shared code:

<!-- Shared project import -->
<Import Project="..\Shared\Shared.projitems" Label="Shared" /> 
<!-- Version number generator -->
<Import Project="$([MSBuild]::GetPathOfFileAbove('Version.Build.props', '$(MSBuildThisFileDirectory)../Shared/'))" />

I'll left this code here just in case someone needs it.

*Solution tested for .Net5 but should works for earlier versions.

Antonio Rodríguez
  • 976
  • 2
  • 11
  • 25
5
  <PropertyGroup>
    <SecondsSinceEpoch>$([System.DateTime]::UtcNow.Subtract($([System.DateTime]::MinValue)).TotalSeconds)</SecondsSinceEpoch>
    <Revision>$([System.Math]::Truncate($([System.Decimal]::Remainder($(SecondsSinceEpoch), 100000))))</Revision>
    <Version>1.7.0.$(Revision)</Version>
    <AssemblyVersion>$(Version)</AssemblyVersion>
    <FileVersion>$(Version)</FileVersion>
  </PropertyGroup>

My take on setting a decent value via .csproj. Unfortunately if your next rebuild is an interval of 100000 seconds later it will be the same value. Better than MSBump making every Build a Rebuild though.

Can use TotalMinutes, TotalDays, etc. if slow or automated builds.

Christopher Galpin
  • 1,088
  • 12
  • 27
4

To enable versioning of your .NET Core / .NET Whatever project based on your GIT setup, using the tags/describe functionality of GIT.

I have been using a Prebuild.targets.xml file which is located in the root folder for the project and included in the csproj file like:

<Project Sdk="Microsoft.NET.Sdk">
  <Import Project="PreBuild.targets.xml" />
  ...
  <PropertyGroup>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>

Use the "GenerateAssembyInfo" tag to disable automatic assembly info generation.

Then the Prebuild.targets.xml will generate a CommonAssemblyInfo.cs file where you can include the version tags you want based on your GIT version

NOTE: I have found the Prebuilds.targets.xml somewhere else, so haven't bothered cleaning it up .)

The Prebuild.targets.xml file:

    <?xml version="1.0" encoding="utf-8" ?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
     
      <UsingTask
        TaskName="GetVersion"
        TaskFactory="CodeTaskFactory"
        AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
        <ParameterGroup>
          <VersionString ParameterType="System.String" Required="true" />
          <Version ParameterType="System.String" Output="true" />
          <Commit ParameterType="System.String" Output="true" />
          <VersionSuffix ParameterType="System.String" Output="true" />
        </ParameterGroup>
        <Task>
          <!--<Reference Include="" />-->
          <Using Namespace="System"/>
          <Using Namespace="System.IO"/>
          <Using Namespace="System.Text.RegularExpressions" />
          <Code Type="Fragment" Language="cs">
            <![CDATA[
              var match = Regex.Match(VersionString, @"^(?<major>\d+)\.(?<minor>\d+)(\.?(?<patch>\d+))?-(?<revision>\d+)-(?<commit>[a-z0-9-]+)$");
              int major, minor, patch, revision;
              Int32.TryParse(match.Groups["major"].Value, out major);
              Int32.TryParse(match.Groups["minor"].Value, out minor);
              Int32.TryParse(match.Groups["patch"].Value, out patch);
              Int32.TryParse(match.Groups["revision"].Value, out revision);
              _Version = new Version(major, minor, patch, revision).ToString();
              _Commit = match.Groups["commit"].Value;
            ]]>
          </Code>
        </Task>
      </UsingTask>
     
      <UsingTask
        TaskName="GitExistsInPath"
        TaskFactory="CodeTaskFactory"
        AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
        <ParameterGroup>
          <Exists ParameterType="System.Boolean" Output="true" />
        </ParameterGroup>
        <Task>
          <!--<Reference Include="" />-->
          <Using Namespace="System"/>
          <Using Namespace="System.IO"/>
          <Using Namespace="System.Text.RegularExpressions" />
          <Code Type="Fragment" Language="cs">
            <![CDATA[
            var values = Environment.GetEnvironmentVariable("PATH");
            foreach (var path in values.Split(';')) {
                var exeFullPath = Path.Combine(path, "git.exe");
                if (File.Exists(exeFullPath)) {
                    Exists = true;
                    return true;
                }
                var cmdFullPath = Path.Combine(path, "git.cmd");
                if (File.Exists(cmdFullPath)) {
                    Exists = true;
                    return true;
            }
            }
            Exists = false;
            ]]>
          </Code>
        </Task>
      </UsingTask>
     
      <Target Name="CreateCommonVersionInfo" BeforeTargets="CoreCompile">
        <Message Importance="high" Text="CreateCommonVersionInfo" />
     
        <GitExistsInPath>
          <Output TaskParameter="Exists" PropertyName="GitExists"/>
        </GitExistsInPath>
        <Message Importance="High" Text="git not found!" Condition="!$(GitExists)"/>
          
        <Exec Command="git describe --tags --long --dirty > $(ProjectDir)version.txt" Outputs="$(ProjectDir)version.txt" WorkingDirectory="$(SolutionDir)" IgnoreExitCode="true" Condition="$(GitExists)">
          <Output TaskParameter="ExitCode" PropertyName="ExitCode" />
        </Exec>
        <Message Importance="high" Text="Calling git failed with exit code $(ExitCode)" Condition="$(GitExists) And '$(ExitCode)'!='0'" />
        
        <ReadLinesFromFile File="$(ProjectDir)version.txt" Condition="$(GitExists) And '$(ExitCode)'=='0'">
          <Output TaskParameter="Lines" ItemName="OutputLines"/>
        </ReadLinesFromFile>
        <Message Importance="High" Text="Tags: @(OutputLines)" Condition="$(GitExists) And '$(ExitCode)'=='0'"/>

        <Delete Condition="Exists('$(ProjectDir)version.txt')" Files="$(ProjectDir)version.txt"/>
     
        <GetVersion VersionString="@(OutputLines)" Condition="$(GitExists) And '$(ExitCode)'=='0'">
          <Output TaskParameter="Version" PropertyName="VersionString"/>
          <Output TaskParameter="Commit" PropertyName="Commit"/>
        </GetVersion>
          
        <PropertyGroup>
          <VersionString Condition="'$(VersionString)'==''">0.0.0.0</VersionString>
        </PropertyGroup>
     
        <Message Importance="High" Text="Creating CommonVersionInfo.cs with version $(VersionString) $(Commit)" />
     
        <WriteLinesToFile Overwrite="true" File="$(ProjectDir)CommonAssemblyInfo.cs" Encoding="UTF-8" Lines='using System.Reflection%3B
     
    // full version: $(VersionString)-$(Commit)
     
    [assembly: AssemblyVersion("$(VersionString)")]
    [assembly: AssemblyInformationalVersion("$(VersionString)")] 
    [assembly: AssemblyFileVersion("$(VersionString)")]' />
        
      </Target>
    </Project>

EDIT: If you are building using MSBUILD the

 $(SolutionDir)

Might cause you trouble, use

 $(ProjectDir)

instead

Pang
  • 9,564
  • 146
  • 81
  • 122
Tue Skeltved
  • 196
  • 3
4

As an alternative, you can try fixed major number with a suffix based on current date:

  <PropertyGroup>
    <VersionPrefix>1</VersionPrefix>
    <VersionSuffix>$([System.DateTime]::UtcNow.ToString(yyMM)).$([System.DateTime]::UtcNow.ToString(ddHH)).$([System.DateTime]::UtcNow.ToString(mmss))</VersionSuffix>
    <Version Condition=" '$(VersionSuffix)' == '' ">$(VersionPrefix).0.0.1</Version>
    <Version Condition=" '$(VersionSuffix)' != '' ">$(VersionPrefix).$(VersionSuffix)</Version>
  </PropertyGroup>
Pang
  • 9,564
  • 146
  • 81
  • 122
tsubasaetkisi
  • 301
  • 2
  • 10
  • 1
    You have a nice solution here. Not everyone likes to use 'DateTime' in their versions though. – Jason H Aug 21 '20 at 15:09
2

We can use special parameter for dotnet publish -- version-suffix 1.2.3

For file version:

<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</AssemblyVersion>
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>

For version:

<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1</Version>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>

https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish?tabs=netcore21

--version-suffix <VERSION_SUFFIX>     Defines the value for the $(VersionSuffix) property in the project.
Pang
  • 9,564
  • 146
  • 81
  • 122
Anatoli Klamer
  • 2,279
  • 2
  • 17
  • 22
1

I think this Answer from @joelsand is the correct answer for setting version number for dotnet core running on VSTS

To add more information for this answer,

BUILD_BUILDNUMBER is actually a predefined variable.

It turns out there are 2 versions of predefined variable.

One is build.xxxx, the other is BUILD_XXXX.

You can only use Environment Variable Name in cproj.

maxisam
  • 21,975
  • 9
  • 75
  • 84
  • Isn't `build.xxxx` used on the front end for referencing within a pipeline and `BUILD_XXXX` is the same value but with slightly modified syntax required for referencing the variable in PS? – dst3p Feb 21 '19 at 21:17
0

My OSS project "RelaxVersioner" can full automatic insert with the attributes and constatnt literals on git repository only NuGet package installed without any tool-depended operation.

Example for applied information:

sing System.Reflection;
[assembly: AssemblyVersion("1.0.21")]
[assembly: AssemblyFileVersion("2020.12.20.33529")]
[assembly: AssemblyInformationalVersion("1.0.21-561387e2f6dc90046f56ef4c3ac501aad0d5ec0a")]
[assembly: AssemblyMetadata("Date","Sun, 20 Dec 2020 09:37:39 GMT")]
[assembly: AssemblyMetadata("Branch","master")]
[assembly: AssemblyMetadata("Tags","")]
[assembly: AssemblyMetadata("Author","Kouji Matsui <k@kekyo.net>")]
[assembly: AssemblyMetadata("Committer","Kouji Matsui <k@kekyo.net>")]
[assembly: AssemblyMetadata("Message","Merge branch 'devel'")]
[assembly: AssemblyMetadata("Build","")]
[assembly: AssemblyMetadata("Generated","Sun, 20 Dec 2020 09:37:43 GMT")]
[assembly: AssemblyMetadata("Platform","AnyCPU")]
[assembly: AssemblyMetadata("BuildOn","Unix")]
[assembly: AssemblyMetadata("SdkVersion","5.0.101")]

namespace YourApp
{
  internal static class ThisAssembly
  {
    public const string AssemblyVersion = "1.0.21";
    public const string AssemblyFileVersion = "2020.12.20.33529";
    public const string AssemblyInformationalVersion = "1.0.21-561387e2f6dc90046f56ef4c3ac501aad0d5ec0a";
    public static class AssemblyMetadata
    {
      public const string Date = "Sun, 20 Dec 2020 09:37:39 GMT";
      public const string Branch = "master";
      public const string Tags = "";
      public const string Author = "Kouji Matsui <k@kekyo.net>";
      public const string Committer = "Kouji Matsui <k@kekyo.net>";
      public const string Message = "Merge branch 'devel'";
      public const string Build = "";
      public const string Generated = "Sun, 20 Dec 2020 09:37:43 GMT";
      public const string Platform = "AnyCPU";
      public const string BuildOn = "Unix";
      public const string SdkVersion = "5.0.101";
    }
  }
}
0

Another alternative with dates, based on Antonio Rodríguez's answer, to avoid repetitions in the numbers

  • Version Patch: (Year in 2 digits)+(Day of Year)
  • VersionRevision: Total number of seconds in the Day
  <PropertyGroup>
    <VersionMajor>1</VersionMajor>
      <VersionMinor>0</VersionMinor>
      <VersionPatch Condition="'$(VersionPatch)' == ''">$([System.DateTime]::UtcNow.ToString("yy"))$([System.DateTime]::UtcNow.DayOfYear.ToString("0"))</VersionPatch>
      <VersionRevision Condition="'$(VersionRevision)' == ''">$([System.DateTime]::UtcNow.TimeOfDay.TotalSeconds.ToString("0"))</VersionRevision>
  </PropertyGroup>

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <Version>$(VersionMajor).$(VersionMinor).$(VersionPatch).$(VersionRevision)</Version>
  </PropertyGroup>  

  • This was great and I loved it! I even played with it a bit for my liking and learned how to use `MSBuild` functions and others. It ran well, until I ran into another issue: Visual Studio 2022 constantly kept restoring my projects and their Nuget packages. I thought it was a Nuget issue until I found this: https://developercommunity.visualstudio.com/t/visual-studio-2022-nuget-solutions-tab-keeps-reloa/1574510. Now I'm looking for a different solution. I understand MS's stance on this and there is a work-around, but that requires manually changing of the versions once again. Still searching... – RoLYroLLs Feb 03 '23 at 20:15
  • For completeness, here's what the link above says: `...the use of System.DateTime.UtcNow in MSBuild properties is considered a poor practice as it prevents builds from being repeatable and will tend to prevent MSBuild and VS from successfully caching data. In this particular case, the project is different each time VS checks it (because the VersionPrefix/VersionSuffix property is different every time) and VS thinks it need to attempt another restore to bring everything back up to date.` – RoLYroLLs Feb 03 '23 at 20:21
0

I am working on a .Net Core project (ver .NET7.0), this is what worked for me, other answers returns 0.0.0.0 Apparently there is no way to have an incremental version number in Assembly unless you get the Assembly file version (which somehow can be considered as application version number) In my project .csproj file, I have added the following entries:

<Deterministic>false</Deterministic>
<VersionSuffix>1.0.0.$([System.DateTime]::UtcNow.ToString(mmff))</VersionSuffix>
<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.0.1</AssemblyVersion>
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</Version>
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>

then in my cshtml file I read the version number of the assembly file:

@using System.Reflection;
@{
    var assembly = Assembly.GetEntryAssembly();
    var fileVersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location);
    var fileVersion = fileVersionInfo.FileVersion;
}
    <p>Version: @fileVersion</p>
Amir978
  • 857
  • 3
  • 15
  • 38