Automation Add-ins for MS Excel 2013 - c#

I am trying to write a C# automation add-in in Visual Studio 2013. The objective is to be able to call UDFs written in C# from within MS Excel 2013. I have read most of the publicly available materials on the subject and have tried to adapt several simple examples, such as.
Unfortunately, neither of them is written under VS 2013 and MSExcel 2013. Code sample:
using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Globalization;
using Microsoft.Win32;
namespace MyFirstAddIn
{
// Early binding. Doesn't need AutoDual.
[Guid("5E6CD676-553F-481E-9104-4701C4DAB272")]
[ComVisible(true)]
public interface IFinancialFunctions
{
double Bid(string symbol);
double Ask(string symbol);
double[,] BidnAsk(string symbol, string direction = null);
}
[Guid("B9B7A498-6F84-43EB-A50C-6D26B72895DA")]
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
public class FinancialFunctions : IFinancialFunctions
{
// Private class members.
private static readonly WebClient webClient = new WebClient();
private const string UrlTemplate = "http://finance.yahoo.com/d/quotes.csv?s={0}&f={1}";
// Private method - data download.
private static double GetDoubleDataFromYahoo(string symbol, string field)
{
string request = string.Format(UrlTemplate, symbol, field);
string rawData = webClient.DownloadString(request);
return double.Parse(rawData.Trim(), CultureInfo.InvariantCulture);
}
// Public "interface" methods.
public double Bid(string symbol)
{
return GetDoubleDataFromYahoo(symbol, "b3");
}
public double Ask(string symbol)
{
return GetDoubleDataFromYahoo(symbol, "b2");
}
public double[,] BidnAsk(string symbol, string direction = null)
{
double bid = GetDoubleDataFromYahoo(symbol, "b3");
double ask = GetDoubleDataFromYahoo(symbol, "b2");
return direction == "v" ? new[,]{{bid}, {ask}} : new[,]{{bid, ask}};
}
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type type)
{
Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable"));
RegistryKey key = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), true);
key.SetValue("",System.Environment.SystemDirectory + #"\mscoree.dll",RegistryValueKind.String);
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type type)
{
Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), false);
}
private static string GetSubKeyName(Type type, string subKeyName)
{
System.Text.StringBuilder s = new System.Text.StringBuilder();
s.Append(#"CLSID\{");
s.Append(type.GUID.ToString().ToUpper());
s.Append(#"}\");
s.Append(subKeyName);
return s.ToString();
}
}
}
I have made the assembly COM-visible via:
Project->Properties->Application->Assembly Information
and I've also registered COM interop in the "Build" tab.
After building, I can see in the registry that registration was successful and the add-in is registered under the correct GUID. However, when I open Excel and go to Developer->Add-ins->Automation, I cannot see my add-in in the list. I verified that the code I'm posting works with Excel 2010 but for some reason I fail to see my add-in in Excel 2013.
Can anyone help me with this?

OK, I have found the cause of the problem. Surprisingly, it turns out that Visual Studio does not have the capability to automatically register 64bit dlls under the correct tree in the registry. The solution is not to register the project for COM interop and manually add commands to invoke the 64bit version of RegAsm as a post-build event. The full path and name of the dll need to be included, so unfortunately this is not a fully automated solution.

Related

Registry.LocalMachine.OpenSubKey() returns a result different from Registry Editor

I'm trying to Create an installer for my CAD plugin, and need to get the AutoCAD install location. but the return values of RegistryKey.GetSubKeyNames() is different from what I see in Registry Editor.
string registry_key = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (Microsoft.Win32.RegistryKey key = Registry.LocalMachine.OpenSubKey(registry_key))
{
foreach (string subkey_name in key.GetSubKeyNames())
{
Console.WriteLine(subkey_name);
}
}
output:
AddressBook
Autodesk Application Manager
Autodesk Content Service
Autodesk Material Library 2015
Autodesk Material Library Base Resolution Image Library 2015
Connection Manager
DirectDrawEx
DXM_Runtime
f528b707
Fontcore
...
In Registry Editor:
animizvideocn_is1
AutoCAD 2015
Autodesk 360
Connection Manager
...
AutoCAD 2015 is what i need
Your installer seems to be a 32 bit application, or at least runs as a 32 bit process.
Therefore, Windows redirects
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
to
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
To access the non redirected node, follow the instructions here.
This might be not a direct answer to your question, but i had to do the same thing. I was not looking at the registry, but the Program Files directory. It will then add the netload command to the autoload lisp file. It will install a list of Plugin dlls to all installed autocad versions. This can easily be changed... Hopefully it helps.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace AMU.AutoCAD.Update
{
public class AutoCadPluginInstaller
{
private static readonly Regex AutoloadFilenameRegex = new Regex("acad([\\d])*.lsp");
public void Install(IEnumerable<string> pluginFiles)
{
var acadDirs = this.GetAcadInstallationPaths();
var autoloadFiles = acadDirs.Select(this.GetAutoloadFile);
foreach (var autoloadFile in autoloadFiles)
this.InstallIntoAutoloadFile(autoloadFile, pluginFiles);
}
private void InstallIntoAutoloadFile(string autoloadFile, IEnumerable<string> pluginFiles)
{
try
{
var content = File.ReadAllLines(autoloadFile).ToList();
foreach (var pluginFile in pluginFiles)
{
var loadLine = this.BuildLoadLine(pluginFile);
if(!content.Contains(loadLine))
content.Add(loadLine);
}
File.WriteAllLines(autoloadFile, content);
}
catch (Exception ex)
{
//log.Log();
}
}
private string BuildLoadLine(string pluginFile)
{
pluginFile = pluginFile.Replace(#"\", "/");
return $"(command \"_netload\" \"{pluginFile}\")";
}
private IEnumerable<string> GetAcadInstallationPaths()
{
var programDirs =
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var autoDeskDir = Path.Combine(programDirs, "Autodesk");
if (!Directory.Exists(autoDeskDir))
return null;
return Directory.EnumerateDirectories(autoDeskDir)
.Where(d => d.Contains("AutoCAD"));
}
private string GetAutoloadFile(string acadDir)
{
var supportDir = Path.Combine(acadDir, "Support");
var supportFiles = Directory.EnumerateFiles(supportDir);
return supportFiles.FirstOrDefault(this.IsSupportFile);
}
private bool IsSupportFile(string path)
=> AutoloadFilenameRegex.IsMatch(Path.GetFileName(path));
}
}
(see here: https://gist.github.com/felixalmesberger/4ff8ed27f66f872face4368a13123fff)
You can use it like this:
var installer = new AutoCadPluginInstaller();
installer.Install(new [] {"Path to dll"});
Have fun.

'PrivateKey' does not have a definition for 'CreateFromFile'

I am trying to implement a solution into my application that mirrors the answer in this post
I have a similar scenario where I have an HttpListener and Grapevine based application running on an Ubuntu server that I need to get working with HTTPS using Mono and I am trying to create and include the relevant keys to allow HTTPS
The problem I am having is the last line of the solution,
key = PrivateKey.CreateFromFile (pvk_file).RSA;
When I try the same Visual Studio shows an error/text highlighted red, 'PrivateKey' does not have a definition for 'CreateFromFile'
Am I using the wrong libraries or is something else the issue with my code itself?
My code, cut down to the relevant method.
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using java.security;
public class ConfigureCertificates
{
private readonly string _dirName;
private readonly string _path;
private readonly string _port;
private readonly string _certFile;
public ConfigureCertificates(string port)
{
_dirName = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
_path = Path.Combine(_dirName, ".mono");
_path = Path.Combine(_path, "httplistener");
_port = port;
_certFile = Path.Combine(_path, String.Format("{0}.cer", _port));
}
public void SetUpCerts()
{
if (!File.Exists(_certFile))
throw new Exception("Certificate file not found");
string pvkFile = Path.Combine(_path, String.Format("{0}.pvk", _port));
if (!File.Exists(pvkFile))
throw new Exception("Private key not found");
var cert = new X509Certificate2(_certFile);
var key = PrivateKey.CreateFromFile(pvkFile).RSA; // Error occurs here
}
}
You have a naming clash - in other words there is another class called PrivateKey that doesn't have the method you require. A quick Google hunt indicates the correct class is in the Mono.Security.Authenticode namespace. So you will need to reference the full path:
Mono.Security.Authenticode.PrivateKey.CreateFromFile(...)
You may also need to add the Mono.Security package if you don't already have it.

Total number of labels inside a module of TFS repository

I need to find out how many labels are there inside each module of a collection in Team Foundation repository.
I am using TFS 2013.
I know we can get it from Visual Studio. But we need a script which gets us the number of labels as output.
Can anyone help me out in getting a C# or a Powershell code to obtain the same?
TIA
You can use .NET Client Libraries to get this: .NET client libraries for Visual Studio Team Services (and TFS)
Code sample:
using System;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
namespace GetLabels
{
class Program
{
static void Main(string[] args)
{
string tfscollection = "http://xxx:8080/tfs/defaultcollection";
TfsTeamProjectCollection ttpc = new TfsTeamProjectCollection(new Uri(tfscollection));
VersionControlServer vcs = ttpc.GetService<VersionControlServer>();
string labelname = null;
string labelscope = "$/";
string owner = null;
bool includeitem = false;
int labelnumber;
VersionControlLabel[] labels = vcs.QueryLabels(labelname,labelscope,owner,includeitem);
labelnumber = labels.Length;
Console.WriteLine(labelnumber);
Console.ReadLine();
}
}
}

How to release COM handle in .NET

I am using the following code under ASP.NET 4.0 framework to obtain the version of MSI file from a web app:
string strVersion = "";
try
{
Type InstallerType;
WindowsInstaller.Installer installer;
InstallerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
installer = (WindowsInstaller.Installer)Activator.CreateInstance(InstallerType);
WindowsInstaller.Database db = installer.OpenDatabase(strMSIFilePath, 0);
WindowsInstaller.View dv = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'");
WindowsInstaller.Record record = null;
dv.Execute(record);
record = dv.Fetch();
strVersion = record.get_StringData(1).ToString();
dv.Close();
//db.Commit();
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dv);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(db);
}
catch
{
//Failed
strVersion = "";
}
It works fine except that when the code finishes running it holds an internal MSI file handle so when I try to move or rename the MSI file I get the error that the file is still in use. This continues until I actually navigate away from the ASPX page that calls the method above.
My question is, I obviously didn't close some handle or object in the code above. But what could that be?
PS. I'm testing it in a development IDE from VS2010.
EDIT: Edited the code like it should be after Adriano's suggestion. Thanks!
The COM object has not been released (it should be auto-released when it goes out of scope but in .NET this doesn't work really well). Because it does not implement the IDisposable interface you can't call its Dispose() method and you can't use it inside an using statement. You have to explicitly call Marshal.FinalReleaseComObject. For example:
try
{
// Your stuffs
}
finally
{
dv.Close();
Marshal.FinalReleaseComObject(dv);
Marshal.FinalReleaseComObject(db);
}
Moreover note that you do not really need a call to the Commit() method because you didn't make any change but just a query.
FWIW, you should be using Windows Installer XML (WiX) Deployment Tools Foundation (DTF). It's an FOSS project from Microsoft that can be found on CodePlex. It has MSI interop libraries with classes that are very similar to the COM classes but implement IDisosable and use P/Invoke instead of COM behind the scenes. There is even support for Linq to MSI if you want. And the full source code is available.
DTF is the gold standard for MSI interop in a .NET world. Here are two examples:
using System;
using System.Linq;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Linq;
namespace ConsoleApplication3
{
class Program
{
const string DATABASE_PATH = #"C:\FOO..MSI";
const string SQL_SELECT_PRODUCTVERSION = "SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'";
static void Main(string[] args)
{
using (Database database = new Database(DATABASE_PATH, DatabaseOpenMode.ReadOnly))
{
Console.WriteLine(database.ExecuteScalar(SQL_SELECT_PRODUCTVERSION).ToString());
}
using (QDatabase database = new QDatabase(DATABASE_PATH, DatabaseOpenMode.ReadOnly))
{
var results = from property in database.Properties where property.Property == "ProductVersion" select property.Value;
Console.WriteLine(results.AsEnumerable<string>().First());
}
}
}
}
try to Dispose the Objects.
dv.Dispose();
db.Dispose();

Runtime Compilation of DLL for COM Interop

I am working on a project that builds mathematical models for a user and then can output them in different formats. Right now I support outputting in Python, Java, C++. This works, I just autogenerate the code and call it a day.
However, a new request has been made. The user wants to be able to use the models from within Excel. I did some searching and found http://richnewman.wordpress.com/2007/04/15/a-beginner%E2%80%99s-guide-to-calling-a-net-library-from-excel/
So this is a nice start but I need to do this programmatically. The models are stored as objects in the bigger program. If the user selects to export as a DLL for Excel, I would take some boilerplate code and insert the methods I would want to use.
However, it seems like I need to register the code for COM Interop. My test code creates a DLL I can use it C# and access its methods. But trying to add a reference in Excel 2000 (I know, I know, corporate sucks) VBA doesn't work. It seems that no TLB file is created, so there is nothing for it to load.
If I take the generated code compile it as a standalone having checked the make COM Visible and register for com interop boxes, the TLB is generated but Excel VBA throws an automation error.
So the actual questions.
1) How can I create at runtime a DLL that is Com Visible and Reistered for COM Interop?
2) How do I get Excel to play nice with it.
Simple Example DLL Code Follows:
using System;
using System.Runtime.InteropServices;
namespace VSN
{
[ComVisibleAttribute(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class VSNFunctions
{
public VSNFunctions()
{
}
/// <summary>
/// Adds 2 variables together.
/// </summary>
/// <param name=\"v1\">First Param</param>
/// <param name=\"v2\">Second Param</param>
/// <returns>Sum of v1 and v2</returns>
public double Add2(double v1, double v2)
{
return v1 + v2;
}
public double Sub2(double v1, double v2)
{
return v1 - v2;
}
public double Mul2(double v1, double v2)
{
return v1 * v2;
}
public double div2(double v1, double v2)
{
return v1 / v2;
}
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey("CLSID\\{"+t.GUID.ToString().ToUpper() + "}\\Programmable");
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type t)
{
Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey("CLSID\\{"+t.GUID.ToString().ToUpper() + "}\\Programmable");
}
}
}
Code To Build the DLL programmitcally Follows:
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
String exeName = String.Format(#"{0}\{1}.dll", System.Environment.CurrentDirectory, "VSNTest");
MessageBox.Show(exeName);
parameters.OutputAssembly = exeName;
parameters.CompilerOptions = "/optimize";
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, DLLString);
How to: Expose Code to VBA in a Visual C# Project
To enable VBA code to call code in a Visual C# project, modify the code so it is visible to COM, and then set the ReferenceAssemblyFromVbaProject property to True in the designer.

Categories

Resources