Get parent directory of MSBuildProjectDirectory - msbuild

I have solution with projects. Also I have in solution directory folder with msbuild files.
In msbuild file I have next code :
<PropertyGroup Label="Build Directories">
<RootPath>$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)'))</RootPath>
</PropertyGroup>
<ItemGroup>
<MSBuildProjectInfrastructure Include="$(RootPath)MyApp.Services.Infrastructure.sln">
<AdditionalProperties>Configuration=$(Configuration);Platform=$(Platform);</AdditionalProperties>
</MSBuildProjectInfrastructure>
</ItemGroup>
Which works badly, as I need to go in parent directory to find MyApp.Services.Infrastructure.sln
Structure :
SolutionFolder
-- MsBuildsFolder
-- ProjectFile
Here is quite similar question, but doesn't resolve my issue

To get the parent folder, you can let MSBuild determine the location of known file in that folder by means of the built in property function GetDirectoryNameOfFileAbove:
<PropertyGroup>
<RootPath>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), MyApp.Services.Infrastructure.sln))</RootPath>
</PropertyGroup>

Related

What can I do about my build error in Visual Studio 2019 .net core? cannot find part of path \obj\Debug\net48\Package\PackageTmp

I am so confused, this is my error:
1>obj\Debug\net48\Package\PackageTmp.
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.targets(3000,5): error : Copying file obj\Debug\net48\NuGet\DD3A9BE7C0EDA549CD5B7B690E0621F0E2C932EC\Glass.Mapper.Sc.100\5.8.173\App_Config/Include/Glass/Glass.Mapper.Sc.Start.config to obj\Debug\net48\Package\PackageTmp\obj\Debug\net48\NuGet\DD3A9BE7C0EDA549CD5B7B690E0621F0E2C932EC\Glass.Mapper.Sc.100\5.8.173\App_Config/Include/Glass/Glass.Mapper.Sc.Start.config failed. Could not find a part of the path 'obj\Debug\net48\Package\PackageTmp\obj\Debug\net48\NuGet\DD3A9BE7C0EDA549CD5B7B690E0621F0E2C932EC\Glass.Mapper.Sc.100\5.8.173\App_Config/Include/Glass/Glass.Mapper.Sc.Start.config'.
1>Done building project "some.project.ORM.csproj" -- FAILED.
I have tried to delete the obj\Debug folder in the file system as well as the solution, as some have suggested. I have unloaded the project to review the .csproj file, I didn't see anything strange. I tried removing the Nuget Package and Reinstalling it. I just am not certain.
any help would be welcomed.
Note: I checked the directories do exist and they do. I even deleted the /obj/Debug folders from the solution as well as the source, as suggested, nothing seems to be working.
here is the .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RootNamespace>some.project.ORM</RootNamespace>
<AssemblyName>some.project.ORM</AssemblyName>
<PublishTargetType>website</PublishTargetType>
</PropertyGroup>
<Import Project="$(SolutionDir)\_Build\props\_PublishTargetType.props" />
<ItemGroup>
<PackageReference Include="Glass.Mapper.Sc.100" Version="5.8.173" />
</ItemGroup>
<ItemGroup>
<Content Update="App_Config\Include\Foundation\Glass\Glass.Mapper.Sc.Start.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
this issue was because the file that I was copying to was over the 260 limit in VS. My path length was 261. We copied the solution to a shorter path and the build worked fine. thanks for your help.

how to add files to a projects output files from an MSBuild Task

Given an MSBuild Task that runs in AfterTargets="AfterCompile" and produces some files how do you get those files to be included in the current projects output so that the files will be copied to the bin directory of any projects referencing that project?
I have no guarantees that this is the right solution but it seems to work:
<Target Name="MyTarget" AfterTargets="AfterCompile">
<PropertyGroup>
<MyInput>D:\1.txt</MyInput>
<MyOutput>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)$(OutDir)\1.txt'))</MyOutput>
</PropertyGroup>
<Copy SourceFiles="$(MyInput)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" />
<ItemGroup>
<AllItemsFullPathWithTargetPath Include="$(MyOutput)">
<TargetPath>1.txt</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AllItemsFullPathWithTargetPath>
</ItemGroup>
</Target>
The relevant logic is here:
http://sourceroslyn.io/#MSBuildTarget=GetCopyToOutputDirectoryItems
http://sourceroslyn.io/#MSBuildItem=AllItemsFullPathWithTargetPath
Basically we rely on the fact that to determine the list of files to copy from dependent projects MSBuild calls the GetCopyToOutputDirectoryItems target of the dependent projects and uses its output (which is AllItemsFullPathWithTargetPath).
By adding ourselves to AllItemsFullPathWithTargetPath at the last minute we get picked up when a dependent project calls us.
Thank you, Kirill. That was an excellent answer and it helped me when trying to copy ETW manifest files from a different project's output. Below is the final output.
Since I've simply expanded upon Kirill's answer, I do not expect this answer to be accepted. I post this here in the hope it helps someone else.
<Target Name="IncludeEtwFilesInOutput"
BeforeTargets="GetCopyToOutputDirectoryItems">
<PropertyGroup>
<EtwManifestFile>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)$(OutDir)\My.etwManifest.man'))</EtwManifestFile>
<EtwResourceFile>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)$(OutDir)\My.etwManifest.dll'))</EtwResourceFile>
</PropertyGroup>
<ItemGroup>
<AllItemsFullPathWithTargetPath Include="$(EtwManifestFile)">
<TargetPath>My.etwManifest.man</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AllItemsFullPathWithTargetPath>
<AllItemsFullPathWithTargetPath Include="$(EtwResourceFile)">
<TargetPath>My.etwManifest.dll</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AllItemsFullPathWithTargetPath>
</ItemGroup>
</Target>

structure solution outputpath by project

I'm building a visual studio solution with msbuild
msbuild.exe my.sln
This way it outputs everything to the output paths specified in each project (bin\ by default), but in this case I need all the output artefacts to be in different folder, used for packaging. If I run
msbuild.exe my.sln /p:OutputhPath=<someFolder>
Then all the artifacts will end up in the specified folder, but the structure will be flat. What I would like it to be, is:
\package
\project1
\project2
...
But I can't think of a good way to do this, without modifying individual project files (which is almost out of question). Any ideas? (msbuild 4.0, VS2010 - if that changes anything)
There is probably a better way, but one thing you could do is build in place with msbuild.exe my.sln, and then copy the outputs to your \package dir so you keep the hierarchy. It should be pretty simple to do. You can use this as a starting point:
<Target Name="Package">
<PropertyGroup>
<SourceFolder>$(MSBuildProjectDirectory)\src</SourceFolder>
<TargetFolder>$(MSBuildProjectDirectory)\package</TargetFolder>
</PropertyGroup>
<ItemGroup>
<FilesToCopy Include="$(SourceFolder)\**\bin\Debug\**\*.*" />
</ItemGroup>
<!-- Recursive copy w/o flattening folder structure: -->
<Copy
SourceFiles="#(FilesToCopy)"
DestinationFiles="#(FilesToCopy->'$(TargetFolder)\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
You can also define a property to keep track of your build configuration, and replace the hardcoded bin\Debug with bin\$(BuildConfig).

Property scope using msbuild extension pack detokenise

Im trying to use the msbuild extensions pack to fix up the configuration of our app on deploy,
i want to be able to pass a property (ENV) which will load my environment specific config file to use with the detokeniser, and fix up my application configs.
Like this:
<UsingTask TaskName="MSBuild.ExtensionPack.FileSystem.Detokenise"
AssemblyFile=".\Tools\MSBuild Extension Pack 4.0.3.0\MSBuild.ExtensionPack.dll"/>
<Import Project=".\Environments\$(Env).properties"/>
<Target Name="Build" >
<ItemGroup>
<SourceTemplates Include=".\Templates\**\*.*"/>
</ItemGroup>
<RemoveDir Directories=".\Temp"/>
<MakeDir Directories=".\Temp"/>
<Message Text="#(SourceTemplates)"/>
<Copy SourceFiles="#(SourceTemplates)"
DestinationFolder=".\Temp\%(RecursiveDir)" />
<ItemGroup>
<TargetTemplates Include=".\Temp\**\*.*"/>
</ItemGroup>
<MSBuild.ExtensionPack.FileSystem.Detokenise
TaskAction="Detokenise"
TargetFiles="#(TargetTemplates)"/>
</Target>
So i call this using
msbuild Detokenise.msbuild /p:Env=Prod
Msbuild knows about my file and i have access to its properties, but when the detokeniser runs i get the error:
Detokenise Task Execution Completed [15:07:50]
C:\Source\1.2\Build\Detokenise.msbuild(27,3):
error : InvalidProjectFileException: The imported project "C:\Source\1.2\Build\Environments\.properties" was not found.
Confirm that the path in the <Import> declaration is correct, and that the file exists on disk.
C:\Source\1.2\Build\Detokenise.msbuild\r
C:\Source\1.2\Build\Detokenise.msbuild(27,3): error :
All works fine if i hard code it-
Any ideas how to solve this. I thought of doing some text replacement on the msbuild before i execute...
You could try to assign this parameter to a local property:
<PropertyGroup Condition="'$(Env)'=='Prod'">
<TargetEnv>Prod</TargetEnv>
</PropertyGroup>
<!-- add other environments as needed -->
<PropertyGroup Condition="'$(Env)'=='Test'">
<TargetEnv>Test</TargetEnv>
</PropertyGroup>
<Import Project=".\Environments\$(TargetEnv).properties"/>
You could also try to enclose your parameter value in quotes:
msbuild Detokenise.msbuild /p:"Env=Prod"
As is your problem can't be reproduced, so it may be a side effect of other parameters not shown in your sample code.
I've seen a number of other questions where a similar problems was happening:
Visual Studio Ignoring MSBuild file (csproj) Customizations

MSBuild, clear out multiple files in a project

I am getting the following error when trying to clear out files within my project
LC error LC0000: 'Could not find file 'E:\CI\BuildServer\RMS-Transition\Group\dev\Controls\Properties\licenses.licx'.'
My MSBuild task looks like this...
<Target Name="ClearLicenseFiles">
<ItemGroup>
<LicenseFiles Include="..\**\*.licx"/>
</ItemGroup>
<WriteLinesToFile File="%(LicenseFiles.FullPath)" Lines="" Overwrite="true"/>
</Target>
What is going on? It seems to find all of the .licx files just fine but when it goes to write to them, they don't exist... and according to the documentation the WriteLinesToFile task should create the file anyways if it doesn't already exist.
I am starting to believe that this is a bug with MSBuild... the license files are being DELETED not overwritten as you would expect. Someone else has had this issue as well (comment on bottom of this msdn article)
This is my solution... I created a file that is empty named empty.txt right next to my msbuild proj and then copied this file onto the licx files.
<Target Name="ClearLicenseFiles">
<ItemGroup>
<LicenseFiles Include="..\**\*.licx"/>
</ItemGroup>
<Copy SourceFiles="empty.txt" DestinationFiles="%(LicenseFiles.FullPath)"/>
</Target>