How to deploy a visual studio custom tool? - c#

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.

Related

Xamarin Resource.Designer.cs generating const instead of static

I have on .NET 6 project using xamarin.android where the resource designer file generated with an error CS0131 : The left-hand side of an assignment must be a variable, property or indexer:
public static void UpdateIdValues()
{
global::MyGame.Resource.Color.alignment_marker_color = global::MyGame.Resource.Color.alignment_marker_color;
global::MyGame.Resource.Color.white_transparent = global::MyGame.Resource.Color.white_transparent;
global::MyGame.Resource.Dimension.alignment_marker_height = global::MyGame.Resource.Dimension.alignment_marker_height;
// ...
}
with values generated like so :
public partial class Color
{
// aapt resource value: 0x7F010000
public const int alignment_marker_color = 2130771968;
// aapt resource value: 0x7F010001
public const int white_transparent = 2130771969;
// ...
}
Is it an issue with the xamarin dependency ? If so, is it something i can solve through nuget ? dotnet workload ?
I had this issue myself, and after much trial and error, I found that the issue was caused by me having two assemblies, both containing Resources which had the same "Default Namespace" in their project properties.
After changing the namespace in one of them, deleting the bin and obj folders, running a Clean (overkill) I was able to successfully rebuild the project without error.

resharper code analysis using Command line tool (inspectcode.exe)

In My C# language sln file contains two projects. first project has the actual code & second project has the unittest cases for the first project. but while am doing the resharper code analysis using Command line tool (inspectcode.exe).
for both project resharper doing the inspection in the unit test project only, not on the Actual code file.
'
package org.sonar.plugins.resharper;
import java.io.File;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.utils.command.Command;
import org.sonar.api.utils.command.CommandException;
import org.sonar.api.utils.command.CommandExecutor;
public class ReSharperExecutor
{
private static final Logger LOG = LoggerFactory.getLogger(ReSharperExecutor.class);
public void execute(String executable, String project, String solutionFile, File rulesetFile, File reportFile, int timeout)
{
Command cmd = Command.create(getExecutable(executable)).addArgument("/output=" + reportFile.getAbsolutePath()).addArgument("/no-swea").addArgument("/project=" + project).addArgument("/profile=" + rulesetFile.getAbsolutePath()).addArgument("/no-buildin-settings").addArgument(solutionFile);
int exitCode = CommandExecutor.create().execute(cmd, TimeUnit.MINUTES.toMillis(timeout));
if (exitCode != 0) {
throw new CommandException(cmd, "ReSharper execution failed with exit code: " + exitCode, null);
}
}
private static String getExecutable(String propertyValue)
{
String execName = "inspectcode.exe";
if (!propertyValue.endsWith(execName)) {
return new File(propertyValue, execName).getAbsolutePath();
}
return propertyValue;
}
}'
Where I need to do the change to do the Resharper analysis for the Actual code.
Executing command: cd:/inspectcode.exe /output=cd:\resharper-report.xml /no-swea /project=* /profile=cd:.sonar\resharper-sonarqube.DotSettings /no-buildin-settings cd:\XXXX.sln
The above issue was caused bcoz the VS build version different in the
1.project built machine(VS2015) &
2.project analysis happening machine(VS2013),
so its not show any error details in log of the machine 2.
After testing the analysis in the built machine i got the root cause of the issue(Few of the dependency library of the project was missed which should be availble under MSBuild folder{Msbuildfolder->Microsoft->visualstudio->version}) & fixed it(by replacing the missing dll's).
Now Analysis running successfully.

Programmatically build solution with filter

After looking all over the Google I found a good way to build a solution. However the solution I want to build also contains unit test projects, which I don't want to include in the build, or if I can't prevent that at least put those binaries in a separate folder. The code is as follows:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
public class BuildSolution
{
private readonly string _solutionPath;
private readonly string _outputPath = "C:\\Temp\\TestBuild\\";
public BuildSolution(string solutionPath, string outputPath = null)
{
if (!string.IsNullOrEmpty(outputPath))
_outputPath = outputPath;
_solutionPath = solutionPath;
Directory.EnumerateFiles(_outputPath, "*", SearchOption.AllDirectories)
.Select(x => new FileInfo(x))
.ToList()
.ForEach(x => x.Delete());
}
public void Build()
{
var pc = new ProjectCollection();
var globalProps = new Dictionary<string, string>()
{
{ ProjectPropertyNames.Configuration, "Debug" },
{ ProjectPropertyNames.OutputPath, _outputPath },
{ ProjectPropertyNames.EnableNuGetPackageRestore, "true"},
};
var targetsToBuild = new[] { "Build" };
var buildRequest = new BuildRequestData(_solutionPath, globalProps, null, targetsToBuild, null);
var buildParams = new BuildParameters(pc);
buildParams.Loggers = new List<ILogger>() { new ConsoleLogger(LoggerVerbosity.Minimal) };
var buildManager = BuildManager.DefaultBuildManager;
buildManager.BeginBuild(buildParams);
var buildSubmission = buildManager.PendBuildRequest(buildRequest);
buildSubmission.ExecuteAsync(BuildCompleted, null);
while (!done)
{
Thread.Sleep(10);
}
buildManager.EndBuild();
Console.WriteLine("OverallResult:{0}", buildSubmission.BuildResult.OverallResult);
}
bool done = false;
private void BuildCompleted(BuildSubmission submission)
{
done = submission.IsCompleted;
}
/// <summary>
/// Unused, but I tried it and it gives me back the correct projects but the build fails because of dependant nuget packages
/// </summary>
/// <param name="path">path of solution</param>
/// <returns></returns>
private IEnumerable<FileInfo> GetFirstLevelProjects(string path)
{
foreach (var dir in Directory.EnumerateDirectories(path))
{
foreach (var file in Directory.EnumerateFiles(dir, "*.csproj"))
{
if (!file.Contains("Test"))
yield return new FileInfo(file);
}
}
}
}
nothing fancy about it. (I'm playing with the idea of making the build async so I can update status...we'll see about that, I might switch it back to sync). One thing I tried was that instead of putting the solution in the build request, I would build the project collection using the first level projects (I use git with sub-modules, so I don't want to build all the non-relevant sub-modules). The problem with that route was that the build would fail because of nuget packages (not sure why or how to get around that). When I build the solution it builds successfully, but my outputPath also includes the test binaries. My end game is that the output can get copied to a specific folder of mine. I wouldn't mind having the test binaries if I knew I could filter ALL the binaries that are in the test projects... So how? What options do I have?
Why Not MsBuild?
The easiest way that I can think of to do this is to use MsBuild to do it.
msbuild C:\myFolder\mySolution.sln /p:Configuration=Release
As for not building the tests, this could now be easily changed from within visual studio
Right Click on Solution > Properties > Configuration Properties (on left side)
From there you could switch to release mode and uncheck the box next to your test projects. This will tell visual studio that when it does a release build it can skip these projects.
Without MsBuild
However, if you wanted to keep building them in the manner that you showed, I would change the assembly name (the dll name) so that you could identify them easily in your favorite scripting language.
Right Click on Project > Application Tab (on left side) > Assembly Name Box
I would call them something like SolutionNameSpace.Tests.ProjectUnderTest.dll.
Then in your build process you can filter out SolutionNameSpace.Tests.*.dll. Just be carefull, if you reference testing libraries they could get copied to your output folder also.

Cannot apply indexing with[] to an expression of type

I am creating an SSIS package and want to include a script which checks if a file exist before retrieving the file and saving that data to a table.
I have three separate variable that I have set up:
fileExistFlag Int32 0
fileName String check.txt
folderPath String C:\
My C# code looks like this, where I am checking:
public void Main()
{
// TODO: Add your code here
String fp = Dts.Variables["User::folderPath"].Value.ToString() + Dts.Variables["User::fileName"].Value.ToString();
if (File.Exists(fp))
{
Dts.Variables["User::fileExistFlag"].Value = 1;
}
MessageBox.Show(fp);
MessageBox.Show(Dts.Variables["User::fileExistFlag"].Value.ToString());
Dts.TaskResult = (int)ScriptResults.Success;
}
When I try to compile my script, I receive the following error:
Cannot apply indexing with [] to an expression of type 'Microsoft.SqlServer.Dts.Runtime.Variables for all four instances.
How can I solve the issue?
Updated code:
/*
Microsoft SQL Server Integration Services Script Task
Write scripts using Microsoft Visual C# 2008.
The ScriptMain is the entry point class of the script.
*/
using System;
using System.Data;
using Microsoft.SqlServer.Dts.Runtime;
using System.Windows.Forms;
using System.IO;
namespace ST_04f6fa3ba49a4ddeac3d3d7fc29f04f2.csproj
{
[System.AddIn.AddIn("ScriptMain", Version = "1.0", Publisher = "", Description = "")]
public partial class ScriptMain : Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase
{
#region VSTA generated code
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
/*
The execution engine calls this method when the task executes.
To access the object model, use the Dts property. Connections, variables, events,
and logging features are available as members of the Dts property as shown in the following examples.
To reference a variable, call Dts.Variables["MyCaseSensitiveVariableName"].Value;
To post a log entry, call Dts.Log("This is my log text", 999, null);
To fire an event, call Dts.Events.FireInformation(99, "test", "hit the help message", "", 0, true);
To use the connections collection use something like the following:
ConnectionManager cm = Dts.Connections.Add("OLEDB");
cm.ConnectionString = "Data Source=localhost;Initial Catalog=AdventureWorks;Provider=SQLNCLI10;Integrated Security=SSPI;Auto Translate=False;";
Before returning from this method, set the value of Dts.TaskResult to indicate success or failure.
To open Help, press F1.
*/
public void Main()
{
// TODO: Add your code here
String fp = Dts.Variables.Get("User::folderPath").Value.ToString() + Dts.Variables.Get("User::fileName").Value.ToString();
if (File.Exists(fp))
{
Dts.Variables.Get("User::fileExistFlag").Value = 1;
}
MessageBox.Show(fp);
MessageBox.Show(Dts.Variables.Get("User::fileExistFlag").Value.ToString());
Dts.TaskResult = (int)ScriptResults.Success;
}
}
public static Microsoft.SqlServer.Dts.Runtime.Variable Get(
this Microsoft.SqlServer.Dts.Runtime.Variables variables, string name)
{
foreach(Microsoft.SqlServer.Dts.Runtime.Variable item in variables)
{
if(item.Name == name) return item;
}
return null;
}
}
This is a known BUG in SQL Server BIDS 2005/2008 after installing side by side a later version of SSIS. For example if you are developing a SSIS 2008 package and then install SSIS 2012.
A workaround is to move the file "Microsoft.SQLServer.ManagedDTS.dll" located in the path:
"C:\Program Files (x86)\Microsoft SQL Server\110\SDK\Assemblies" to a backup folder, then the bids take the reference from the path "C:\Windows\assembly\GAC_MSIL\Microsoft.SqlServer.ManagedDTS\10.0.0.0__89845dcd8080cc91\"
But it doesn't seem to work for all the cases reported.
Source:
https://connect.microsoft.com/SQLServer/feedback/details/744390/ssis-any-pre-2012-error-cannot-apply-indexing-with-to-an-expression-of-type-microsoft-sqlserver-dts-runtime-variables
http://support.microsoft.com/kb/938608/en-us
Oddly, this indexer does seem to exist. If it isn't working, though, you might be able to use an extension method:
public static class MyExtensionMethods
{
public static Microsoft.SqlServer.Dts.Runtime.Variable Get(
this Microsoft.SqlServer.Dts.Runtime.Variables variables, string name)
{
foreach(Microsoft.SqlServer.Dts.Runtime.Variable item in variables)
{
if(item.Name == name) return item;
}
return null;
}
}
and use:
... Dts.Variables.Get("User::folderPath").Value ...
instead.
Use Browse in the add references window and look for this dll: C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies\Microsoft.SQLServer.ManagedDTS.dll

Roslyn: workspace loads in console application but not in msbuild task

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!

Categories