Nuget Update Packages.config in MSBuild BeforeBuild Step - c#

I have been trying to write an MsBuild task to automatically get Nuget packages from a feed url and automatically update the packages.config to update to the latest version.
// ---- Download and install a package at a desired path ----
var sourceUri = new Uri("FEED URL");
// ---- Update the ‘packages.config’ file ----
var packageReferenceFile = new PackageReferenceFile("../../packages.config");
string packagesPath = "../../packages";
IPackageRepository sourceRepository = PackageRepositoryFactory.Default.CreateRepository(sourceUri.ToString());
PackageManager packageManager = new PackageManager(sourceRepository, packagesPath);
foreach (var sourcePackage in sourceRepository.GetPackages().Where(x => x.IsLatestVersion))
{
if (!packageReferenceFile.EntryExists(sourcePackage.Id + " " + sourcePackage.Version, sourcePackage.Version))
{
var oldPackage = packageReferenceFile.GetPackageReferences().FirstOrDefault(x => x.Id.Contains(sourcePackage.Id));
if (oldPackage != null)
{
packageReferenceFile.DeleteEntry(oldPackage.Id, oldPackage.Version);
}
packageManager.InstallPackage(sourcePackage.Id, SemanticVersion.Parse(sourcePackage.Version.ToFullString()));
// Get the target framework of the current project to add --> targetframework="net452" attribute in the package.config file
var currentTargetFw = Assembly.GetExecutingAssembly()
.GetCustomAttributes(typeof(TargetFrameworkAttribute), false);
var targetFrameworkAttribute = ((TargetFrameworkAttribute[]) currentTargetFw).FirstOrDefault();
// Update the packages.config file
packageReferenceFile.AddEntry(sourcePackage.GetFullName(),
SemanticVersion.Parse(sourcePackage.Version.ToFullString()), false,
new FrameworkName(targetFrameworkAttribute.FrameworkName));
}
}
This is working fine as a console app and is automatically reading the file correctly and updating the necessary references.
When i try to run this as an MsBuild task I keep running into errors.
An error has occurred during compilation. c:\Users\user\AppData\Local\Temp\dkkg20ya.0.cs(22,11) : error CS0246: The type or namespace name 'NuGet' could not be found (are you missing a using directive or an assembly reference?)
The task factory "CodeTaskFactory" could not be loaded from the assembly "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Microsoft.Build.Tasks.v15.0.dll". The task factory must return a value for the "TaskType" property.
This is the code I have put in the csproj (also moved to the nuget.targets to test)
<Target Name="BeforeBeforeBuild" BeforeTargets="BeforeBuild">
<UpdateNugetFiles />
</Target>
<UsingTask TaskName="UpdateNugetFiles" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll" >
<Task>
<Reference Include="System.Core" />
<Using Namespace="System" />
<Using Namespace="System.Linq" />
<Using Namespace="System.Reflection" />
<Using Namespace="System.Runtime.Versioning" />
<Using Namespace="NuGet" />
<Code Type="Fragment" Language="cs">
<![CDATA[
try {
// ---- Download and install a package at a desired path ----
var sourceUri = new Uri("FEED URL");
// ---- Update the ‘packages.config’ file ----
var packageReferenceFile = new PackageReferenceFile("../../packages.config");
string packagesPath = "../../packages";
IPackageRepository sourceRepository = PackageRepositoryFactory.Default.CreateRepository(sourceUri.ToString());
PackageManager packageManager = new PackageManager(sourceRepository, packagesPath);
foreach (var sourcePackage in sourceRepository.GetPackages().Where(x => x.IsLatestVersion))
{
if (!packageReferenceFile.EntryExists(sourcePackage.Id + " " + sourcePackage.Version, sourcePackage.Version))
{
var oldPackage = packageReferenceFile.GetPackageReferences().FirstOrDefault(x => x.Id.Contains(sourcePackage.Id));
if (oldPackage != null)
{
packageReferenceFile.DeleteEntry(oldPackage.Id, oldPackage.Version);
}
packageManager.InstallPackage(sourcePackage.Id, SemanticVersion.Parse(sourcePackage.Version.ToFullString()));
// Get the target framework of the current project to add targetframework="net452" attribute in the package.config file
currentTargetFw = Assembly.GetExecutingAssembly()
.GetCustomAttributes(typeof(TargetFrameworkAttribute), false);
var targetFrameworkAttribute = ((TargetFrameworkAttribute[]) currentTargetFw).FirstOrDefault();
// Update the packages.config file
packageReferenceFile.AddEntry(sourcePackage.GetFullName(),
SemanticVersion.Parse(sourcePackage.Version.ToFullString()), false,
new FrameworkName(targetFrameworkAttribute.FrameworkName));
}
}
return true;
}
catch (Exception ex) {
Log.LogErrorFromException(ex);
return false;
}
]]>
</Code>
</Task>
</UsingTask>
Any ideas on how to resolve this as cannot seem to find a solution.
Overall what to run this a pre step on a CI build to keep nugets up to date.
Thanks
Tim

Just call
nuget restore "your_solution.sln"
Don't reinvent the wheel by writing it in C# code.

Nuget Update Packages.config in MSBuild BeforeBuild Step
Not sure where your code issue comes from. It may be simpler just use NuGet.exe to restore and update the solution instead of trying to use C# code.
So you could add following nuget command line in the MSBuild BeforeBuild Step
<Target Name="BeforeBeforeBuild" BeforeTargets="BeforeBuild">
<Exec Command="$(YourNuGetPath)\nuget.exe restore "$(YouSolutionPath)\YourSolution.sln" -PackagesDirectory "$(YouPackagePath)\packages"" />
<Exec Command="$(YourNuGetPath)\nuget.exe update "$(YouSolutionPath)\YourSolution.sln"" />
</Target>
Note: If you are using Visual Studio, Visual Studio will automatically check the missing packages during the build and restore them: Package Restore.
Hope this helps.

Related

Can I get NugetRestore to work with build.cake on azure devops (TFS)

I am trying to extract build tasks from TFS to cake scripts so that the build tasks get versioned together with the code. I have managed to create a script which cleans, NuGet-restores, builds, and runs unit tests on my project. It all works fine locally. however, after configuring TFS to run my cake script it keeps failing on following error:
2019-07-24T11:30:58.0377820Z ##[error]Unable to find version '4.4.3' of package 'System.Data.SqlClient'.
2019-07-24T11:30:58.0385573Z ##[error]Unable to find version '11.0.1' of package 'Newtonsoft.Json'.
2019-07-24T11:30:58.0401542Z ##[error]An error occurred when executing task 'Restore-Nuget-Packages'.
2019-07-24T11:30:58.0449315Z ##[error]Error: One or more errors occurred.
2019-07-24T11:30:58.0450913Z ##[error] NuGet: Process returned an error (exit code 1).
2019-07-24T11:30:58.0739069Z ##[error]System.Exception: Unexpected exit code 1 returned from tool Cake.exe
I tried to change the Cake task settings to use different versions of Nuget and feed urls. Currently, they look like this:
Nuget Exe Location: https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
Tool Feed Url: https://api.nuget.org/v3/index.json
We do have a private nuget host and I have tried modifying the tool feed url. However, from the logs it seems like the feed urls are OK.
Cake output:
Feeds used:
2019-07-24T11:30:55.7676783Z C:\Users\{usr}\AppData\Local\NuGet\Cache
2019-07-24T11:30:55.7677002Z C:\Users\{usr}\.nuget\packages\
2019-07-24T11:30:55.7677066Z http://{ournugeturl}
2019-07-24T11:30:55.7677233Z http://{ournugeturl2}
2019-07-24T11:30:55.7677301Z http://{ournugeturl3}
2019-07-24T11:30:55.7677572Z C:\Program Files (x86)\Microsoft SDKs\NuGetPackages\
Other nuget packages are installed, e.g,
2019-07-24T11:30:55.7882050Z Restoring NuGet package Autofac.4.8.1.
2019-07-24T11:30:55.9806596Z Adding package 'Autofac.4.8.1' to folder 'd:\w1\3263\s\packages'
I even set the verbosity level to Detailed in the Nuget-Restore task, however, I do not manage to find where the problem is.
Snippet from cake.build.
Task("Restore-Nuget-Packages")
.IsDependentOn("Clean")
.Does(() =>
{
NuGetRestore(solution, new NuGetRestoreSettings {
Verbosity = NuGetVerbosity.Detailed,
});
});
my NuGet.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<solution>
<add key="disableSourceControlIntegration" value="true" />
</solution>
<packageSources>
<add key="privaterepo1" value="http://privatenugetrepo/nuget/NTS" />
<add key="privaterepo2" value="http://privatenugetrepo/Default" />
<add key="nugetv3" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<activePackageSource>
<add key="All" value="(Aggregate source)" />
</activePackageSource>
<packageRestore>
<add key="enabled" value="True" />
</packageRestore>
</configuration>
build.cake
#tool "nuget:?package=xunit.runner.console&version=2.4.1"
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var solution = "./pathtosolution.sl";
////////////////////////////////////////////////
///// Clean the project packages
////////////////////////////////////////////////
Task("Clean")
.Does(() =>
{
CleanDirectory("./packages");
});
////////////////////////////////////////////////
///// Adds private Nuget feed links
////////////////////////////////////////////////
Task("Add-Private-NuGet-Feed")
.Does(() =>
{
string[] sources = {
"private-src1",
"private-src2",
"https://api.nuget.org/v3/index.json"
};
foreach(string feed in sources)
{
if (!NuGetHasSource(feed))
{
var accessToken = EnvironmentVariable("SYSTEM_ACCESSTOKEN")
?? throw new Exception("VSTS System Access Token is required to setup Private NuGet Feed");
Information($"Source {feed} is missing");
NuGetAddSource($"MyCompany-NuGet {feed}", feed, new NuGetSourcesSettings
{
UserName = "VSTS",
Password = accessToken,
}
);
} else
{
Information($"Source {feed} Exists");
}
}
});
////////////////////////////////////////////////
///// Restores all nuget packages.
////////////////////////////////////////////////
Task("Restore-Nuget-Packages")
.IsDependentOn("Add-Private-NuGet-Feed")
.Does(() =>
{
var solutions = GetFiles("./**/*.sln");
// Restore all NuGet packages.
foreach(var solution in solutions)
{
Information(solution);
Information("Restoring {0}", solution);
var nugetRestoreSettings = new NuGetRestoreSettings {
ConfigFile = new FilePath("./NuGet.Config"),
//MSBuildVersion = NuGetMSBuildVersion.MSBuild15
};
NuGetRestore(solution, nugetRestoreSettings);
}
});
////////////////////////////////////////////////
///// Runs DotNetCoreRestore
////////////////////////////////////////////////
Task("DotNetCoreRestore")
.Does(() =>
{
DotNetCoreRestore(
solution,
new DotNetCoreRestoreSettings()
{});
});
////////////////////////////////////////////////
///// Runs unit tests.
////////////////////////////////////////////////
Task("xUnit")
.IsDependentOn("Build")
.Does(() =>
{
var projects = GetFiles("./*.Tests/**/*.Tests.csproj");
foreach(var project in projects)
{
DotNetCoreTest(
project.FullPath,
new DotNetCoreTestSettings()
{
// Set configuration as passed by command line
Configuration = configuration
});
}
});
////////////////////////////////////////////////
///// Build
////////////////////////////////////////////////
Task("Build")
.IsDependentOn("Clean")
.IsDependentOn("Add-Private-Nuget-Feed")
.IsDependentOn("DotNetCoreRestore")
.IsDependentOn("Restore-Nuget-Packages")
.Does(() =>
{
MSBuild(solution, new MSBuildSettings {
ToolVersion = MSBuildToolVersion.VS2017});
});
////////////////////////////////////////////////
///// The main task.
////////////////////////////////////////////////
Task("Default")
.IsDependentOn("xUnit");
RunTarget(target);
Solved
By replacing the Cake task in TFS with a Powershell Script invoking build.ps1 on the build server resolved my problem.

How to programmatically install a NuGet package?

I want to programmatically install a NuGet package to a project, and update the .csproj file, and the packages.config file.
I am using the official Nuget.core framework which source code is available here: https://github.com/NuGet/NuGet2
I am not using the NuGet package: https://www.nuget.org/packages/NuGet.Core/
But the source code found on GitHub to be able to do some debugging.
Note: I am using the version 2.11 and not the 2.13
I am able to download a package at a desired directory and update the packages.config file:
// ---- Download and install a package at a desired path ----
string packageID = "Newtonsoft.json";
var sourceUri = new Uri("https://packages.nuget.org/api/v2");
// Return an IPackage
var package = GetNugetPackage(packageID, sourceUri);
IPackageRepository sourceRepository = PackageRepositoryFactory.Default.CreateRepository(sourceUri.ToString());
string packagesPath = "../../TestFiles/packages";
PackageManager packageManager = new PackageManager(sourceRepository, packagesPath);
packageManager.InstallPackage(packageID, SemanticVersion.Parse(package.Version.ToFullString()));
// ---- Update the ‘packages.config’ file ----
var packageReferenceFile = new PackageReferenceFile("../../TestFiles/packages.config");
// Get the target framework of the current project to add --> targetframework="net452" attribute in the package.config file
var currentTargetFw = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetFrameworkAttribute), false);
var targetFrameworkAttribute = ((TargetFrameworkAttribute[])currentTargetFw).FirstOrDefault();
// Update the packages.config file
packageReferenceFile.AddEntry(package.GetFullName(), SemanticVersion.Parse(package.Version.ToFullString()), false, new FrameworkName(targetFrameworkAttribute.FrameworkName));
Now I need to update the .csproj and here is the tricky part...
Here's what I tried so far:
string csprojFilePath = "../../TestFiles/test.csproj";
var project = new MSBuildProjectSystem(csprojFilePath);
string pathToAnExistingNugetPackageDll = "../../TestFiles/packages/Newtonsoft.json/lib/net45/Newtonsoft.json.dll"
project.AddReference(pathToAnExistingNugetPackageDll, Stream.Null);
project.Save();
This piece of code update the .csproj file, it add a new reference node like this:
<Reference Include="Newtonsoft.json">
<HintPath>..\packages\Newtonsoft.json\lib\net45\Newtonsoft.json.dll</HintPath>
</Reference>
But I need a complete reference node like this:
<Reference Include="Newtonsoft.json, Version=9.0.8, Culture=neutral, PublicKeyToken=b03f4f7d11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.json\lib\net45\Newtonsoft.json.dll</HintPath>
<Private>True</Private>
</Reference>
How can I do it ?
Thanks to #MattWard and #bashis, i've written my own code generator:
So, to add properly a reference into a given .csproj here's what I do:
var CsprojDoc = new XmlDocument();
CsprojDoc.LoadXml("*your_csproj_content*");
var Nsmgr = new XmlNamespaceManager(CsprojDoc.NameTable);
Nsmgr.AddNamespace("x", "http://schemas.microsoft.com/developer/msbuild/2003");
IPackage packageInfos = GetNugetPackage(packageId, packageRepositoryUri);
XmlNode referenceNode = CsprojDoc.CreateNode(XmlNodeType.Element, "Reference", XmlNamespaceValue);
XmlAttribute includeAttribute = CsprojDoc.CreateAttribute("Include");
var targetFwProfile = CsprojDoc.SelectSingleNode("//x:TargetFrameworkProfile", Nsmgr);
string targetFrameworkProfile = string.Empty;
if (!string.IsNullOrEmpty(targetFwProfile?.InnerXml))
{
targetFrameworkProfile = targetFwProfile.InnerXml;
}
var targetFwAttribute = GetTargetFrameworkFromCsproj();
Regex p = new Regex(#"\d+(\.\d+)+");
Match m = p.Match(targetFwAttribute.FrameworkName);
Version targetFwVersion = Version.Parse(m.Value);
// Get the package's assembly reference matching the target framework from the given '.csproj'.
var assemblyReference =
packageInfos.AssemblyReferences
.Where(a => a.TargetFramework.Identifier.Equals(targetFwAttribute.FrameworkName.Split(',').First()))
.Where(a => a.TargetFramework.Profile.Equals(targetFrameworkProfile))
.Last(a => (a.TargetFramework.Version.Major.Equals(targetFwVersion.Major) && a.TargetFramework.Version.Minor.Equals(targetFwVersion.Minor)) ||
a.TargetFramework.Version.Major.Equals(targetFwVersion.Major));
DownloadNugetPackage(packageInfos.Id, packageRepositoryUri, packagesFolderPath, packageInfos.Version.ToFullString());
string dllAbsolutePath = Path.GetFullPath($"{packagesFolderPath}\\{packageInfos.GetFullName().Replace(' ', '.')}\\{assemblyReference.Path}");
var assemblyInfos = Assembly.LoadFile(dllAbsolutePath);
includeAttribute.Value = $"{assemblyInfos.FullName}, processorArchitecture=MSIL";
referenceNode.Attributes.Append(includeAttribute);
XmlNode hintPathNode = CsprojDoc.CreateNode(XmlNodeType.Element, "HintPath", XmlNamespaceValue);
XmlNode privateNode = CsprojDoc.CreateNode(XmlNodeType.Element, "Private", XmlNamespaceValue);
hintPathNode.InnerXml = $"$(SolutionDir)\\packages\\{assemblyReference.Path}";
privateNode.InnerXml = "True";
referenceNode.AppendChild(hintPathNode);
referenceNode.AppendChild(privateNode);
var itemGroupNode = CsprojDoc.SelectSingleNode("//x:Project/x:ItemGroup/x:Reference", Nsmgr).ParentNode;
itemGroupNode.AppendChild(referenceNode);
Here's my DownloadNugetPackage method:
private static void DownloadNugetPackage(string packageId, Uri repoUri, string packagesFolderPath, string version)
{
IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(repoUri.ToString());
PackageManager packageManager = new PackageManager(packageRepository, packagesFolderPath);
packageManager.InstallPackage(packageId, SemanticVersion.Parse(version));
}
My GetTargetFrameworkFromCsproj
public static TargetFrameworkAttribute GetTargetFrameworkFromCsproj()
{
XmlNode targetFrameworkNode = CsprojDoc.SelectSingleNode("//x:TargetFrameworkVersion", Nsmgr);
return new TargetFrameworkAttribute($".NETFramework, Version={targetFrameworkNode.InnerXml}");
}
And my GetNugetPackage method:
public static IPackage GetNugetPackage(string packageId, Uri repoUri, string version = null)
{
IPackageRepository packageRepository = PackageRepositoryFactory.Default.CreateRepository(repoUri.ToString());
IPackage package;
if (!string.IsNullOrEmpty(version))
{
package = packageRepository.FindPackagesById(packageId).SingleOrDefault(p => p.Version.ToFullString().Equals(version));
}
else
{
package = packageRepository.FindPackagesById(packageId).SingleOrDefault(p => p.IsLatestVersion);
}
return package;
}
Note: This time, i'm using the official NuGet package: Nuget.core 2.14: https://www.nuget.org/packages/NuGet.Core/
Note 2: When I add a new Reference node, in the processorArchitecture attribute, I've hard coded the value: MSIL
If you want to test it, you might tweak it a bit.
This work fine for me.
(Assuming you are writing the code generator on your own)
I am not sure this is the best solution but you could try something like this:
var assembly = Assembly.LoadFile("path_to_dll");
string info = assembly.FullName; // contains something like "AssemblyName, Version=0.0.0.0, Culture=neutral, PublicKeyToken=foobar"
which will add some overhead by loading the assembly, but will presumably do the job.

How can I automatically verify that all web.config transforms have the same elements?

I'm working on an ASP.NET project that has several web.config transformations that are generated at build time (all at once, using MsBuild); one .config file for each deployment environment.
EX:
<Target Name="BeforeBuild">
<TransformXml
Source="Web.Base.config"
Transform="Web.DevServer1.config"
Destination="ConfigBuild\Web.DevServer1.config" />
<TransformXml
Source="Web.Base.config"
Transform="Web.QAServer1.config"
Destination="ConfigBuild\Web.QAServer1.config" />
<!-- ... -->
<!-- ... -->
</Target>
Each transformation has several elements whose values are substituted into the base web.config file. Management and I are concerned that a necessary element could potentially be overlooked by mistake in one of the transformation files.
Is there a way to automatically verify that none of our transformation files are missing any elements, using either MsBuild or some other VisualStudio tool?
Preferably, this check would be carried out at build time.
Is there a way to automatically verify that none of our transformation files are missing any elements, using either MsBuild or some other VisualStudio tool?
You can create a custom task, which compare the config file by using XmlDocument class, if they are different, use Log.LogMessage to output the node message. Like this:
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Xml;
namespace Common
{
public class SimpleTask3 : Task
{
private string myProperty;
// The [Required] attribute indicates a required property.
// If a project file invokes this task without passing a value
// to this property, the build will fail immediately.
[Required]
public string MyProperty
{
get
{
return myProperty;
}
set
{
myProperty = value;
}
}
public override bool Execute()
{
// Log a high-importance comment
Log.LogMessage(MessageImportance.High, "The task was passed \"" + myProperty + "\"");
XmlDocument xDoc = new XmlDocument();
xDoc.Load(myProperty + "/web.base.config");
XmlDocument sDoc = new XmlDocument();
sDoc.Load(myProperty + "/ConfigBuild/Web.DevServer1.config");
//compare with them and check the different.
//if different
Log.LogMessage(MessageImportance.High, "different message");
return true;
}
}
}
Web.config:
<UsingTask TaskName="TransformXml" AssemblyFile="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll" />
<Target Name="BeforeBuild">
<!--<Message Text="BuildDependsOn: $(BuildDependsOn)" />-->
<Message Text="Inside of BeforeBuild, time: $([System.DateTime]::Now)" />
<TransformXml Source="D:\Project\Msbuild\App1\App2\Web.Base.config" Transform="D:\Project\Msbuild\App1\App2\Web.DevServer1.config" Destination="D:\Project\Msbuild\App1\App2\ConfigBuild\Web.DevServer1.config" />
<TransformXml Source="D:\Project\Msbuild\App1\App2\Web.Base.config" Transform="D:\Project\Msbuild\App1\App2\Web.QAServer1.config" Destination="D:\Project\Msbuild\App1\App2\ConfigBuild\Web.QAServer1.config" />
</Target>
<UsingTask TaskName="Common.SimpleTask3"
AssemblyFile="D:\Project\Msbuild\App1\Common\bin\Debug\Common.dll"/>
<Target Name="AfterBuild">
<SimpleTask3 MyProperty="D:\Project\Msbuild\App1\App2"/>
</Target>

Release DLL after creation in BuildManager

I have written an C# WPF Application to compile code during runtime. The application does basically the following steps
click button [Compile stuff]
Create code files via StreamWriter
Build code files using the Microsoft.Build.Execution.BuildManager class
Uses reflection to access the DLL file (Assembly.LoadFrom(filePath))
creates an instanz of the class contained in the dll (assembly.CreateInstance(NamespaceName + "." + ClassName))
I works fine, but only once (I need to restart the application to do it again)
This is what happens during the next execution
click button [Compile stuff]
Create code files via StreamWriter
Build code files using the Microsoft.Build.Execution.BuildManager class
->Produces an error saying that the DLL file is locked.
The process cannot access the file 'DLL\generatedflexform.dll' because
it is being used by another process
The problem doesn't occur when I leave out step 2 because then the code files are the same. Therefore the BuildManager doesn't recreate/copy the dll.
I need to figure out how to release the DLL after the BuildManager has done his job. This is because the code files are likely to change very often and otherwise i have to close and re-open the application for every code change.
EDIT: My first thought was that the BuildManager causes the locking but this is not the case.
I rather think the locking happens when I try to load the DLL. I will try the Shadow Copy think (as mentioned by #granadaCoder).
private Window LoadWindowFromDll(string filePathToDll)
{
var assembly = Assembly.LoadFrom(filePathToDll);
var window = assembly.CreateInstance(NamespaceName + "." + ClassName) as Window;
return window;
}
Solution:
I had to change the LoadWindowFromDllmethod to avoid the DLL locking
private Window LoadWindowFromDll(string filePathToDll)
{
byte[] readAllBytes = File.ReadAllBytes(filePathToDll);
Assembly assembly = Assembly.Load(readAllBytes);
var window = assembly.CreateInstance(NamespaceName + "." + ClassName) as Window;
return window;
}
But somehow the pdb file was locked which causes the build to failed when I try to execute it twice.
I fixed this behavior by adding one line to my build file:
<DebugType>none</DebugType>
Here is the complete build file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<AssemblyName>generatedflexform</AssemblyName>
<OutputPath>DLL\</OutputPath>
<OutputType>Library</OutputType>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Page Include="MyForm.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="MyForm.xaml.cs">
<DependentUpon>MyForm.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
And here comes the method which does the compile magic>
public Window BuildXamlWindowFromStrings(string xaml, string codeBehind)
{
//Erstellen der Codefiles (XAML und CodeBehind)
this.CreateCodeFile(codeBehind);
this.CreateXamlFile(xaml);
//Erstellen der project file
this.CreateProjectFile();
//Build der DLL
//using (var buildManager = BuildManager.DefaultBuildManager)
using (var buildManager = new BuildManager())
{
var result = buildManager.Build(this.CreateBuildParameters(), this.CreateBuildRequest());
if (result.OverallResult == BuildResultCode.Success)
{
return this.LoadWindowFromDll(FolderPath + DllRelativeFilePath + NamespaceName + DllFileExtension);
}
}
//Error handling
var stringbuilder = new StringBuilder();
using (var reader = new StreamReader(DebuggerLogFileName))
{
stringbuilder.Append(reader.ReadToEnd());
}
throw new CompilerException(stringbuilder.ToString());
}
Helper Methods:
private BuildParameters CreateBuildParameters()
{
var projectCollection = new ProjectCollection();
var buildLogger = new FileLogger { Verbosity = LoggerVerbosity.Detailed, Parameters = "logfile=" + DebuggerLogFileName };
var buildParameters = new BuildParameters(projectCollection) { Loggers = new List<ILogger>() { buildLogger } };
return buildParameters;
}
private BuildRequestData CreateBuildRequest()
{
var globalProperties = new Dictionary<string, string>();
var buildRequest = new BuildRequestData(FolderPath + ProjectFileName, globalProperties, null,
new string[] { "Build" }, null, BuildRequestDataFlags.ReplaceExistingProjectInstance);
return buildRequest;
}
Check out "Shadow Copying".
Shadow copying enables assemblies that are used in an application domain to be updated without unloading the application domain. This is particularly useful for applications that must be available continuously.
http://msdn.microsoft.com/en-us/library/ms404279.aspx
See also:
http://gotchahunter.net/2010/12/net-how-do-you-load-an-assembly-programmatically-and-avoid-a-file-lock/
and
http://blogs.msdn.com/b/junfeng/archive/2004/02/09/69919.aspx

Migrate Microsoft.Build.BuildEngine.Engine to Microsoft.Build.Evaluation.ProjectCollection

after Microsoft marked the BuildEngine.Engine and BuildEngine.Project as obsolete i have tried to use the new proposal from Microsoft how you can see it underneath. But i have no idea where i can integrate the xmlprojectfile. Is here someone who knows the solution of this problem?
The XML project file content
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectToBuild Include ="myproject.csproj" />
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectToBuild)"
Properties="Configuration=Debug" StopOnFirstFailure="true" />
</Target>
</Project>
The old and working version (but obsolete)
Microsoft.Build.BuildEngine.Engine engine = new Microsoft.Build.BuildEngine.Engine();
engine.DefaultToolsVersion = "4.0";
engine.RegisterLogger(new ConsoleLogger());
Microsoft.Build.BuildEngine.Project project = new Microsoft.Build.BuildEngine.Project(engine);
project.Load(xmlprojectfile);
if (!project.Build())
{
Console.ReadLine();
}
The new not working verison
Microsoft.Build.Evaluation.ProjectCollection collection = new Microsoft.Build.Evaluation.ProjectCollection();
collection.DefaultToolsVersion = "4.0";
collection.RegisterLogger(new ConsoleLogger());
Microsoft.Build.Evaluation.Project project = new Microsoft.Build.Evaluation.Project(collection);
if (!project.Build())
{
Console.ReadLine();
}
You also need to create a Microsoft.Build.Execution.BuildRequestData object containing the XML project file and a dictionary of std properties like Configuration and Platform. Then you create a Microsoft.Build.Execution.BuildParameters object containing your ProjectCollection object from your code snippet and pass that off to the default Microsoft.Build.Execution.BuildManager.
powershell pseudo code:
#set build properties
$props = new-object "System.Collections.Generic.Dictionary``2[[System.String],[System.String]]"
$props.Add("Configuration",$config)
$props.Add("Platform",$platform)
#create the projectCollection
$projectCollection = new-object Microsoft.Build.Evaluation.ProjectCollection -argumentList $props,$loggers,"ConfigurationFile,Registry"
$request = new-object Microsoft.Build.Execution.BuildRequestData -argumentlist $project,$props,$null,$targets,$null
#create a BuildParameters object to hold the Project Collection
$parameters = new-object Microsoft.Build.Execution.BuildParameters -argumentlist #($projectCollection)
$parameters.MaxNodeCount = 1
$parameters.Loggers = $projectCollection.Loggers
$parameters.ToolsetDefinitionLocations = "ConfigurationFile,Registry"
$parameters.DefaultToolsVersion = $toolsVersion
#get the build manager and submit a build request with the appropriate parameters
$manager = [Microsoft.Build.Execution.BuildManager]::DefaultBuildManager
$result = $manager.Build($parameters, $request)
You are missing project.Load(xmlprojectfile); line in your migrated code. You have to add xmlprojectfile into projectcollection somehow, I guess.
Instead of creating a new Project with new keyword:
// var project = new Project(collection);
Use this:
var project = collection.LoadProject("myproject.csproj")

Categories

Resources