How can I add an AfterBuild event to a project when installing my NuGet package? - powershell

I have a nuget package which adds an executable that I need to run after a project builds each time.
I can manually add this by adding a section to each project file like so:
<Target Name="AfterBuild">
<PropertyGroup>
<PathToOutputExe>..\bin\Executable.exe</PathToOutputExe>
<PathToOutputJs>"$(MSBuildProjectDirectory)\Scripts\Output.js"</PathToOutputJs>
<DirectoryOfAssemblies>"$(MSBuildProjectDirectory)\bin\"</DirectoryOfAssemblies>
</PropertyGroup>
<AspNetCompiler Condition="'$(MvcBuildViews)'=='true'" VirtualPath="temp" PhysicalPath="$(ProjectDir)" />
<Exec Command="$(PathToOutputExe) $(PathToOutputJs) $(DirectoryOfAssemblies)" />
</Target>
How can I add this to a project when I install the nuget package? (i.e. using the DTE $project object in the Install.ps1 file)
I would greatly appreciate any help on this.
Thanks
Richard

As of NuGet 2.5, by convention, if you add a targets file at build\{packageid}.targets (note 'build' is at the same level as content and tools), NuGet will automatically add an Import to the .targets file in the project. Then you don't need handle anything in install.ps1. The Import will be automatically removed on uninstall.
Also, I think the recommended way to do what you want would to create a separate target that is configured to run after the standard "Build" target:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="NuGetCustomTarget" AfterTargets="Build">
<PropertyGroup>
<PathToOutputExe>..\bin\Executable.exe</PathToOutputExe>
<PathToOutputJs>"$(MSBuildProjectDirectory)\Scripts\Output.js"</PathToOutputJs>
<DirectoryOfAssemblies>"$(MSBuildProjectDirectory)\bin\"</DirectoryOfAssemblies>
</PropertyGroup>
<AspNetCompiler Condition="'$(MvcBuildViews)'=='true'" VirtualPath="temp" PhysicalPath="$(ProjectDir)" />
<Exec Command="$(PathToOutputExe) $(PathToOutputJs) $(DirectoryOfAssemblies)" />
</Target>
</Project>

Here's a script that adds a afterbuild target. It also user the NugetPowerTools mentioned above.
$project = Get-Project
$buildProject = Get-MSBuildProject
$target = $buildProject.Xml.AddTarget("MyCustomTarget")
$target.AfterTargets = "AfterBuild"
$task = $target.AddTask("Exec")
$task.SetParameter("Command", "`"PathToYourEXe`" $(TargetFileName)")
The hard part was getting the quotes right. This is what it looks like in my powertools script.

You can use the MSBuild Api directly, as in this blog post.
Add-Type -AssemblyName 'Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
$msbProject = [Microsoft.Build.Evaluation.ProjectCollection]::GlobalProjectCollection.GetLoadedProjects($project.FullName) | Select-Object -First 1
# use $msbProject to add/change AfterBuild target
Or you could use NugetPowerTools which adds Powershell command Get-MSBuildProject to do much the same thing.
Also, see this forum post for more details on adding the new target.

Related

Nuget Pack Failure - The process cannot access the file 'D:\a\1\a\*.nupkg'

Error on Azure Pipeline for NuGet Pack task, using a SDK format .csproj, which autogenerates the .nuspec file:
The process cannot access the file 'D:\a\1\a\*.nupkg' because it is being used by another process.
System.IO.IOException: The process cannot access the file 'D:\a\1\a\*.nupkg' because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalDelete(String path, Boolean checkHost)
at NuGet.Commands.PackCommandRunner.BuildPackage(PackageBuilder builder, String outputPath, Boolean symbolsPackage)
at NuGet.Commands.PackCommandRunner.BuildFromProjectFile(String path)
at NuGet.CommandLine.PackCommand.ExecuteCommand()
at NuGet.CommandLine.Command.ExecuteCommandAsync()
at NuGet.CommandLine.Command.Execute()
at NuGet.CommandLine.Program.MainCore(String workingDirectory, String[] args))
##[error]An error occurred while trying to pack the files.
The .csproj file being built, uses TargetsForTfmSpecificBuildOutput:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>Provides a .....</Description>
</PropertyGroup>
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
<!-- Filter out unnecessary files -->
<_ReferenceCopyLocalPaths Include="#(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" />
</ItemGroup>
<!-- Print batches for debug purposes -->
<Message Text="Batch for .nupkg: ReferenceCopyLocalPaths = #(_ReferenceCopyLocalPaths), ReferenceCopyLocalPaths.DestinationSubDirectory = %(_ReferenceCopyLocalPaths.DestinationSubDirectory) Filename = %(_ReferenceCopyLocalPaths.Filename) Extension = %(_ReferenceCopyLocalPaths.Extension)" Importance="High" Condition="'#(_ReferenceCopyLocalPaths)' != ''" />
<ItemGroup>
<!-- Add file to package with consideration of sub folder. If empty, the root folder is chosen. -->
<BuildOutputInPackage Include="#(_ReferenceCopyLocalPaths)" TargetPath="%(_ReferenceCopyLocalPaths.DestinationSubDirectory)" />
</ItemGroup>
</Target>
The above modification to the .csproj file is needed due to legacy dll's being required to be built which can't be packaged up on their own. But is based on this answer: https://stackoverflow.com/a/59893520/1231374
Note: Removing the custom package steps still causes the error.
There is an additional error before this, not sure if this could be related.
Error NU5128: Some target frameworks declared in the dependencies group of the nuspec and the lib/ref folder do not have exact matches in the other location. Consult the list of actions below:
- Add a dependency group for .NETStandard2.0 to the nuspec
See the task configuration below:
See the Nuget installer task, which is the first task the installer runs:
I finialy found a "reason" to this problem.
I can't use dotnet cli on my side because my project is not compatible, but I find a workaround.
It seems related to that https://github.com/NuGet/Home/issues/8713, so I used the "Nuget Tool Installer" to force version "5.2.x" and it just works as expected.
I don't understand why this problem is present since 5 minor versions !
The resolution for my problem was to use dotnet pack (as I am working with .NetStandard and .NetCore projects) instead of nuget pack.
In particular to enable the Do not build option. As a pervious step builds the solution and projects within it.

NuGet package adds incorrect hint path

During an automated build, my nuget package needs to be non framework dependent, however I keep finding that the nuget package getting added is incorrectly adding a HintPath.
Within my nuspec I've defined the files that are part of the package:
<files>
<file src="lib\xyz.dll" target="lib\xyz.dll" />
<file src="lib\xyz.xml" target="lib\xyz.xml" />
</files>
However whenever I add the package to my project/solution, it incorrectly adds a hint path specifying:
<Reference Include="xyz, Version=11.0.0.0, Culture=neutral, PublicKeyToken=4a3c0a4c668b48b4">
<HintPath>..\packages\xyz.11.0.0.0\xyz.dll</HintPath>
<Private>True</Private>
</Reference>
This is causing the automated build server to not find the assembly and fail to build. I can manually fix the hint path, but would rather not.
I took a look at this post (Failed to add NuGet package) but I don't find it relevant. This post (NuGet package install uses specific assembly version in csproj files) seemed to be referring to the same problem but with no answer. Anybody have any thoughts?
You can work around this by using a custom MSBuild task.
Instead of adding the assembly to the lib directory create an MSBuild .targets file named after the package id and put your xyz assembly next to it.
\build
\Net45
\MyPackage.targets
\xyz.dll
\xyz.xml
Then in the MSBuild .targets file add the reference exactly how you want it to be. Something like:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Reference Include="xyz">
<HintPath>$(MSBuildThisFileDirectory)\xyz.dll</HintPath>
</Reference>
</ItemGroup>
</Project>
The above shows how to specify a hint path relative to the MSBuild .targets file. You said that you do want to use a hint path so you could remove that if xyz.dll can be resolved by MSBuild somehow, such as it being in the GAC.

How to use a native NuGet package from a managed project?

I have a managed project that uses a C-style native DLL through P/Invoke.
What is the correct way to package the native DLL so it can be added as a NuGet package to the managed project, and have the DLL be copied automatically to the output folder?
I have currently created a package using CoApp for the native DLL but i can't use it from the managed project; I get the following error when trying to add the package:
Could not install package 'foo.redist 1.0.0'. You are trying to
install this package into a project that targets
'.NETFramework,Version=v4.5.1', but the package does not contain any
assembly references or content files that are compatible with that
framework. For more information, contact the package author.
Currently i only have these "pivots" in the autopkg file:
[Win32,dynamic,release] {
bin: release\foo.dll;
}
[Win32,dynamic,debug] {
bin: debug\foo.dll;
}
... do i need to add something else?
I'm in a similar situation. I opted not to use CoApp for this project, but to create a fresh nuspec/.targets file combination instead.
Inside the nuspec file I use a <files> element to list my native dlls.
In the .targets file you have access to the msbuild Condition attribute, which allows basic Configuration pivoting. In our case we always deploy 64 bit binaries, so the Platform pivot is not needed, but you could also add it if needed.
I get warnings when running nuget pack since the binaries are not inside lib, but it works fine otherwise.
Steps:
run nuget spec in the folder that contains your vcxproj
create a .build folder, in that folder create an empty mydll.targets file (match the nuspec filename)
manually populate the files similarly to the examples below;
Example mydll.nuspec:
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
...your metadata here
</metadata>
<files>
<file src="x64\Release\my.dll" target="x64\Release\my.dll" />
<file src="x64\Debug\my.dll" target="x64\Debug\my.dll" />
</files>
</package>
Example mydll.targets:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\..\x64\Release\my.dll" Condition="'$(Configuration)'=='Release'">
<Link>my.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)\..\x64\Debug\my.dll" Condition="'$(Configuration)'=='Debug'">
<Link>my.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

NuGet Package Restore: Visual Studio Online & POSTSHARP

I installed POSTSHARP as a nuget package and I want Visual Studio Online to automatically restore it.
POSTSHARP must be restored before build though.
I am trying to follow this with no success: link
How can I run scripts / commands in Visual Studio Online BEFORE build?
There are instructions on nuget.org on how to set up a package restore with TFS, including Visual Studio Online: http://docs.nuget.org/docs/reference/package-restore-with-team-build
It mentions that the default Build Process Templates for VSO already implements NuGet Package Restore workflow. So, supposedly, you need to do additional setup only when you customize the templates.
The proposed approach is to create a simple MSBuild project file that will be used to build the solution. You can include all the required targets there (e.g. Build, Rebuild, Clean) that will just invoke MSBuild on your solution file with specifying the corresponding target.
Additionally create a target for package restore - it will invoke NuGet.exe restore MySolution.sln command. The common build targets will depend on this one.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutDir>$(MSBuildThisFileDirectory)bin</OutDir>
<Configuration>Release</Configuration>
<ProjectProperties>
OutDir=$(OutDir);
Configuration=$(Configuration);
</ProjectProperties>
</PropertyGroup>
<ItemGroup>
<Solution Include="$(MSBuildThisFileDirectory)src\*.sln" />
</ItemGroup>
<Target Name="RestorePackages">
<Exec Command=""$(MSBuildThisFileDirectory)tools\NuGet\NuGet.exe" restore "%(Solution.Identity)"" />
</Target>
<Target Name="Build" DependsOnTargets="RestorePackages">
<MSBuild Targets="Build"
Projects="#(Solution)"
Properties="$(ProjectProperties)" />
</Target>
<!-- other targets... -->
</Project>

Packing NuGet projects compiled in release mode?

Is there some way to make a NuGet package using code compiled in release mode? Or is there some reason I should only publish (make available locally, in this case) packages compiled in debug mode?
Every time I call nuget pack from my project directory, where I have the nuspec file below, on code I have only compiled in release mode, it complains about not finding the DLL in the debug folder ("\bin\Debug\SomeProject.dll"). If I compile it in debug mode, those files are there and it packs them up as it should.
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>$id$</id>
<version>$version$</version>
<authors>$author$</authors>
<owners>$author$</owners>
<iconUrl>http://somewhere/project.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>$description$</description>
</metadata>
</package>
You can solve it like this:
NuGet.exe pack Foo.csproj -Prop Configuration=Release
(reference)
If you are using a post-build event and you want to create a package whether using Debug or Release configuration you can setup the post-build event commandline like so:
"<path to nuget tools>\NuGet.exe" pack "$(ProjectPath)" -Prop Configuration=$(ConfigurationName)
To have NuGet automatically use Release mode when you run nuget pack, do the following:
Open your .csproj file in a text editor.
Find the following line:
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
In this line, replace Debug with Release.
Save changes.
The answers here are good, but I was having a lot of problems with this for a .NET Standard project. I had a project that was only going to publish Release binaries, but it wasn't respecting my default build output path.
I added this to my CSProj which then enabled me to use the accepted answer here.
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<OutputPath>$(SolutionDir)bin\$(PlatformTarget)\Release</OutputPath>
</PropertyGroup>
Chiming in here.
My build profile would build the DLLs to bin\<arch>\Debug|Release.
I was able to point to my folders by running the nuget command as follows:
Notice how I used the -p option.
PS > nuget pack -p Configuration="x64\Release"
Attempting to build package from ...
...
Found packages.config. Using packages listed as dependencies
...
- Add a dependency group for .NETFramework4.7.2 to the nuspec
Successfully created package...