The '$(SolutionDir)' macro doesn't work well from the command line. Replace references to the Solution with references relative to the current Project.If you'd like an overview on using build events (and the macros that are available), take a look at this article: Using Build Events in Visual Studio to Make Life Easier.
Using a Post-Build Event
Let's look at an example of how I use post-build events. Here's the folder structure for a solution:
This solution has 4 projects: (1) "Common" is a shared library with cross-cutting classes defined; (2) "PeopleViewer" is a desktop application, (3) "PeopleViewer.Presentation" is a view-model class library, and (4) "PersonReader.CSV" is a data reader.
The top folder, "AdditionalFiles", contains a text file with the data. The "PeopleViewer" needs access to this file in order for the application to work. So, in this scenario, I want to copy the contents of the "AdditionalFiles" folder into the output folder for the "PeopleViewer" project.
Here is a post-build event that does this:
xcopy "$(SolutionDir)AdditionalFiles\*.*" "$(TargetDir)" /Y
The "$(SolutionDir)" macro expands into the fully qualified path for the solution. This includes the trailing "\". The "$(TargetDir)" macro expands to the fully-qualified location where the .exe file ends up -- something like "[fullpath]\PeopleViewer\bin\Debug\" for .NET Framework and "[fullpath]\PeopleViewer\bin\Debug\netcoreapp3.1" for .NET Core.
This works fine when we build the application from Visual Studio:
4>1 File(s) copied
The build succeeds and the file is copied.
The problem arises when we leave Visual Studio.
.NET Core and the Lack of Solutions
In the .NET Core world, the concept of the "Solution" is less important. We often open folders (not solutions) in Visual Studio Code. And when we use the command-line tools, we build projects (not solutions). The ".sln" file is not important in this scenario, and it is generally not used at all.
This works because the projects have all of the information that they need to build, including dependencies on other projects and on NuGet packages.
So to build the "PeopleViewer" project, I can open the command line to the project location and type "dotnet build".
But, the post-build event fails:
0 File(s) copied
File not found - *.*
C:\Development\Sessions\DI\Scratch\PeopleViewer\PeopleViewer.csproj(15,5): error MSB3073: The command "xcopy "*Undefined*AdditionalFiles\*.*" "C:\Development\Sessions\DI\Scratch\PeopleViewer\bin\Debug\netcoreapp3.0\" /Y" exited with code 4.
The message tells us that the copy step failed. And if we look closer at the message, we can see why:
When trying to expand the macros, the '$(SolutionDir)' is undefined. There is no concept of a solution at this level, just the project. So the build fails.
Relative to the Project
The fix for this is to not use any of the "Solution" macros in the post-build events. Instead, set things relative to the current project.
Here's an updated post-build event:
xcopy "$(ProjectDir)..\AdditionalFiles\*.*" "$(TargetDir)" /Y
To get to the "AdditionalFiles" folder, we start with the current project folder, then go up a level (with the "..").
When we build this from Visual Studio, it still works. But more importantly, it works when we build from the command line:
1 File(s) copied
Post-build events are really useful, and I'm often using them to copy data files or late-bound assemblies to the output folder of my projects. But I've changed how I use them.
Instead of using post-build events relative to the Solution, I use events relative to the Project.It's always exciting to come up with a fix to an issue. But this isn't completely solved.
One of the really awesome things about .NET Core is that it is cross platform. I can create applications that will run on Windows, macOS, and Linux.
But "xcopy" is a Windows command. How do we copy files for other OSes?
For that we need to dig into MSBuild. That will wait for the next article, but here's a preview:
This block in the project file will copy the files from the "AdditionalFiles" folder to the output folder. And it works on macOS and Linux.
We'll explore that in the next article.
Update: That article is now available: Cross-Platform Build Events in .NET Core using MSBuild.