Enumerate Files in a .csproj (dotnet core) - c#

I am working on a build tool in C# that should work with Visual Studio / MSBuild. I want to enumerate all files that are part of a C# project. The project format is the new (.NET Core) .csproj.
The documentation describing the Project System points at using MSBuild (file format) or Common Project System (project tree). I'm unfamiliar with both APIs. Looking at documentation for those respective projects is not immediately helpful.
As the expert probably knows, the new .csproj file does not list every file that is implicitly part of the project. On the other hand it may list a 'linked' file that is outside the project folder. I want to make sure I get all files that are considered part of the project.
Ultimately I want to focus on a particular file type (.json), but I thought the general question was worth asking.
To sum up: How can I write a C# library that leverages the appropriate packages to (hopefully easily) enumerate all the files in a csproj?

Buildalyzer is the easiest package to use, and it targets .NETStandard 2.0 making it cross-platform. (Omnisharp does not currently offer a NuGet package for working with the workspace. And Microsoft.CodeAnalysis poses a challenge to get the correct references in place, and is limited to net46.)
using Buildalyzer;
private static IList<string> InlcudedProjectKeys = new[] { "None", "Compile", "Content", "EmbeddedResource" };
private static IEnumerable<string> EnumerateProjectFiles(string projectPath)
{
AnalyzerManager manager = new AnalyzerManager();
ProjectAnalyzer analyzer = manager.GetProject(projectPath);
AnalyzerResults results = analyzer.Build();
AnalyzerResult result = results.Single();
// If only interested in C# files, check out:
//string[] sourceFiles = result.SourceFiles;
IReadOnlyDictionary<string, ProjectItem[]> items = result.Items;
foreach (var item in items)
{
// Skip keys like ProjectReference that aren't for files
if (!InlcudedProjectKeys.Contains(item.Key))
continue;
ProjectItem[] projectItems = item.Value;
foreach (var projectItem in projectItems)
{
// The item spec for files will be the path relative to the project directory
yield return projectItem.ItemSpec;
}
}
}
And for bonus points, to get only *.json files:
var jsonFiles = EnumerateProjectFiles(projectPath)
.Where(path => path.EndsWith(".json"))
.ToArray();
Thanks Hitesh for linking to relevant resources.

You can use Roslyn Analyzer Libraries to load csproject and access it's content as well as properties in program. you can follow instructions from this previous SO post, or use OpenProjectAsync(projectFilePath) method to load instance of Project class in Microsoft.CodeAnalysis namespace.
using Microsoft.CodeAnalysis.MSBuild;
AnalyzerManager manager = new AnalyzerManager();
ProjectAnalyzer analyzer = manager.GetProject(#"C:\MyCode\MyProject.csproj");
You can find more information on roslyn at Github.

Related

CefSharp.offscreen in LinqPad

LinqPad is my goto REPL and there isn't much I throw at it that it cant handle.
However I cannot for the life of me get CefSharp (specifically OffScreen) to run.
I'm constantly met with either of the below errors
Could not load file or assembly 'CefSharp.Core.Runtime, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138' or one of its dependencies. The system cannot find the file specified.
BadImageFormatException: Could not load file or assembly 'CefSharp.Core.Runtime, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138'. An attempt was made to load a program with an incorrect format.
I have tried
LP5/6 32 and 64 bit
Adding Cefsharp via nuget
Referencing .dll's manually from the file system
Referencing x86 or x64 .dll's
Copying .dll's into assembly search paths
Adding nuget paths to Environment path
And what seems like every combination of the above.
I don't understand the assembly resolution process that Visual Studio uses with the nuget package, but whatever it does I would like to at least simulate in Linqpad so I can avoid the VS ceremony when testing something simple.
I assume that manually referencing the correct .dll's and maybe setting a path somewhere should be sufficient, but I'm ideas=>EOF.
Can CefSharp be run outside of VS / MSBuild ?
It doesn't work because of the shadow-copying that LinqPad is using. Here is a hack to make your problem go away (spoiler alert: not really, read on):
For LinqPad v5
Copy all CefSharp libraries to a separate folder (don't forget cef.redist).
In LinqPad Preferences dialog (Advanced/Execution), set Do not shadow assembly references to True, restart LinqPad.
Write your code in the LinqPad query.
Reference CefSharp libraries from the folder you've set up on step 1.
Run the query.
For previous LinqPad (earlier than v5)
Write your code in the LinqPad query.
Reference CefSharp libraries, so you get an exception from your question
Find a LinqPad working directory (usually something like C:\Users\<user>\AppData\Local\Temp\LINQPad5\_yyigmhzg).
Copy all CefSharp libraries to this folder (don't forget cef.redist).
In LinqPad, click Ctrl + Shift + F5; this will reset the query state.
Rerun the query.
Now all the referenced libraries should load. But you will likely face more problems after that.
I couldn't make CefSharp.MinimalExample work. LinqPad kept crashing for me with the cryptic message Query ended because an uncatchable exception was thrown and a crashdump.
Although I am not sure if you will make CefSharp work as intended under LinqPad, maybe this can get you a bit further.
Found the answer with motivation from #Sasha's post and #amaitland's note about BadImageFormatException's being more than just incorrect architectures.
The below is all in reference to LP6 and CefSharp.Offscreen.NetCore. I have not pushed the efforts into LP5 but the process should be similar.
After some trial and error I narrowed down all of the necessary dependencies and worked out why CefSharp would not run in LinqPad.
Here are the steps to make it run -
Add CefSharp.Offscreen.NetCore package as normal to query
Enable Copy all NuGet assemblies into a single local folder (F4->Advanced)
Add the OnInit() and queryPath code as below to the query
Ensure the BrowserSubprocessPath is set before Initializing Cef
Here is the code.
async Task Main()
{
var are = new AutoResetEvent(false);//my technique for waiting for the browser
var sett = new CefSettings();
sett.BrowserSubprocessPath = this.queryPath + #"\CefSharp.BrowserSubprocess.exe"; //CefSharp will complain it cant find it
if (!Cef.IsInitialized)
Cef.Initialize(sett);
var browser = new ChromiumWebBrowser("http://www.google.com");
browser.LoadingStateChanged += (sender, args) => { if (!args.IsLoading) are.Set(); };
are.WaitOne();
await browser.WaitForInitialLoadAsync();
var html = await browser.GetBrowser().MainFrame.GetSourceAsync();
html.Dump("winner winner chicken dinner");
}
//this is the location of the queries shaddow folder
string queryPath = Path.GetDirectoryName(typeof(CefSettings).Assembly.Location);
void OnInit() // Executes when the query process first loads
{
if (!Directory.Exists(queryPath + #"\locales")) //subdirectory of cef.redist
{
var nugetPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var sources = new[] {
/*paths here are hardcoded version dependant. Can get cefsharp.common.netcore version
from Util.CurrentQuery.NuGetReferences, but cef.redist not available via that method. */
#"cef.redist.x64\95.7.14\CEF", //contans all the Cef dependencies needed
#"cefsharp.common.netcore\95.7.141\runtimes\win-x64\lib\netcoreapp3.1", //mainly for ijwhost.dll
#"cefsharp.common.netcore\95.7.141\runtimes\win-x64\native"}; //contains BrowserSubprocess & friends
var dst = new DirectoryInfo(queryPath);
foreach (var path in sources)
{
var src = new DirectoryInfo($#"{nugetPath}\.nuget\packages\{path}");
CopyFilesRecursively(src, dst);
}
}
}
//curtesy of https://stackoverflow.com/a/58779/2738249 with slight mod
public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
foreach (DirectoryInfo dir in source.GetDirectories())
CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
foreach (FileInfo file in source.GetFiles())
{
var dst = Path.Combine(target.FullName, file.Name);
if (!File.Exists(dst))
file.CopyTo(dst);
}
}
The why for those interested -
CefSharp needs every dependency to be in the same directory so they can be resolved at runtime, but Linqpad only copies a few key dll's from the NuGet package. None of the cef.redist files, ijwhost.dll or BrowserSubprocess.exe et al. come across. Dependencies are scattered between NuGet packages and trying to resolve them directly from the .nuget cache just does not work. So all these need to be brought in manually to the running query shadow path.
I did initially copy all files into the Assembly.GetExecutingAssembly().Location path, but this approach requires adding the assembly directory to the "path" environment variable.
Internally Linqpad seems to have the shadow path set, so copying the dependencies to the shadow folder skips the need to set the environment variable.

How can I use an SLN file with MSBuild programmatically from C#?

I am trying to create a custom MSBuild script in C#, using the newer Microsoft.Build.Evaluation API. The problem I have is that this newer API does not support .sln files. The older deprecated Microsoft.Build.Engine API does support .sln files, but I'd like to use the newer one because 1) it's not deprecated and 2) there seems to be more online documentation and usage to reference. I've seen that MSBuild can create a .metaproj file when is successfully compiles a solution, when this assignment is made in CMD: set MSBuildEmitSolution=1. I need the .metaproj file to be able to compile the solution in the first place. Is there anything in the API for converting .sln to .metaproj? Is there any library out there for parsing .sln files?
I figured it out after more searching. Finding good examples online is a little difficult because of the two different versions of the MSBuild API, and the popularity of just running MSBuild from the command line.
Here is the code that is now working for me, using the newer MSBuild API:
var pc = new ProjectCollection();
var parameters = new BuildParameters(pc)
{
Loggers = new[] { _logger } //Instance of ILogger instantiated earlier
};
var request = new BuildRequestData(
projectFullPath: pathToMySlnFile, //Solution file path
globalProperties: myPropertyDictionary,
toolsVersion: null,
targetsToBuild: myTargetsArray,
hostServices: null,
flags: BuildRequestDataFlags.ProvideProjectStateAfterBuild);
var buildResult = BuildManager.DefaultBuildManager.Build(parameters, request);

Opening a solution with msbuildworkspace gives diagnostics errors without details

I am trying to analyse a solution with Roslyn, with MSBuildWorkspace.
The solution is a new solution, with 2 class library projects in them, one referencing the other.
They are created in Visual Studio 2017, .Net 4.6.2.
When I open the solution, I receive two generic errors in workspace.Diagnostics, both are :
Msbuild failed when processing the file 'PathToProject'
There is nothing more in the diagnostics or output window, to indicate WHY it failed to process the project file.
The code for opening the solution:
namespace RoslynAnalyse
{
class Program
{
static void Main(string[] args)
{
LocalAnalysis();
}
private static void LocalAnalysis()
{
var workspace = MSBuildWorkspace.Create();
var solution = workspace.OpenSolutionAsync(#"D:\Code\Roslyn\RoslynAnalyse\SolutionToAnalyse\SolutionToAnalyse.sln").Result;
var workspaceDiagnostics = workspace.Diagnostics;
}
}
}
The version of Microsoft.CodeAnalysis is 2.0.0.0.
Does anybody have any idea why MSBuild failed, how I can get more information ?
When MSBuildWorkspace fails to open a project or solution this way, it is almost always because the application using MSBuildWorkspace does not include the same binding redirects that msbuild.exe.config has in it.
MSBuild uses binding redirects to allow tasks (typically already compiled C# code using possibly different versions of msbuild API libraries) to all use the current msbuild API's. Otherwise, msbuild gets runtime load failures.
The solution is to add an app.config file to your project and copy the binding redirects (the assemblyBinding section of the msbuild.exe.config file) into your file.

Code Contracts and Programmatically compiling projects

I have a bunch of legacy web projects that are compiled using a custom build application that we wrote. This because the depedencies were complex and the code... is... less than optimal. On the plus side it has recently been upgraded to .NET 4.5 and we are using Visual Studio 2013.
I recently started using code contracts in one of our other projects and really like the SoC it provides. I want to implement this in the legacy code that we are maintaining for new features (without doing a complete re-write). In development I have gotten it to work as long as I do a build from within VS and copy the compiled DLL files into the BIN folder of the web application (aps.net 32bit latest IIS).
I want to include the contracts in our release build as well using our Build tool. Otherwise I would have to use the tool and then use VS to create a release which is a 2 step process. I have complete source control over the tool (which is executed locally on my DEV PC) but I cannot get it to produce output with the code contracts. I read the Code Contract documentation and they mention the build script Microsoft.CodeContracts.targets and I have tried to add this into the build code without success. Admittedly my knowledge of the build process, build scripts, etc. is lacking at best. Any help / pointers in how to get this to work would be greatly appreciated. I would still like to normally compile my code in VS and have contracts work as well as use the build tool and and contracts work.
(i have not manually changed any of the CSPROJ files)
Build Code:
Build code is done using Microsoft.Build and Microsoft.Build.Framework assemblies.
var pc = new ProjectCollection();
var buildLogger = new FileLogger();
var logFilePath = #"MyLog.log";
buildLogger.Parameters = string.Format("logfile={0}", logFilePath);
var binDirectory = Path.Combine(outputWebDir, "Bin");
var globalProperty = new Dictionary<string, string>();
globalProperty.Add("OutputPath", binDirectory);
globalProperty.Add("Configuration", publishParams.ReleaseMode);
globalProperty.Add("Platform", publishParams.PlatformMode);
if (projectFileToPublish.IndexOf("SOTAQ.WebPoint.Web.csproj", StringComparison.OrdinalIgnoreCase) < 0)
globalProperty.Add("SolutionDir", publishParams.SparcoSolutionPath);
if (isWebSite)
{
globalProperty.Add("WebProjectOutputDir", outputWebDir);
globalProperty.Add("DeployOnBuild", "True");
}
globalProperty.Add("CodeContractsInstallDir", #"C:\Program Files (x86)\Microsoft\Contracts\");
globalProperty.Add("CodeContractRewriteCommand", #"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccrewrite.exe");
globalProperty.Add("CodeContractAnalysisTargets", #"C:\Program Files (x86)\Microsoft\Contracts\MsBuild\v4.0\Microsoft.CodeContractAnalysis.targets");
globalProperty.Add("CodeContractsCCDocgenCommand", #"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccdocgen.exe");
globalProperty.Add("CodeContractsCCRefgenCommand", #"C:\Program Files (x86)\Microsoft\Contracts\Bin\ccrefgen.exe");
string[] targets;
if (isWebSite)
targets= new[] { "Build", "ResolveReferences", "_CopyWebApplication" };
else
targets = new[] { "Build", "ResolveReferences" };
var buildRequestData = new BuildRequestData(projectFileToPublish, globalProperty, null, targets, null);
var buildParams = new BuildParameters(pc);
buildParams.Loggers = new[] {buildLogger};
BuildResult buildResult = BuildManager.DefaultBuildManager.Build(buildParams, buildRequestData);
Any help is greatly appreciated!!
Thanks in advance,
-Igor
After reading and re-reading the diagnostic output from Visual Studio and comparing it to my build project and trying many various possible solutions I found online the answer was to add the variable CodeContractsEnableRuntimeChecking with value true.
globalProperty.Add("CodeContractsEnableRuntimeChecking", "true");
Once that was added everything worked as expected.
-Igor

Merge DLL into EXE?

I have two DLL files which I'd like to include in my EXE file to make it easier to distribute it. I've read a bit here and there how to do this, even found a good thread here, and here, but it's far too complicated for me and I need real basic instructions on how to do this.
I'm using Microsoft Visual C# Express 2010, and please excuse my "low standard" question, but I feel like I'm one or two level below everyone else's expercise :-/ If someone could point out how to merge these DDL files into my EXE in a step-by-step guide, this would be really awesome!
For .NET Framework 4.5
ILMerge.exe /target:winexe /targetplatform:"v4,C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0" /out:finish.exe insert1.exe insert2.dll
ILMerge
Open CMD and cd to your directory. Let's say: cd C:\test
Insert the above code.
/out:finish.exe replace finish.exe with any filename you want.
Behind the /out:finish.exe you have to give the files you want to be
combined.
Use Costura.Fody.
You just have to install the nuget and then do a build. The final executable will be standalone.
Download ilmerge and ilmergre gui . makes joining the files so easy
ive used these and works great
Reference the DLL´s to your Resources and and use the AssemblyResolve-Event to return the Resource-DLL.
public partial class App : Application
{
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
Assembly thisAssembly = Assembly.GetExecutingAssembly();
//Get the Name of the AssemblyFile
var name = args.Name.Substring(0, args.Name.IndexOf(',')) + ".dll";
//Load form Embedded Resources - This Function is not called if the Assembly is in the Application Folder
var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(name));
if (resources.Count() > 0)
{
var resourceName = resources.First();
using (Stream stream = thisAssembly.GetManifestResourceStream(resourceName))
{
if (stream == null) return null;
var block = new byte[stream.Length];
stream.Read(block, 0, block.Length);
return Assembly.Load(block);
}
}
return null;
};
}
}
Download
ILMerge
Call
ilmerge /target:winexe /out:c:\output.exe c:\input.exe C:\input.dll
Install ILMerge
as the other threads tell you to
Then go to the installation folder, by default
C:\Program Files (x86)\Microsoft\ILMerge
Drag your Dll's and Exes to that folder
Shift-Rightclick in that folder and choose open command prompt
Write
ilmerge myExe.exe Dll1.dll /out:merged.exe
Note that you should write your exe first.
There you got your merged exe. This might not be the best way if your going to
do this multiple times, but the simplest one for a one time use, I would
recommend putting Ilmerge to your path.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
/* PUT THIS LINE IN YOUR CLASS PROGRAM MAIN() */
AppDomain.CurrentDomain.AssemblyResolve += (sender, arg) => { if (arg.Name.StartsWith("YOURDLL")) return Assembly.Load(Properties.Resources.YOURDLL); return null; };
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
First add the DLL´s to your project-Resources. Add a folder "Resources"
2019 Update (just for reference):
Starting with .NET Core 3.0, this feature is supported out of the box. To take advantage of the single-file executable publishing, just add the following line to the project configuration file:
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
Now, dotnet publish should produce a single .exe file without using any external tool.
More documentation for this feature is available at https://github.com/dotnet/designs/blob/master/accepted/single-file/design.md.
Also you can use ilmergertool at codeplex with GUI interface.
Here is the official documentation. This is also automatically downloaded at step 2.
Below is a really simple way to do it and I've successfully built my app using .NET framework 4.6.1
Install ILMerge nuget package either via gui or commandline:
Install-Package ilmerge
Verify you have downloaded it. Now Install (not sure the command for this, but just go to your nuget packages):
Note: You probably only need to install it for one of your solutions if you have multiple
Navigate to your solution folder and in the packages folder you should see 'ILMerge' with an executable:
\FindMyiPhone-master\FindMyiPhone-master\packages\ILMerge.2.14.1208\tools
Now here is the executable which you could copy over to your \bin\Debug (or whereever your app is built) and then in commandline/powershell do something like below:
ILMerge.exe myExecutable.exe myDll1.dll myDll2.dll myDlln.dll myNEWExecutable.exe
You will now have a new executable with all your libraries in one!
I answered a similar question for VB.NET. It shouldn't however be too hard to convert. You embedd the DLL's into your Ressource folder and on the first usage, the
AppDomain.CurrentDomain.AssemblyResolve event gets fired.
If you want to reference it during development, just add a normal DLL reference to your project.
Embedd a DLL into a project
NOTE: if you're trying to load a non-ILOnly assembly, then
Assembly.Load(block)
won't work, and an exception will be thrown:
more details
I overcame this by creating a temporary file, and using
Assembly.LoadFile(dllFile)
I Found The Solution Below are the Stpes:-
Download ILMerge.msi and Install it on your Machine.
Open Command Prompt
type cd C:\Program Files (x86)\Microsoft\ILMerge Preess Enter
C:\Program Files (x86)\Microsoft\ILMerge>ILMerge.exe /target:winexe /targetplatform:"v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319"
/out:NewExeName.exe SourceExeName.exe DllName.dll
For Multiple Dll :-
C:\Program Files (x86)\Microsoft\ILMerge>ILMerge.exe /target:winexe /targetplatform:"v4,C:\Windows\Microsoft.NET\Framework\v4.0.30319"
/out:NewExeName.exe SourceExeName.exe DllName1.dll DllName2.dll DllName3.dll
The command should be the following script:
ilmerge myExe.exe Dll1.dll /target:winexe /targetplatform:"v4,c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\" /out:merged.exe /out:merged.exe

Categories