Roslyn: workspace loads in console application but not in msbuild task - c#

I have a custom msbuild task with this command:
var workspace = Workspace.LoadStandAloneProject(csprojPath);
When I run it, it throws the following error:
System.InvalidCastException was unhandled by user code
Message=Unable to cast transparent proxy to type 'Roslyn.Utilities.SerializableDataStorage'.
Source=Roslyn.Services
StackTrace:
at Roslyn.Utilities.RemoteServices.CreateInstance[T]()
at Roslyn.Services.Host.TemporaryStorageServiceFactory.CreateService(IWorkspaceServiceProvider workspaceServices)
at Roslyn.Services.Host.WorkspaceServiceProviderFactory.Provider.c__DisplayClass7.b__4()
at Roslyn.Utilities.NonReentrantLazy`1.get_Value()
at Roslyn.Services.Host.WorkspaceServiceProviderFactory.Provider.GetService[TWorkspaceService]()
at Roslyn.Services.SolutionServices..ctor(IWorkspaceServiceProvider workspaceServices, ILanguageServiceProviderFactory languageServicesFactory)
at Roslyn.Services.Solution..ctor(SolutionId id, String filePath, VersionStamp version, VersionStamp latestProjectVersion, ILanguageServiceProviderFactory languageServiceProviderFactory, IWorkspaceServiceProvider workspaceServices)
at Roslyn.Services.Host.SolutionFactoryServiceFactory.SolutionFactoryService.CreateSolution(SolutionId id)
at Roslyn.Services.Host.TrackingWorkspace.CreateNewSolution(ISolutionFactoryService solutionFactory, SolutionId id)
at Roslyn.Services.Host.TrackingWorkspace..ctor(IWorkspaceServiceProvider workspaceServiceProvider, Boolean enableBackgroundCompilation, Boolean enableInProgressSolutions)
at Roslyn.Services.Host.HostWorkspace..ctor(IWorkspaceServiceProvider workspaceServiceProvider, Boolean enableBackgroundCompilation, Boolean enableInProgressSolutions, Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace..ctor(ILanguageServiceProviderFactory languageServiceProviderFactory, IWorkspaceServiceProvider workspaceServiceProvider, IProjectFileService projectFileFactsService, IDictionary`2 globalProperties, Boolean enableBackgroundCompilation, Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace..ctor(ExportProvider exportProvider, Boolean solutionLoadOnly, Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace..ctor(Boolean enableFileTracking)
at Roslyn.Services.Host.LoadedWorkspace.LoadStandAloneProject(String projectFileName, String configuration, String platform, String language, Boolean enableFileTracking)
at Roslyn.Services.Workspace.LoadStandAloneProject(String projectFileName, String configuration, String platform, String language, Boolean enableFileTracking)
...
The same code, when run in a console application, with the same project, runs fine.
Any ideas? Googling has not been helpful!

Here's a sample MsBuild task with Roslyn.
In order to reconstruct the command line needed by the Workspace.LoadProjectFromCommandLineArguments method, we have to pass some info from the msbuild file into our task.
The referenced assemblies: the
#(ReferencePath) item group.
The cs files to be compiled: the #(Compile) item group.
The base directory: the $(MSBuildProjectDirectory) built-in property.
That's all that Roslyn needs to parse your source files. (See the note at the end of this post.)
So create a C# class library project.
These are the project references that you'll need:
Microsoft.Build.Framework
Microsoft.Build.Utilities.v4.0
Roslyn.Compilers
Roslyn.Services
The code for the custom MsBuild task:
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Roslyn.Services;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace RoslynMsBuildTask
{
public class RoslynTask : Task
{
[Required]
public ITaskItem[] ReferencePath { get; set; }
[Required]
public ITaskItem[] Compile { get; set; }
[Required]
public ITaskItem BaseDirectory { get; set; }
public override bool Execute()
{
Log.LogMessage(MessageImportance.High, "RoslynTask.Execute called...\n");
// Format the command line with the minimal info needed for Roslyn to create a workspace.
var commandLineForProject = string.Format("/reference:{0} {1}",
ReferencePath.Select(i => i.ItemSpec).ToSingleString(",", "\"", "\""),
Compile.Select(i => i.ItemSpec).ToSingleString(" ", "\"", "\""));
// Create the Roslyn workspace.
var workspace = Workspace.LoadProjectFromCommandLineArguments("MyProject", "C#", commandLineForProject, BaseDirectory.ItemSpec);
// Make sure that Roslyn actually parsed the project: dump the source from a syntax tree to the build log.
Log.LogMessage(MessageImportance.High, workspace.CurrentSolution.Projects.First()
.Documents.First(i => i.FilePath.EndsWith(".cs")).GetSyntaxRoot().GetText().ToString());
return true;
}
}
public static class IEnumerableExtension
{
public static string ToSingleString<T>(this IEnumerable<T> collection, string separator, string leftWrapper, string rightWrapper)
{
var stringBuilder = new StringBuilder();
foreach (var item in collection)
{
if (stringBuilder.Length > 0)
{
if (!string.IsNullOrEmpty(separator))
stringBuilder.Append(separator);
}
if (!string.IsNullOrEmpty(leftWrapper))
stringBuilder.Append(leftWrapper);
stringBuilder.Append(item.ToString());
if (!string.IsNullOrEmpty(rightWrapper))
stringBuilder.Append(rightWrapper);
}
return stringBuilder.ToString();
}
}
}
To demonstrate that it actually works, add the following lines at the end of your csproj file (just before the closing Project tag). But only if the project was already built successfully and it can find your task dll in the output folder.
<Target Name="AfterBuild" DependsOnTargets="RoslynTask"/>
<UsingTask AssemblyFile="$(OutputPath)\RoslynMsBuildTask.dll" TaskName="RoslynMsBuildTask.RoslynTask" />
<Target Name="RoslynTask">
<RoslynTask ReferencePath="#(ReferencePath)" Compile="#(Compile)" BaseDirectory="$(MSBuildProjectDirectory)" />
</Target>
It will dump the source of your first cs file to the build output.
Note that other csc.exe switches (like ConditionalDirectives, output type, etc) may also matter depending on the type of analysis you are trying to do. You can also pass them to your task using this pattern. See $(MSBuildToolsPath)\Microsoft.CSharp.targets file, CoreCompile target, Csc task for a complete list of properties that MsBuild passes to csc.exe.

This is a limitation of MSBuild. Roslyn can't invoke MSBuild recursively during a build to determine the project properties/files/references. In order to create a Roslyn IProject during in a build task, try using the LoadFromCommandLineArgs() method instead. You'll need to construct your task to take the same arguments as the CscTask ends up passing to the compiler.
Hope this helps!

Related

Creating a command line in C# that includes file path

I am trying to create a project that accepts a configuration file and 2 comparison files using a command line arguments with the paths to these files included. Would I construct this the same way you would pass any command line argument? Sorry I am new to this so I am not sure if there is an exception when trying to pass files.
Can I get an example of how this would be done? Here is a picture of the directions of what exactly I have been asked.
Accept the following command line arguments:
Configuration file (with path) (described below)
Comparison File 1 (with path)
Comparison File 2 (with path)
Take a look at the documentation of Main function arguments
Assuming this is your main function and you want to accept 3 parameters:
static int Main(string[] args)
{
// check the length of args for validation.
// args[0] -> Configuration file
// args[1] -> Comparison File 1
// args[2] -> Comparison File 2
..... DO SOMETHING...
return 0;
}
Usage (from command line or debugger):
SomeProgram.exe "ConfigFilePath" "ComparisonFile1" "ComparisonFile2".
Because I really like this nuget(No association just a fan). Here is an example of it using CommandLineUtils
First add an new project with dotnet new consol TestConsolUtils then add the nuget dotnet add package McMaster.Extensions.CommandLineUtils then copy this code to the program class.
using McMaster.Extensions.CommandLineUtils;
using System;
namespace ConsolUtilsTest
{
class Program
{
public static int Main(string[] args)
=> CommandLineApplication.Execute<Program>(args);
[Argument(0, Description = "Configuration file")]
[FileExists]
public string ConfigurationFile { get; }
[Argument(1, Description = "Comparison file 1")]
[FileExists]
public string ComparisonFile1 { get; }
[Argument(2, Description = "Comparison File 2")]
[FileExists]
public string ComparisonFile2 { get; }
private void OnExecute()
{
Console.WriteLine(ConfigurationFile);
Console.WriteLine(ComparisonFile1);
Console.WriteLine(ComparisonFile2);
}
}
}
do a dotnet build
Go to the dll folder that was just build most likely in Debug\netcoreapp2.2\
Create a fake file A.json this is required because the utility will check if the file exists.
Run it with dotnet command
dotnet TestConsolUtils.dll A.json A.json A.json
There are a lot more you can do with this utill just look at the documentation.

C# for scripting (csx) location of script file

In F# it's rather easy with predefined identifier __SOURCE_DIRECTORY__
https://stackoverflow.com/a/4861029/2583080
However this identifier does not work in C# scripting (csx files or C# Interactive).
> __SOURCE_DIRECTORY__
(1,1): error CS0103: The name '__SOURCE_DIRECTORY__' does not exist in the current context
Getting current directory in more traditional way will not work either.
Directory.GetCurrentDirectory()
Returns: C:\Users\$USER_NAME$\
new Uri(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).LocalPath;
Returns: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\CommonExtensions\Microsoft\ManagedLanguages\VBCSharp\InteractiveComponents\
In C# you can take advantage of caller information attributes (available since C# 5 / VS2012). Just declare a method like this:
string GetCurrentFileName([System.Runtime.CompilerServices.CallerFilePath] string fileName = null)
{
return fileName;
}
And call it without specifying the optional parameter:
string scriptPath = GetCurrentFileName(); // /path/to/your/script.csx
In csx, you are can add ExecutionContext as a parameter and access FunctionDirectory from it like so:
using System;
using Microsoft.Azure.WebJobs;
public static void Run(TimerInfo myTimer, ExecutionContext executionContext, ILogger log) {
var dir = executionContext.FunctionDirectory;
log.LogInformation($"Directory: {dir}");
}
ExecutionContext.FunctionDirectory will return the directory the contains the function's function.json file. It doesn't include the trailing .
At this time this seems to be the best documentation for ExecutionContext.
I am trying to find the answer to this question myself, and this was my previous answer.
In csx, the following helper method will return the directory "of the source file that contains the caller".
using System.IO;
...
public static string CallerDirectory([System.Runtime.CompilerServices.CallerFilePath] string fileName = null)
{
return Path.GetDirectoryName(fileName);
}
To call it, don't specify the fileName parameter.
var dir = CallerDirectory();

How to find the 'obj' directory for C# projects using Roslyn MSBuildWorkspace?

I'm using Roslyn to compile a C# solution with several projects in it, using the MsBuildWorkspace.
It's easy to find the output file of a Project, for that I can just use the OutputFilePath property.
But I can't find a way to figure out the 'intermediate' directory (typically this is the 'obj' directory, but this can be changed using the MSBuild properties BaseIntermediateOutputPath and IntermediateOutputPath in the csproj file).
Does anyone have an idea how to do this?
You can use Microsoft.Build.Evaluation.Project from Microsoft.Build.dll and got project properties:
void foo(String projectPath, IDictionary<String, String> globalProperties, String toolsVersion)
{
Project project = new Project(projectPath, globalProperties, toolsVersion);
String baseIntermediateOutputPath = GetProjectProperty(project, "BaseIntermediateOutputPath");
String intermediateOutputPath = GetProjectProperty(project, "IntermediateOutputPath");
// ....
}
static String GetProjectProperty(Microsoft.Build.Evaluation.Project project, String propertyName)
{
return project.Properties
.FirstOrDefault(prop => String.Equals(prop.Name, propertyName, StringComparison.OrdinalIgnoreCase))
?.EvaluatedValue;
}

Print the source filename and linenumber in C#

Is there any way to retrieve the current source filename and linenumber in C# code and print that value in the console output? Like LINE and FILE in C?
Please advise.
Many thanks
Anders Hejlsberg presented new API for that in BUILD keynote:
Print current file name, method name and line number
private static void Log(string text,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine("{0}_{1}({2}): {3}", Path.GetFileName(file), member, line, text);
}
Test:
Log(".NET rocks!");
Output:
Program.cs_Main(11): .NET rocks!
What's going on here?
You define a method with optional parameters and decorate them with special attributes. If you call method without passing actual arguments (leave defaults) - the Framework populates them for you.
This answer is outdated! See #taras' answer for more recent information.
No constant :(
What you can do is a lot uglier :
string currentFile = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileName();
int currentLine = new System.Diagnostics.StackTrace(true).GetFrame(0).GetFileLineNumber();
Works only when PDB files are available.
You can use the StackTrace object from the System.Diagnostics namespace but the information will only be available if the PDB files are there.
PDB files are generated by default for both the Debug and Release builds the only difference is that Debug is setup to generate a full debug info where as the Release build is setup to only generate a pdb (full/pdb-only).
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileName());
Console.WriteLine(new StackTrace(true).GetFrame(0).GetFileLineNumber());
There are no constants defined for that as of now.
The .NET way of doing it is using StackTrace class.
It however works only for Debug builds. So in case you use it, you can have the code using StackTrace between
#if DEBUG
//your StackTrace code here
#endif
You can read about using #if preprocessors for your DEBUG vs. RELEASE builds in the following Stackoverflow thread.
C# if/then directives for debug vs release
EDIT: Just in case you still need this debugging information in release builds, read the following answer on Stackoverflow:
Display lines number in Stack Trace for .NET assembly in Release mode
If you want some more internal detail, but you don't specifically need filename and line number, you can do something like this:
System.Diagnostics.Debug.Print(this.GetType().ToString() + " My Message");
This has an advantage over printing out the filename in that if you put this in a parent class, it will print out the child class name that is actually running the code.
If you wanted to write your own version of Debug.Assert, then here's a more complete answer:
// CC0, Public Domain
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System;
public static class Logger {
[Conditional("DEBUG")]
public static void Assert(bool condition, string msg,
[CallerFilePath] string file = "",
[CallerMemberName] string member = "",
[CallerLineNumber] int line = 0
)
{
// Debug.Assert opens a msg box and Trace only appears in
// a debugger, so implement our own.
if (!condition)
{
// Roughly follow style of C# error messages:
// > ideone.cs(14,11): error CS1585: Member modifier 'static' must precede the member type and name
Console.WriteLine($"{file}({line}): assert: in {member}: {msg}");
// Or more precisely match style with a fake error so error-parsing tools will detect it:
// Console.WriteLine($"{file}({line}): warning CS0: {msg}");
}
}
}
class Program {
static void Main(string[] args) {
Logger.Assert(1+1 == 4, "Why not!");
}
}
Try it online.

How to deploy a visual studio custom tool?

I have my own custom tool for Visual Studio 2008 SP1. It consists of 5 assemblies: 3 assemblies with code that are used heavily in my other projects, 1 assembly-wrapper above VS2008 SDK and an assembly with the tool.
If I'd debug my tool from visual studio, using "Run external program" option with command line "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe" and arguments "/ranu /rootsuffix Exp" all works perfectly.
After that I'm trying to deploy it to my working VS copy, not to experimental hive, doing: gacutil /i Asm1.dll for all my assemblies and doing RegAsm Asm1.dll only for assembly with custom tool. Neither of utils prints any error, all work as planned, even registry keys appear. But my tool doesn't work (error occurred "Cannot find custom tool 'TransportGeneratorTool' on this system") even after PC restart. What did I do wrong?
Wrapper looks like that:
[ComVisible(true)]
public abstract class CustomToolBase : IVsSingleFileGenerator, IObjectWithSite
{
#region IVsSingleFileGenerator Members
int IVsSingleFileGenerator.DefaultExtension(out string pbstrDefaultExtension)
{
pbstrDefaultExtension = ".cs";
return 0;
}
int IVsSingleFileGenerator.Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, IntPtr[] rgbOutputFileContents, out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
{
GenerationEventArgs gea = new GenerationEventArgs(
bstrInputFileContents,
wszInputFilePath,
wszDefaultNamespace,
new ServiceProvider(Site as Microsoft.VisualStudio.OLE.Interop.IServiceProvider)
.GetService(typeof(ProjectItem)) as ProjectItem,
new GenerationProgressFacade(pGenerateProgress)
);
if (OnGenerateCode != null)
{
OnGenerateCode(this, gea);
}
byte[] bytes = gea.GetOutputCodeBytes();
int outputLength = bytes.Length;
rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(outputLength);
Marshal.Copy(bytes, 0, rgbOutputFileContents[0], outputLength);
pcbOutput = (uint)outputLength;
return VSConstants.S_OK;
}
#endregion
#region IObjectWithSite Members
void IObjectWithSite.GetSite(ref Guid riid, out IntPtr ppvSite)
{
IntPtr pUnk = Marshal.GetIUnknownForObject(Site);
IntPtr intPointer = IntPtr.Zero;
Marshal.QueryInterface(pUnk, ref riid, out intPointer);
ppvSite = intPointer;
}
void IObjectWithSite.SetSite(object pUnkSite)
{
Site = pUnkSite;
}
#endregion
#region Public Members
public object Site { get; private set; }
public event EventHandler<GenerationEventArgs> OnGenerateCode;
[ComRegisterFunction]
public static void Register(Type type)
{
using (var parent = Registry.LocalMachine.OpenSubKey(#"Software\Microsoft\VisualStudio\9.0", true))
foreach (CustomToolRegistrationAttribute ourData in type.GetCustomAttributes(typeof(CustomToolRegistrationAttribute), false))
ourData.Register(x => parent.CreateSubKey(x), (x, name, value) => x.SetValue(name, value));
}
[ComUnregisterFunction]
public static void Unregister(Type type)
{
using (var parent = Registry.LocalMachine.OpenSubKey(#"Software\Microsoft\VisualStudio\9.0", true))
foreach (CustomToolRegistrationAttribute ourData in type.GetCustomAttributes(typeof(CustomToolRegistrationAttribute), false))
ourData.Unregister(x => parent.DeleteSubKey(x, false));
}
#endregion
}
My tool code:
[ComVisible(true)]
[Guid("55A6C192-D29F-4e22-84DA-DBAF314ED5C3")]
[CustomToolRegistration(ToolName, typeof(TransportGeneratorTool))]
[ProvideObject(typeof(TransportGeneratorTool))]
public class TransportGeneratorTool : CustomToolBase
{
private const string ToolName = "TransportGeneratorTool";
public TransportGeneratorTool()
{
OnGenerateCode += GenerateCode;
}
private static void GenerateCode(object s, GenerationEventArgs e)
{
try
{
var serializer = new XmlSerializer(typeof (Parser.System));
using (var reader = new StringReader(e.InputText))
using (var writer = new StringWriter(e.OutputCode))
{
Generator.System = (Parser.System) serializer.Deserialize(reader);
Generator.System.Namespace = e.Namespace;
Generator.GenerateSource(writer);
}
}
catch (Exception ex)
{
e.Progress.GenerateError(ex.ToString());
}
}
}
Resulting registry keys:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Generators\{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}\TransportGeneratorTool]
#="TransportGeneratorTool"
"CLSID"="{55a6c192-d29f-4e22-84da-dbaf314ed5c3}"
"GeneratesDesignTimeSource"=dword:00000001
"GeneratesSharedDesignTimeSource"=dword:00000001
Here is the code of my custom attribute (it is in wrapper assembly):
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class CustomToolRegistrationAttribute : RegistrationAttribute
{
public CustomToolRegistrationAttribute(string name, Type customToolType)
{
Name = name;
CustomToolType = customToolType;
}
/// <summary>
/// The type that implements the custom tool. This starts
/// as MyCustomTool by default in the template.
/// </summary>
public Type CustomToolType { get; set; }
public string Name { get; set; }
#region RegistrationAttribute abstract member implementations
public override void Register(RegistrationContext context)
{
Register(x => context.CreateKey(x), (x, key, value) => x.SetValue(key, value));
}
public void Register<T>(Func<string, T> keyCreator, Action<T, string, object> valueCreator)
{
var keyName = CreateKeyName(Name);
var key = keyCreator(keyName);
valueCreator(key, string.Empty, Name);
valueCreator(key, "CLSID", CustomToolType.GUID.ToString("B"));
valueCreator(key, "GeneratesDesignTimeSource", 1);
valueCreator(key, "GeneratesSharedDesignTimeSource", 1);
var disposable = key as IDisposable;
if (disposable != null)
disposable.Dispose();
}
private static string CreateKeyName(string name)
{
return string.Format(#"Generators\{0}\{1}", vsContextGuids.vsContextGuidVCSProject, name);
}
public override void Unregister(RegistrationContext context)
{
Unregister(context.RemoveKey);
}
public void Unregister(Action<string> keyRemover)
{
keyRemover(CreateKeyName(Name));
}
#endregion
}
My solution is to make a setup project. I get the registry settings from the pkgdef file by adding the following to the csproj file of the package:
<Target Name="GeneratePackageRegistryFiles">
<Exec Command=""$(VSSDK90Install)VisualStudioIntegration\Tools\Bin\RegPkg.exe" /root:Software\Microsoft\VisualStudio\9.0 /codebase "$(TargetPath)" /regfile:"$(OutDir)$(TargetName).reg"" />
</Target>
<PropertyGroup>
<BuildDependsOn>$(BuildDependsOn);GeneratePackageRegistryFiles;</BuildDependsOn>
</PropertyGroup>
When building look in the output directory you should find a .reg file which you can import in the setup project.
Obviously you can run the regpkg.exe from the command-line if modifying the project is not an option.
This is what I ended up with last time when I struggled to get my custom tool registered.
I hope this instruction is detailed enough and covers everything so you won't spend much time fighting it. The following MSDN article was used as a starting point. http://msdn.microsoft.com/en-US/library/bb166527(v=vs.80).aspx Unfortunately you cannot use it alone. What you really need to do is:
Make sure the assembly is signed. Why? Because otherwise you won't be able to put it into GAC at step 6 below.
To sign your assembly follow these steps:
1.1. Go to the Properties screen of the project.
1.2. Once there go to the Signing tab.
1.3. Once there check the Sign the assembly checkbox.
Make sure you know the version number of your assembly. You will need this number to specify the ASSEMBLY_VERSION parameter later.
In order to get this number open the AssemblyInfo.cs file in the Properties folder of your project and look for the line starting with: [assembly: AssemblyVersion(
Make sure you know the GUID of the generator class. You will need it to specify the GENERATOR_GUID parameter later.
In order to get this GUID open the file with the generator class and look for the Guid class-attribute that decorates this class, something like: [Guid("17799E85-421B-4684-B59E-650E34ECC718")]
Build the project
Get the public token key of the assembly. In order to do that you will have to run the following command:
sn.exe -T ASSEMBLY_FILE
You will need this information later when for PUBLIC_TOKEN_KEY.
The sn.exe file can be found in C:\Program Files\Microsoft SDKs\Windows\v8.0A\bin\sn.exe
Pay attention to the version number of the framework (v8.0A) in the filepath above. It needs to be consistent with the version of the framework used to compile the project.
Put the assembly to the GAC using the following command:
gacutil.exe /i ASSEMBLY_FILE /f
Getting registered in GAC requires administrative permissions.
The gacutil.exe file can be found in C:\Program Files\Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\gacutil.exe
Pay attention to the version number of the framework (v8.0A) in the filepath above. It needs to be consistent with the version of the framework used to compile the project.
Make the following changes to the .REG (see below) file. PLEASE NOTE: that both GENERATOR_GUID and PROJECT_TYPE_GUID need to be supplied WITH curly braces: {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
7.1. Fix version number of Visual Studio is used (for example: 10.0 or 9.0): VS_VERSION
7.2. Fix the GUID of the generator: GENERATOR_GUID
7.3. Fix the namespace of the assembly: NAMESPACE_NAME
7.4. Fix the generator class name: GENERATOR_TYPE_NAME
7.5. In order to register the generator the Visual Studio needs to know to which project types this generator can be applied to. So you need to get GUID's of proper project types (C#, VB.NET, etc.).
To figure out the GUID's of the project types you need to open a visual studio project file (*.csproj) in a text editor and look for GUID's in the ProjectTypeGuids XML element.
For each of these GUIDs repeat the block of last 3 entries in the .REG file replacing the PROJECT_TYPE_GUID with the a GUID just found.
7.6. Fix the extension of the file associated with the custom tool: FILE_EXTENSTION
Run the .REG file. You may need to have administrative permissions for doing this.
.REG file:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\VS_VERSION\CLSID\GENERATOR_GUID]
#="COM+ class: NAMESPACE_NAME.GENERATOR_TYPE_NAME"
"InprocServer32"="C:\\WINDOWS\\system32\\mscoree.dll"
"ThreadingModel"="Both"
"Class"="NAMESPACE_NAME.GENERATOR_TYPE_NAME"
"Assembly"="NAMESPACE_NAME, Version=ASSEMBLY_VERSION, Culture=Neutral, PublicKeyToken=PUBLIC_TOKEN_KEY"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\VS_VERSION\Generators]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\VS_VERSION\Generators\PROJECT_TYPE_GUID]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\VS_VERSION\Generators\PROJECT_TYPE_GUID\\.FILE_EXTENSTION]
#="GENERATOR_TYPE_NAME"
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\VS_VERSION\Generators\PROJECT_TYPE_GUID\GENERATOR_TYPE_NAME]
#="Code generator for whatever you like"
"CLSID"="GENERATOR_GUID"
"GeneratesDesignTimeSource"=dword:00000001
PS.
Sorry for not being able to make placehoders in the REG file distinct, unfortunately the text editor that StackOverflow uses cannot distinguish its markup elements from the content.

Categories