ChatGPT解决这个技术问题 Extra ChatGPT

Auto Versioning in Visual Studio 2017 (.NET Core)

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.

I don't know how far you got with this, but looks like I asked almost the same question a different way ( 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.
Thanks for the info. That just open up additional options.
Previously * was supported for AssemblyVersion, not for AssemblyFileVersion- see Can I automatically increment the file build version when using Visual Studio?
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 <Deterministic>False</Deterministic> in the csproj to use it. (or use any other MSbuild logic to calculate <VersionPrefix>/<Version>)

M
Michael Freidgeim

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 False 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   

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.
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.
Also, make sure you don't have 1.0.0 in your .csproj
P
Pang

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.


Nice, I like this approach because the enviornment variables can just be set at build server while these conditionals deteremine the assembly set in the binaries.
Doesn't seem to work on TFS 2010, but hopefully we are moving off that soon!
Not a bad solution, though could be a bit of work if the solution has a lot of projects.
Good solution. I did get a Build exception though. I had to change the config slightly to fix it. stackoverflow.com/a/59858009/106227
This works great with .NET Core 2.1.2 and TFS2017U3
P
Pang

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.


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).
I see it now also supports MSBuild whereas before it required visual studio.
Yes, and it also supports multi-targeting projects.
Consider using GitVersioning. It may be suitable to run on your CI environment. github.com/AArnott/Nerdbank.GitVersioning
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.
P
Pang

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://docs.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>

P
Pang

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.


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.
Is there a NuGet package for this yet?
@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 github.com/holajan/DC.Build.Tasks/tree/master/dist folder
m
marc_s

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)

P
Pang

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>

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.
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).
Also: Instead of 1.0. … I used $(VersionPrefix). … instead, and then I can set Version-Number correctly in VersionPefix property.
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.
P
Pang

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).


I was afraid this would be the answer. I will see if I can do a pre-build step to increment it.
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: stackoverflow.com/questions/42138418/…
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.
@LukePuplett: See [“Confusing error message for wildcard in AssemblyVersion on .Net Core #22660”] (github.com/dotnet/roslyn/issues/22660), The reasons why they consider Deterministic Builds beneficial described in blog.paranoidcoding.com/2016/04/05/… and Compilers should be deterministic: same inputs generate same outputs #372<github.com/dotnet/roslyn/issues/372>
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
W
Wai Ha Lee

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.


The title says "Auto Versioning in Visual Studio 2017 (.NET Core)". Where exactly building it manually complies to "Visual Studio 2017"?
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.
It's a helpful component for me thanks. I will use this as part of a CI solution
@ChrisMcKenzie: your comment should be included in your answer to make your intent clear
** this does not work for me on netstandard projects when assemblyinfo.cs is not specified and the version is in the csproj...
T
Tagc

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.


goddamn genius. love it!
P
Pang

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>

I had the same exact problem and your answer fixed it. Thank you.
K
Kirsan

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.


P
Pang

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


Nice! Does VersionSuffix end up getting set or used? It doesn't seem to be
C
Christopher Galpin
  <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.


P
Pang

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://docs.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.

P
Pang

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>

You have a nice solution here. Not everyone likes to use 'DateTime' in their versions though.
A
Antonio Rodríguez

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://docs.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.


m
maxisam

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.


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?
K
Kouji Matsui

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";
    }
  }
}