Adding a Postbuild event to a project - c#

When I generate a C# project (csproj file) and then compile it, msbuild somehow doesn’t recognize the variables $(ConfigurationName) and $(ProjectDir) (and others) in the pre- and postbuild event.
When I Manually move the pre- and postbuild event configuration in the generated .csproj file further downwards, then msbuild recognizes these variables correctly.
Adding the buildevent to the project is the last thing I do before saving the project.
This is how I add it:
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
private const string PreBuildEventFixture = "PreBuildEvent";
private const string PostBuildEventFixture = "PostBuildEvent";
private const string PreBuildEvent = "attrib -R \"$(ProjectDir)app.config\"";
private const string PostBuildEvent = "copy \"$(ProjectDir)app.config.$(ConfigurationName)\" \"$(TargetDir)\\$(ProjectName).dll.config\" \r\n attrib -R \"$(ProjectPath)\"";
public void AddBuildEvents(Project project)
{
ProjectPropertyGroupElement propertyGroupElement = project.Xml.AddPropertyGroup();
propertyGroupElement.AddProperty(PreBuildEventFixture, PreBuildEvent);
propertyGroupElement.AddProperty(PostBuildEventFixture, PostBuildEvent);
}
The error I get when running the generated project through msbuild is this:
The command "copy "app.config." "\.dll.config"" exited with code 1
When I then manually edit the .csproj file (with notepad or another text editor), cut the pre-and postbuild event, and paste it below the <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> element, then msbuild builds the generated .csproj file fine.
What is the best way to add the build events to the .csproj file so it ends up after the Import element in the resulting XML?
Apparently, my current way of using [ProjectPropertyGroupElement][1] by requesting it from AddPropertyGroup of the the Xml property of the Microsoft.Build.Evaluation.Project is not.
Example Project:
using System.IO;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
class Program
{
private const string PreBuildEventFixture = "PreBuildEvent";
private const string PostBuildEventFixture = "PostBuildEvent";
private const string PreBuildEvent = "attrib -R \"$(ProjectDir)app.config\"";
private const string PostBuildEvent = "copy \"$(ProjectDir)app.config.$(ConfigurationName)\" \"$(TargetDir)\\$(ProjectName).exe.config\" \r\n attrib -R \"$(ProjectPath)\"";
private const string ProjectFile = #"C:\test\TestProject\TestProject.csproj";
static void Main(string[] args)
{
if (!File.Exists(ProjectFile))
throw new FileNotFoundException("ProjectFile not found");
ProjectCollection collection = new ProjectCollection();
Project project = collection.LoadProject(ProjectFile);
ProjectPropertyGroupElement propertyGroupElement = project.Xml.AddPropertyGroup();
propertyGroupElement.AddProperty(PreBuildEventFixture, PreBuildEvent);
propertyGroupElement.AddProperty(PostBuildEventFixture, PostBuildEvent);
project.Save();
collection.UnloadAllProjects();
}
}
Steps to reproduce
Create a new project
Manually add app.config.debug file which should be different to the app.debug file
Add the postbuildevent: copy "$(ProjectDir)app.config.$(ConfigurationName)" "$(TargetDir)\$(ProjectName).exe.config
See that the project build and the correct config file is applied
Remove the pre- and postbuild events using notepad (so not to leave any traces)
Run the example project
Reload and build the project you created.
Output window will now say The system cannot find the file specified.

var propertyGroupElement = project.Xml.CreatePropertyGroupElement();
project.Xml.AppendChild(propertyGroupElement);
propertyGroupElement.AddProperty(PreBuildEventFixture, PreBuildEvent);
propertyGroupElement.AddProperty(PostBuildEventFixture, PostBuildEvent);

Project related macros are not parsed if they are added before the project is actually constructed (constructing a project includes adding references). Instead of using $(ProjectName), the path can be constructed using solution variables (that already exist) like this :
copy "$(SolutionDir)ProjectName\app.config.$(Configuration)" "$(SolutionDir)ProjectName\bin\$(Configuration)\ProjectName.dll.config"
Note that ProjectName is the actual name of the project hardcoded, but since you are generating a project this should be easy to add.

Related

What would cause Build Configuration not to recognize new Build Config setting?

In VS 2017 ,I have defined a new Build Config using Configuration Manager called LiveSystemBuild
I have set all projects as AnyCPU . I have some code as follows:
#if LiveSystemBuild
private const string custID = "1234;
#else
private const string custID = "9876";
#endif
The problem is that I have set the build as LiveSystemBuild, i would have expected the line
private const string custID = "1234;
to be enabled , but its not. It doesnt seem to recognize the new build i have defined.
Do I need to define it anywhere else?
The issue is that the new created configuration LiveSystemBuild did not add itself into DefineConstants property. So you should add it manually into csproj file.
First, you should create the new configuration LiveSystemBuild under active solution configuration. If not , you should remove them and add it under active solution configuration.
Second, add this at the bottom of every project's csproj file.
<PropertyGroup Condition="'$(Configuration)'=='LiveSystemBuild'">
<DefineConstants>TRACE;LiveSystemBuild</DefineConstants>
</PropertyGroup>

Proper way to hide API keys in Git

In my C# project have APIKeys.cs file which have const strings with API keys.
I want those strings to be empty in Git server but have actual API keys in my local computer.
So peoples who pull project can compile it without problem and still my local computer gonna have API keys in same file.
If I try to upload APIKeys.cs file with empty strings then I can't have local file with API keys because when I try to push it, it will overwrite empty APIKeys.cs file. Also I can't ignore this file too because it will remove empty APIKeys.cs file from Git server.
So what is best automated approach for this problem which will allow class file with empty strings in server, so project will be compileable when people pull it and have real class file in local computer?
I figured another solution now which is not perfect but still good enough for me, example:
APIKeys.cs file:
public static partial class APIKeys
{
public static readonly string ImgurClientID = "";
public static readonly string ImgurClientSecret = "";
public static readonly string GoogleClientID = "";
public static readonly string GoogleClientSecret = "";
public static readonly string PastebinKey = "";
...
}
APIKeysLocal.cs file:
public static partial class APIKeys
{
static APIKeys()
{
ImgurClientID = "1234567890";
ImgurClientSecret = "1234567890";
GoogleClientID = "1234567890";
GoogleClientSecret = "1234567890";
PastebinKey = "1234567890";
...
}
}
Ignore APIKeysLocal.cs file in Git and people who don't have this file can still be able to compile project if they remove this file from solution explorer.
I also automatically create empty APIKeysLocal.cs file if it is not already exists using project pre build event:
cd $(ProjectDir)APIKeys\
if not exist APIKeysLocal.cs (
type nul > APIKeysLocal.cs
)
That way user don't need to do anything to be able to compile project.
Accept that you cannot hide unencrypted private keys in a public space.
What you can do is move the keys to a private space, and then reference that private space from code.
Your private space might be environment variables or the Windows registry, it should be something outside the source code of your app.
Another approach is to create a new config file (e.g. keys.config) specifically for storing private keys, and then exclude this file from source control.
This means you don't share your private keys, but it also means that you need to document (perhaps in readme.md) that users will need to recreate their own keys.config. Even better (thanks #Joey) is to include a sample config file (keys.sample.config) in the solution, illustrating what's needed.
Here is an example
You've got two options:
Tell Git to ignore changes to APIKeys.cs on your local machine:
git update-index --skip-worktree APIKeys.cs
This will cause local changes not to get committed. If you ever do want to commit changes to the file, you'll have to undo this with the --no-skip-worktree flag.
Rename the APIKeys.cs file to something like APIKeys.template.cs, containing the blank strings that you want to share. Keep this file in your repository. Copy that file to APIKeys.cs. Add APIKeys.cs to your .gitignore. Add instructions to copy the template file and modify with local settings.
git mv APIKeys.cs APIKeys.template.cs
$EDITOR APIKeys.template.cs
git commit
cat APIKeys.cs >> .gitignore
cp APIKeys.template.cs APIKeys.cs
Isn't this very similar to the problem of injecting a build number into your component? The way I do that is to have a pre-build step that generates a file called AssemblyVersionInfo.cs based on some environment variable. You could do the same thing with your API Keys.
In the pre-build step for the component that compiles in the API keys put something like this:-
if not defined API_KEY set API_KEY=DEFAULT_KEY
echo public class ApiKeys>"$(SolutionDir)src\ApiKeys.cs"
echo {>>"$(SolutionDir)src\ApiKeys.cs"
echo public const string Key="%API_KEY%";>>"$(SolutionDir)src\ApiKeys.cs"
echo }>>"$(SolutionDir)src\ApiKeys.cs"
Then you set either a user or system environment variable on your local machine with the real key in it.
setx API_KEY THE_REAL_KEY
To avoid Git wanting to commit the file just add it to the .gitignore.

c# Class Library Project - Load DLL from same folder?

I'm working on a plugin for a existing C# .NET Program. It's structured in a manner where you put your custom .dll file in Program Root/Plugins/your plugin name/your plugin name.dll
This is all working well, but now I'm trying to use NAudio in my project.
I've downloaded NAudio via Nuget, and that part works fine, but the problem is that it looks for the NAudio.dll in Program Root, and not in the folder of my plugin.
This makes it hard to distribute my plugin, because it would rely on users dropping the NAudio.dll in their Program Root in addition to putting the plugin into the "Plugins" folder.
Source:
SettingsView.xaml:
<Button HorizontalAlignment="Center"
Margin="0 5"
Width="120"
Command="{Binding SoundTestCommand,
Source={StaticResource SettingsViewModel}}"
Content="Sound Test" />
SettingsViewModel.cs:
using NAudio.Wave;
.
.
.
public void SoundTest()
{
IWavePlayer waveOutDevice;
WaveStream mainOutputStream;
WaveChannel32 inputStream;
waveOutDevice = new WaveOut();
mainOutputStream = new Mp3FileReader(#"E:\1.mp3");
inputStream = new WaveChannel32(mainOutputStream);
inputStream.Volume = 0.2F;
waveOutDevice.Init(mainOutputStream);
waveOutDevice.Play();
}
How can I get C# to look for NAudio in Program Root/Plugins/my plugin name/NAudio.dll instead of looking for it in Program Root/NAudio.dll ?
I'm using VS Express 2013, Target Framework is 4.5 and Output type is Class Library.
Edit:
I found 2 ways to make this work ( I'm not sure what the pros and cons of both methods are - if anyone knows I would appreciate additional information ).
Using the NuGet Package Costura.Fody.
After installing the NuGet package, I simply had to set all other References "Copy Local" to "False" and then set "Copy Local" for NAudio to "True".
Now when I build, the NAudio.dll is compressed and added to my own DLL.
Using the AssemblyResolver outlined below.
It didn't work right away though, so here is some additional information that may help anyone facing the same issue:
I put Corey's code as he posted it into the Helpers folder.
My entry point is Plugin.cs, the class is public class Plugin : IPlugin, INotifyPropertyChanged
In there, the entry method is public void Initialize(IPluginHost pluginHost), but simply putting PluginResolver.Init(path) did not work.
The host program uses WPF and is threaded and I had to use a dispatcher helper function of the host program to get it to work: DispatcherHelper.Invoke(() => Resolver.Init(path));
As mentioned, I'm currently unsure which method to use, but I'm glad I got it to work. Thanks Corey!
You can use the PATH environment variable to add additional folders to the search path. This works for native DLLs, but I haven't tried to use it for .NET assemblies.
Another option is to add a hook to the AssemblyResolve event on the current application domain and use a custom resolver to load the appropriate assembly from wherever you find it. This can be done at the assembly level - I use it in NAudio.Lame to load an assembly from a resource.
Something along these lines:
public static class PluginResolver
{
private static bool hooked = false;
public static string PluginBasePath { get; private set; }
public static void Init(string BasePath)
{
PluginBasePath = BasePath;
if (!hooked)
{
AppDomain.CurrentDomain.AssemblyResolve += ResolvePluginAssembly;
hooked = true;
}
}
static Assembly ResolvePluginAssembly(object sender, ResolveEventArgs args)
{
var asmName = new AssemblyName(args.Name).Name + ".dll";
var assemblyFiles = Directory.EnumerateFiles(PluginBasePath, "*.dll", SearchOption.AllDirectories);
var asmFile = assemblyFiles.FirstOrDefault(fn => string.Compare(Path.GetFileName(fn), asmName, true) == 0);
if (string.IsNullOrEmpty(asmFile))
return null;
return Assembly.LoadFile(asmFile);
}
}
(Usings for the above: System.IO, System.Reflection, System.Linq)
Call Init with the base path to your plugins folder. When you try to reference an assembly that isn't loaded yet it will search for the first file that matches the base name of the assembly with dll appended. For instance, the NAudio assembly will match the first file named NAudio.dll. It will then load and return the assembly.
No checking is done in the above code on the version, etc. and no preference is given to the current plugin's folder.

VSIX extension for VS2012 not running when debugging

I created a new VSIX extension project in Visual Studio 2012, and wrote a MEF classifier (as a test) that should simply highlight all text in a .mylang file. Here are the relevant parts of my .NET 4.5 code:
internal static class MyLangLanguage
{
public const string ContentType = "mylang";
public const string FileExtension = ".mylang";
[Export(typeof(ClassificationTypeDefinition))]
[Name(ContentType)]
[BaseDefinition("code")]
internal static ContentTypeDefinition MyLangSyntaxContentTypeDefinition = null;
[Export]
[FileExtension(FileExtension)]
[ContentType(ContentType)]
internal static FileExtensionToContentTypeDefinition MyLangSyntaxFileExtensionDefinition = null;
}
[Export(typeof(IClassifierProvider))]
[ContentType(MyLangLanguage.ContentType)]
[Name("MyLangSyntaxProvider")]
internal sealed class MyLangSyntaxProvider : IClassifierProvider
{
[Import]
internal IClassificationTypeRegistryService ClassificationRegistry = null;
public IClassifier GetClassifier(ITextBuffer buffer)
{
return buffer.Properties.GetOrCreateSingletonProperty(() => new MyLangSyntax(ClassificationRegistry, buffer));
}
}
internal sealed class MyLangSyntax : IClassifier { }
Here is the full code.
These are the relevant parts from my source.extension.vsixmanifest file. Based on suggestions and similar files I found across the web, I added the dependency on MPF and the two assets.
<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<!-- ... -->
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="4.5" />
<Dependency d:Source="Installed" Id="Microsoft.VisualStudio.MPF.11.0" DisplayName="Visual Studio MPF 11.0" Version="[11.0,12.0)" />
</Dependencies>
<Assets>
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
</Assets>
</PackageManifest>
I also tried a version 1.0 manifest:
<?xml version="1.0" encoding="utf-8"?>
<Vsix Version="1.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2010">
<!-- ... -->
<References />
<Content>
<MefComponent>|%CurrentProject%|</MefComponent>
</Content>
</Vsix>
When I run it, it starts an experimental instance of Visual Studio 2012, and the Extensions and Updates window shows that my extension is active. However, it does not do anything when I load or create a .mylang file. Any exceptions I throw (as a test) from my extension are never thrown. Breakpoints are never hit, and get an exclamation mark with the following warning:
The breakpoint will not currently be hit. No symbols have been loaded for this document.
It feels as if my extension is never really loaded at all. My problem is similar to this problem and this problem, but I'm using Visual Studio 2012 which uses a new VSIX manifest format.
What I know:
I can find my DLL and VSIX file in the %localappdata%\Microsoft\VisualStudio\11.0Exp\Extensions\MyLang\VSIXProject1\1.0 folder, so I know they are copied.
Their timestamp corresponds to when I last built the project, so I know they are up-to-date.
Project Properties > Debug > Start external program: is already automatically set to C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe, and the Command line arguments were automatically set to /rootsuffix Exp.
The Visual Studio log (created with the /log option) has two entries related to my extension: Successfully loaded extension... and Extension is enabled....
My DLL does not appear on the Modules tab (list of all loaded DLLs) of the debugging Visual Studio, while some (not all) other extensions do appear.
It doesn't get loaded in Visual Studio 2012 or 2010 both on my laptop and my desktop PC.
What I've tried:
Set <IncludeAssemblyInVSIXContainer> to true in the .csproj file, per this suggestion, but it did not make any difference.
I can't add the line <MefComponent>|%CurrentProject%|</MefComponent> to the source.extension.vsixmanifest file as it uses a different format (2.0) than VSIX projects for previous versions of Visual Studio (1.0).
This suggestion (setting IncludeAssemblyInVSIXContainer and friends in my .csproj to true) but it does not make a difference. And my breakpoints are still showing the warning and not being hit.
Reset the VS Experimental instance using the Reset the Visual Studio 2012 Experimental Instance shortcut in the Start Menu, as per this suggestion. It didn't make a difference.
How can I at the very least be sure my VSIX MEF extension is loaded and works? And if possible, how can I make by breakpoint work and debug it?
Edit: The problem is you've improperly exported your ContentTypeDefinition as a ClassificationTypeDefinition. You should use the following instead:
[Export] // <-- don't specify the type here
[Name(ContentType)]
[BaseDefinition("code")]
internal static ContentTypeDefinition MyLangSyntaxContentTypeDefinition = null;
Here's my two guesses right now:
Try removing the following line from your vsixmanifest. I assume you do not have a class in your project that extends Package, in which case Visual Studio might be refusing to load your package due to the following Asset line (your extension does not actually provide this asset).
<Asset Type="Microsoft.VisualStudio.VsPackage" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" />
If that fails, try replacing your current source.extension.vsixmanifest with one written to the old schema (version 1.0). I know this form still works in Visual Studio 2012 because all ~20 extensions I work on (with >10 public releases) use the old schema.
280Z28 solved the problem! For completeness, this is the full tried and tested code that will create a super simple VSIX Visual Studio MEF extension that colors all text in a .mylang file blue (or whatever the current keyword color is).
How to create a simple coloring MEF VSIX extension
Make sure you have the Visual Studio SDK installed. (VS2010 SP1 SDK, VS2012 SDK)
Create a new VSIX Project(From the template under Installed → Templates → Visual C# → Extensibility.)
Enter something in the Author field of the VSIX manifest editor, then save and close it.
Add references to the following libraries,version 10.0.0.0 for VS2010, or 11.0.0.0 for VS2012:
Microsoft.VisualStudio.CoreUtility.dll
Microsoft.VisualStudio.Language.StandardClassification.dll
Microsoft.VisualStudio.Text.Data.dll
Microsoft.VisualStudio.Text.Logic.dll
Microsoft.VisualStudio.Text.UI.dll
Microsoft.VisualStudio.Text.UI.Wpf.dll
Add a reference to the following library:
System.ComponentModel.Composition.dll version 4.0.0.0
Create and add a new code file MyLang.cs, and copy-and-paste the code below in it.
Edit source.extension.vsixmanifest as XML.
For Visual Studio 2010, add the following XML just before the closing tag </Vsix>, and save:
<Content>
<MefComponent>|%CurrentProject%|</MefComponent>
</Content>
(If there is already an empty <Content/>, remove it.)
For Visual Stuio 2012, add the following XML just before the closing tag </PackageManifest>, and save:
<Assets>
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
</Assets>
(If there is already an empty <Assets/>, remove it.)
Only for Visual Studio 2010:
Unload the VSIX project (right-click the project → Unload project).
Edit the .csproj project file (right-click the project → Edit MyProject.csproj).
Change the value at <IncludeAssemblyInVSIXContainer> to true.
Save and close the file.
Reload the VSIX project (right-click the project → Reload project).
Now build and run it. When you load a .mylang file, all text should be colored blue (or whatever the default keyword color is).
MyLang.cs
using Microsoft.VisualStudio.Language.StandardClassification;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
namespace VSIXProject1
{
internal static class MyLangLanguage
{
public const string ContentType = "mylang";
public const string FileExtension = ".mylang";
[Export]
[Name(ContentType)]
[BaseDefinition("code")]
internal static ContentTypeDefinition MyLangSyntaxContentTypeDefinition = null;
[Export]
[FileExtension(FileExtension)]
[ContentType(ContentType)]
internal static FileExtensionToContentTypeDefinition MyLangSyntaxFileExtensionDefinition = null;
}
[Export(typeof(IClassifierProvider))]
[ContentType(MyLangLanguage.ContentType)]
[Name("MyLangSyntaxProvider")]
internal sealed class MyLangSyntaxProvider : IClassifierProvider
{
[Import]
internal IClassificationTypeRegistryService ClassificationRegistry = null;
public IClassifier GetClassifier(ITextBuffer buffer)
{
return buffer.Properties.GetOrCreateSingletonProperty(() => new MyLangSyntax(ClassificationRegistry, buffer));
}
}
internal sealed class MyLangSyntax : IClassifier
{
private ITextBuffer buffer;
private IClassificationType identifierType;
private IClassificationType keywordType;
public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;
internal MyLangSyntax(IClassificationTypeRegistryService registry, ITextBuffer buffer)
{
this.identifierType = registry.GetClassificationType(PredefinedClassificationTypeNames.Identifier);
this.keywordType = registry.GetClassificationType(PredefinedClassificationTypeNames.Keyword);
this.buffer = buffer;
this.buffer.Changed += OnBufferChanged;
}
public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan snapshotSpan)
{
var classifications = new List<ClassificationSpan>();
string text = snapshotSpan.GetText();
var span = new SnapshotSpan(snapshotSpan.Snapshot, snapshotSpan.Start.Position, text.Length);
classifications.Add(new ClassificationSpan(span, keywordType));
return classifications;
}
private void OnBufferChanged(object sender, TextContentChangedEventArgs e)
{
foreach (var change in e.Changes)
ClassificationChanged(this, new ClassificationChangedEventArgs(new SnapshotSpan(e.After, change.NewSpan)));
}
}
}
Set <IncludeAssemblyInVSIXContainer> to true in the .csproj file, per
this suggestion.
I had exactly the same problem and this solved it. Do a full rebuild.

Determine assembly version during a post-build event

Let's say I wanted to create a static text file which ships with each release. I want the file to be updated with the version number of the release (as specified in AssemblyInfo.cs), but I don't want to have to do this manually.
I was hoping I could use a post-build event and feed the version number to a batch file like this:
call foo.bat $(AssemblyVersion)
However I can't find any suitable variable or macro to use.
Is there a way to achieve this that I've missed?
If (1) you don't want to download or create a custom executable that retrieves the assembly version and (2) you don't mind editing the Visual Studio project file, then there is a simple solution that allows you to use a macro which looks like this:
#(Targets->'%(Version)')
#(VersionNumber)
To accomplish this, unload your project. If the project somewhere defines a <PostBuildEvent> property, cut it from the project and save it elsewhere temporarily (notepad?). Then at the very end of the project, just before the end-tag, place this:
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<ItemGroup>
<VersionNumber Include="#(Targets->'%(Version)')"/>
</ItemGroup>
</Target>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
<PostBuildEvent>echo HELLO, THE ASSEMBLY VERSION IS: #(VersionNumber)</PostBuildEvent>
</PropertyGroup>
This snippet has an example <PostBuildEvent> already in it. No worries, you can reset it to your real post-build event after you have re-loaded the project.
Now as promised, the assembly version is available to your post build event with this macro:
#(VersionNumber)
Done!
If you prefer scripting these methods might also work for you:
If you are using the post-build event, you can use the filever.exe tool to grab it out of the already built assembly:
for /F "tokens=4" %%F in ('filever.exe /B /A /D bin\debug\myapp.exe') do (
set VERSION=%%F
)
echo The version is %VERSION%
Get filever.exe from here: http://support.microsoft.com/kb/913111
If you are using the pre-build event, you can take it out of the AssemblyInfo.cs file as follows:
set ASMINFO=Properties\AssemblyInfo.cs
FINDSTR /C:"[assembly: AssemblyVersion(" %ASMINFO% | sed.exe "s/\[assembly: AssemblyVersion(\"/SET CURRENT_VERSION=/g;s/\")\]//g;s/\.\*//g" >SetCurrVer.cmd
CALL SetCurrVer.cmd
DEL SetCurrVer.cmd
echo Current version is %CURRENT_VERSION%
This uses the unix command line tool sed, which you can download from many places, such as here: http://unxutils.sourceforge.net/ - iirc that one works ok.
This answer is a minor modification of the answer of Brent Arias. His PostBuildMacro worked quite well for me until a version update of Nuget.exe.
In the recent releases, Nuget trims non significant parts of the package version number in order to obtain a semantic version like "1.2.3". For example, the assembly version "1.2.3.0" is formatted by Nuget.exe "1.2.3". And "1.2.3.1" is formatted "1.2.3.1" as expected.
As I need to infer the exact package filename generated by Nuget.exe, I use now this adaptated macro (tested in VS2015):
<Target Name="PostBuildMacros">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="Targets" />
</GetAssemblyIdentity>
<ItemGroup>
<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace("%(Targets.Version)", "^(.+?)(\.0+)$", "$1"))" />
</ItemGroup>
</Target>
<PropertyGroup>
<PostBuildEventDependsOn>
$(PostBuildEventDependsOn);
PostBuildMacros;
</PostBuildEventDependsOn>
<PostBuildEvent>echo HELLO, THE ASSEMBLY VERSION IS: #(VersionNumber)</PostBuildEvent>
</PropertyGroup>
UPDATE 2017-05-24: I corrected the regex in this way: "1.2.0.0" will be translated to "1.2.0" and not "1.2" as previously coded.
And to answer to a comment of Ehryk Apr, you can adapt the regex to keep only some part of the version number. As an example to keep "Major.Minor", replace:
<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace("%(Targets.Version)", "^(.+?)(\.0+)$", "$1"))" />
By
<VersionNumber Include="$([System.Text.RegularExpressions.Regex]::Replace("%(Targets.Version)", "^([^\.]+)\.([^\.]+)(.*)$", "$1.$2"))" />
As a workaround I've written a managed console application which takes the target as a parameter, and returns the version number.
I'm still interested to hear a simpler solution - but I'm posting this in case anyone else finds it useful.
using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;
namespace Version
{
class GetVersion
{
static void Main(string[] args)
{
if (args.Length == 0 || args.Length > 1) { ShowUsage(); return; }
string target = args[0];
string path = Path.IsPathRooted(target)
? target
: Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + Path.DirectorySeparatorChar + target;
Console.Write( Assembly.LoadFile(path).GetName().Version.ToString(2) );
}
static void ShowUsage()
{
Console.WriteLine("Usage: version.exe <target>");
}
}
}
I think the best thing you can do is look at MSBuild and MsBuild Extension Pack you should be able to edit you solution file so that a post build event occurs and writes to your test file.
If this is too complicated then you could simply create a small program that inspects all assemblies in you output directory and execute it on post build, you could pass in the output directory using the variable name... for example in the post build event...
AssemblyInspector.exe "$(TargetPath)"
class Program
{
static void Main(string[] args)
{
var assemblyFilename = args.FirstOrDefault();
if(assemblyFilename != null && File.Exists(assemblyFilename))
{
try
{
var assembly = Assembly.ReflectionOnlyLoadFrom(assemblyFilename);
var name = assembly.GetName();
using(var file = File.AppendText("C:\\AssemblyInfo.txt"))
{
file.WriteLine("{0} - {1}", name.FullName, name.Version);
}
}
catch (Exception ex)
{
throw;
}
}
}
}
You could also pass in the text file location...
I've started adding a separate project that builds last and adding a post build event to that project that runs itself. Then I just perform my post build steps programmatically in there.
It makes it a lot easier to do stuff like this. Then you can just inspect the assembly attributes of whatever assembly you want. So far it's working pretty awesome.
From that what I understand...
You need a generator for post build events.
1. Step: Writing a Generator
/*
* Author: Amen RA
* # Timestamp: 2013.01.24_02:08:03-UTC-ANKH
* Licence: General Public License
*/
using System;
using System.IO;
namespace AppCast
{
class Program
{
public static void Main(string[] args)
{
// We are using two parameters.
// The first one is the path of a build exe, i.e.: C:\pathto\nin\release\myapp.exe
string exePath = args[0];
// The second one is for a file we are going to generate with that information
string castPath = args[1];
// Now we use the methods below
WriteAppCastFile(castPath, VersionInfo(exePath));
}
public static string VersionInfo(string filePath)
{
System.Diagnostics.FileVersionInfo myFileVersionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath);
return myFileVersionInfo.FileVersion;
}
public static void WriteAppCastFile(string castPath, string exeVersion)
{
TextWriter tw = new StreamWriter(castPath);
tw.WriteLine(#"<?xml version=""1.0"" encoding=""utf-8""?>");
tw.WriteLine(#"<item>");
tw.WriteLine(#"<title>MyApp - New version! Release " + exeVersion + " is available.</title>");
tw.WriteLine(#"<version>" + exeVersion + "</version>");
tw.WriteLine(#"<url>http://www.example.com/pathto/updates/MyApp.exe</url>");
tw.WriteLine(#"<changelog>http://www.example.com/pathto/updates/MyApp_release_notes.html</changelog>");
tw.WriteLine(#"</item>");
tw.Close();
}
}
}
2. Step: Using it as a post build command in our IDE
After the application is running satisfyingly for you:
In your development IDE, use the following command line for post build events.
C:\Projects\pathto\bin\Release\AppCast.exe "C:\Projects\pathto\bin\Release\MyApp.exe" "c:\pathto\www.example.com\root\pathto\updates\AppCast.xml"
I don't know Why but Brent Arias macro not worked for me (#(VersionNumber) always was empty) :( .Net6 VS2022. I ended up with slightly modified version:
<Target Name="GetVersion" AfterTargets="PostBuildEvent">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="AssemblyInfo" />
</GetAssemblyIdentity>
<PropertyGroup>
<VersionInfo>%(AssemblyInfo.Version)</VersionInfo>
</PropertyGroup>
<!--And use it after like any other variable:-->
<Message Text="VersionInfo = $(VersionInfo)" Importance="high" />
</Target>
It should be noted that using the modernized (VS2017+) .csproj formatting and VS2022, $(AssemblyVersion)
as in the original post can now be used directly.
Unless I'm missing something, this is a lot simpler. Put this in your pre or post-build scripts:
FOR /F delims^=^"^ tokens^=2 %%i in ('findstr /b /c:"[assembly: AssemblyVersion("$(ProjectDir)\Properties\AssemblyInfo.cs') do (set version=%%i)
echo Version: %version%
I needed exactly this for automatically putting the number in the readme file in the output folder. In the end, as Winston Smith showed, a small external tool is a very good solution for that, and it has the advantage you can format it however you want.
This app outputs the formatted version to the console. I used it in my post-build events to build the readme file by calling it with >> to redirect its output to the readme file.
public class GetVerNum
{
static void Main(String[] args)
{
if (args.Length == 0)
return;
try
{
FileVersionInfo ver = FileVersionInfo.GetVersionInfo(args[0]);
String version = "v" + ver.FileMajorPart.ToString() + "." + ver.FileMinorPart;
if (ver.FileBuildPart > 0 || ver.FilePrivatePart > 0)
version += "." + ver.FileBuildPart;
if (ver.FilePrivatePart > 0)
version += "." + ver.FilePrivatePart;
Console.Write(version);
}
catch { }
}
}
My post-build events:
<nul set /p dummyset=My Application > "$(ProjectDir)\Readme\readme-header.txt"
"$(ProjectDir)\Readme\GetVersionNumber.exe" "$(TargetPath)" >>"$(ProjectDir)\Readme\readme-header.txt"
echo by Nyerguds>>"$(ProjectDir)\Readme\readme-header.txt"
echo Build date: %date% %time% >> "$(ProjectDir)\Readme\readme-header.txt"
echo.>>"$(ProjectDir)\Readme\readme-header.txt"
copy /b "$(ProjectDir)\Readme\readme-header.txt" + "$(ProjectDir)\Readme\readme-body.txt" "$(TargetDir)\$(ProjectName).txt"
I put all the readme generating related stuff in the \Readme\ folder of my project; the app containing the above code, and the "readme-body.txt" containing the actual readme stuff.
First line: create the "readme-header.txt" file in the \Readme\ folder of my project, and put the program name inside it. (The <nul set /p dummyset= is a trick I found here: Windows batch: echo without new line). You could also store this string in another text file and just copy that to "readme-header.txt" instead.
Second line: run the version number retrieving app with the freshly-generated exe file as parameter, and add its output to the header file.
Third line: add any other stuff (in this case, credits) to the header file. This also adds a line break to the end.
These three together give you a "readme-header.txt" file with "My Application v1.2.3 by Nyerguds", followed by a line break, in it. Then I add the build date and another open line, and copy the header file and the readme body file together to one file in the final build folder. Note that I specifically use binary copy, otherwise it gives odd results. You do have to make sure the body file contains no UTF-8 byte order mark at the start, or you get weird bytes in your final file.
If you have a library project you can try to use WMIC utility (available in windows).
Here is an example. Good thing - you don't need to use any external tools.
SET pathFile=$(TargetPath.Replace("\", "\\"))
FOR /F "delims== tokens=2" %%x IN ('WMIC DATAFILE WHERE "name='%pathFile%'" get Version /format:Textvaluelist') DO (SET dllVersion=%%x)
echo Found $(ProjectName) version %dllVersion%
I looked for the same feature and i found the solution on MSDN.
https://social.msdn.microsoft.com/Forums/vstudio/de-DE/e9485c92-98e7-4874-9310-720957fea677/assembly-version-in-post-build-event?forum=msbuild
$(ApplicationVersion) did the Job for me.
Edit:
Okay I just saw the Problem $(ApplicationVersion) is not from AssemblyInfo.cs, its the PublishVersion defined in the project Properties. It still does the job for me in a simple way. So maybe someone needs it too.
Another Solution:
You can call a PowerShell script on PostBuild, here you can read the AssemblyVersion directly from your Assembly. I call the script with the TargetDir as Parameter
PostBuild Command:
PowerShell -ExecutionPolicy Unrestricted $(ProjectDir)\somescript.ps1 -TargetDir $(TargetDir)
PowerShell Script:
param(
[string]$TargetDir
)
$Version = (Get-Command ${TargetDir}Example.exe).FileVersionInfo.FileVersion
This way you will get the Version from the AssemblyInfo.cs

Categories