From cfb717b6752cf3b1b01833989848c4fdcd207ced Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 12:47:12 -0300 Subject: [PATCH 01/11] Allow build tasks to define custom target/sender name for logging --- Source/ExcelDna.AddIn.Tasks/AbstractTask.cs | 24 +++++++++++++++---- .../ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs | 1 + .../ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs | 1 + 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs b/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs index 874712ec..3cac0d3d 100644 --- a/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs +++ b/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs @@ -1,24 +1,40 @@ -using Microsoft.Build.Framework; +using System; +using Microsoft.Build.Framework; namespace ExcelDna.AddIn.Tasks { public abstract class AbstractTask : ITask { + private readonly string _targetName; + + protected AbstractTask(string targetName) + { + if (string.IsNullOrWhiteSpace(targetName)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(targetName)); + } + + _targetName = targetName; + } + public abstract bool Execute(); protected void LogMessage(string message, MessageImportance importance = MessageImportance.High) { - BuildEngine.LogMessageEvent(new BuildMessageEventArgs("ExcelDnaBuild: " + message, "ExcelDnaBuild", "ExcelDnaBuild", importance)); + BuildEngine.LogMessageEvent(new BuildMessageEventArgs(string.Format("{0}: {1}", _targetName, message), + _targetName, _targetName, importance)); } protected void LogWarning(string code, string message) { - BuildEngine.LogWarningEvent(new BuildWarningEventArgs("ExcelDnaBuild", code, null, 0, 0, 0, 0, message, "ExcelDnaBuild", "ExcelDnaBuild")); + BuildEngine.LogWarningEvent(new BuildWarningEventArgs(_targetName, code, null, 0, 0, 0, 0, message, + _targetName, _targetName)); } protected void LogError(string code, string message) { - BuildEngine.LogErrorEvent(new BuildErrorEventArgs("ExcelDnaBuild", code, null, 0, 0, 0, 0, message, "ExcelDnaBuild", "ExcelDnaBuild")); + BuildEngine.LogErrorEvent(new BuildErrorEventArgs(_targetName, code, null, 0, 0, 0, 0, message, _targetName, + _targetName)); } public IBuildEngine BuildEngine { get; set; } diff --git a/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs b/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs index 6c424578..6efa5d6d 100644 --- a/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs +++ b/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs @@ -21,6 +21,7 @@ public CleanExcelAddIn() } public CleanExcelAddIn(IExcelDnaFileSystem fileSystem) + : base("ExcelDnaClean") { if (fileSystem == null) { diff --git a/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs b/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs index aaa2aefc..81d3f180 100644 --- a/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs +++ b/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs @@ -22,6 +22,7 @@ public CreateExcelAddIn() } public CreateExcelAddIn(IExcelDnaFileSystem fileSystem) + : base("ExcelDnaBuild") { if (fileSystem == null) { From 898ec067a69af070b5596366ba42033dc6d3b868 Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 13:31:09 -0300 Subject: [PATCH 02/11] Make it easier to log (and see) debug messages in MSBuild tasks --- Source/ExcelDna.AddIn.Tasks/AbstractTask.cs | 9 +++++++ .../ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs | 23 ++++++++-------- .../ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs | 26 ++++++++++--------- Source/ExcelDna.sln.DotSettings | 6 ++++- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs b/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs index 3cac0d3d..5f0bb459 100644 --- a/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs +++ b/Source/ExcelDna.AddIn.Tasks/AbstractTask.cs @@ -19,6 +19,15 @@ protected AbstractTask(string targetName) public abstract bool Execute(); + protected void LogDebugMessage(string message) + { + #if DEBUG + LogMessage(message, MessageImportance.High); + #else + LogMessage(message, MessageImportance.Low); + #endif + } + protected void LogMessage(string message, MessageImportance importance = MessageImportance.High) { BuildEngine.LogMessageEvent(new BuildMessageEventArgs(string.Format("{0}: {1}", _targetName, message), diff --git a/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs b/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs index 6efa5d6d..0faeff87 100644 --- a/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs +++ b/Source/ExcelDna.AddIn.Tasks/CleanExcelAddIn.cs @@ -35,18 +35,18 @@ public override bool Execute() { try { + LogDebugMessage("Running CleanExcelAddIn MSBuild Task"); + LogDiagnostics(); FilesInProject = FilesInProject ?? new ITaskItem[0]; - LogMessage("Number of files in project: " + FilesInProject.Length, MessageImportance.Low); + LogDebugMessage("Number of files in project: " + FilesInProject.Length); _common = new BuildTaskCommon(FilesInProject, OutDirectory, FileSuffix32Bit, FileSuffix64Bit); var existingBuiltFiles = _common.GetBuildItemsForDnaFiles(); _packedFilesToDelete = GetPackedFilesToDelete(existingBuiltFiles); - LogMessage("---"); - // Get the packed name versions : Refactor this + build items DeleteAddInFiles(existingBuiltFiles); DeletePackedAddInFiles(_packedFilesToDelete); @@ -63,14 +63,14 @@ public override bool Execute() private void LogDiagnostics() { - LogMessage("----Arguments----", MessageImportance.Low); - LogMessage("FilesInProject: " + (FilesInProject ?? new ITaskItem[0]).Length, MessageImportance.Low); - LogMessage("OutDirectory: " + OutDirectory, MessageImportance.Low); - LogMessage("Xll32FilePath: " + Xll32FilePath, MessageImportance.Low); - LogMessage("Xll64FilePath: " + Xll64FilePath, MessageImportance.Low); - LogMessage("FileSuffix32Bit: " + FileSuffix32Bit, MessageImportance.Low); - LogMessage("FileSuffix64Bit: " + FileSuffix64Bit, MessageImportance.Low); - LogMessage("-----------------", MessageImportance.Low); + LogDebugMessage("----Arguments----"); + LogDebugMessage("FilesInProject: " + (FilesInProject ?? new ITaskItem[0]).Length); + LogDebugMessage("OutDirectory: " + OutDirectory); + LogDebugMessage("Xll32FilePath: " + Xll32FilePath); + LogDebugMessage("Xll64FilePath: " + Xll64FilePath); + LogDebugMessage("FileSuffix32Bit: " + FileSuffix32Bit); + LogDebugMessage("FileSuffix64Bit: " + FileSuffix64Bit); + LogDebugMessage("-----------------"); } private List GetPackedFilesToDelete(BuildItemSpec[] existingBuiltFiles) @@ -142,6 +142,7 @@ private void DeleteFileIfExists(string path) { if (_fileSystem.FileExists(path)) { + LogDebugMessage("Deleting file " + path); _fileSystem.DeleteFile(path); } } diff --git a/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs b/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs index 81d3f180..0b3f190b 100644 --- a/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs +++ b/Source/ExcelDna.AddIn.Tasks/CreateExcelAddIn.cs @@ -36,6 +36,8 @@ public override bool Execute() { try { + LogDebugMessage("Running CreateExcelAddIn MSBuild Task"); + LogDiagnostics(); RunSanityChecks(); @@ -44,7 +46,7 @@ public override bool Execute() DnaFilesToPack = new ITaskItem[0]; FilesInProject = FilesInProject ?? new ITaskItem[0]; - LogMessage("Number of files in project: " + FilesInProject.Length, MessageImportance.Low); + LogDebugMessage("Number of files in project: " + FilesInProject.Length); _configFilesInProject = GetConfigFilesInProject(); _common = new BuildTaskCommon(FilesInProject, OutDirectory, FileSuffix32Bit, FileSuffix64Bit); @@ -53,7 +55,7 @@ public override bool Execute() TryBuildAddInFor32Bit(buildItemsForDnaFiles); - LogMessage("---"); + LogMessage("---", MessageImportance.High); TryBuildAddInFor64Bit(buildItemsForDnaFiles); @@ -71,16 +73,16 @@ public override bool Execute() private void LogDiagnostics() { - LogMessage("----Arguments----", MessageImportance.Low); - LogMessage("FilesInProject: " + (FilesInProject ?? new ITaskItem[0]).Length, MessageImportance.Low); - LogMessage("OutDirectory: " + OutDirectory, MessageImportance.Low); - LogMessage("Xll32FilePath: " + Xll32FilePath, MessageImportance.Low); - LogMessage("Xll64FilePath: " + Xll64FilePath, MessageImportance.Low); - LogMessage("Create32BitAddIn: " + Create32BitAddIn, MessageImportance.Low); - LogMessage("Create64BitAddIn: " + Create64BitAddIn, MessageImportance.Low); - LogMessage("FileSuffix32Bit: " + FileSuffix32Bit, MessageImportance.Low); - LogMessage("FileSuffix64Bit: " + FileSuffix64Bit, MessageImportance.Low); - LogMessage("-----------------", MessageImportance.Low); + LogDebugMessage("----Arguments----"); + LogDebugMessage("FilesInProject: " + (FilesInProject ?? new ITaskItem[0]).Length); + LogDebugMessage("OutDirectory: " + OutDirectory); + LogDebugMessage("Xll32FilePath: " + Xll32FilePath); + LogDebugMessage("Xll64FilePath: " + Xll64FilePath); + LogDebugMessage("Create32BitAddIn: " + Create32BitAddIn); + LogDebugMessage("Create64BitAddIn: " + Create64BitAddIn); + LogDebugMessage("FileSuffix32Bit: " + FileSuffix32Bit); + LogDebugMessage("FileSuffix64Bit: " + FileSuffix64Bit); + LogDebugMessage("-----------------"); } private void RunSanityChecks() diff --git a/Source/ExcelDna.sln.DotSettings b/Source/ExcelDna.sln.DotSettings index 04c7ec21..09a6f5f2 100644 --- a/Source/ExcelDna.sln.DotSettings +++ b/Source/ExcelDna.sln.DotSettings @@ -1,8 +1,12 @@  DO_NOT_SHOW + DO_NOT_SHOW DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW - DO_NOT_SHOW \ No newline at end of file + DO_NOT_SHOW + DTE \ No newline at end of file From c7b205343ca98815398e91064124b7bb79f9d8c9 Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 13:56:23 -0300 Subject: [PATCH 03/11] Add Bitness enumeration to use with detecting Excel's bitness --- Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj | 1 + Source/ExcelDna.AddIn.Tasks/Utils/Bitness.cs | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 Source/ExcelDna.AddIn.Tasks/Utils/Bitness.cs diff --git a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj index dc675a46..c5138d37 100644 --- a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj +++ b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj @@ -44,6 +44,7 @@ + diff --git a/Source/ExcelDna.AddIn.Tasks/Utils/Bitness.cs b/Source/ExcelDna.AddIn.Tasks/Utils/Bitness.cs new file mode 100644 index 00000000..a306f70b --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/Utils/Bitness.cs @@ -0,0 +1,9 @@ +namespace ExcelDna.AddIn.Tasks.Utils +{ + public enum Bitness + { + Unknown = 0, + Bit32 = 32, + Bit64 = 64, + } +} \ No newline at end of file From 05b31726b821c9f4a404fb26e58674e0322b2147 Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 14:47:33 -0300 Subject: [PATCH 04/11] Add ExcelDetector utility to find Excel's installation path and bitness --- .../ExcelDna.AddIn.Tasks.csproj | 2 + .../Utils/ExcelDetector.cs | 131 ++++++++++++++++++ .../Utils/IExcelDetector.cs | 9 ++ 3 files changed, 142 insertions(+) create mode 100644 Source/ExcelDna.AddIn.Tasks/Utils/ExcelDetector.cs create mode 100644 Source/ExcelDna.AddIn.Tasks/Utils/IExcelDetector.cs diff --git a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj index c5138d37..c7183088 100644 --- a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj +++ b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj @@ -45,7 +45,9 @@ + + diff --git a/Source/ExcelDna.AddIn.Tasks/Utils/ExcelDetector.cs b/Source/ExcelDna.AddIn.Tasks/Utils/ExcelDetector.cs new file mode 100644 index 00000000..6b0ffe44 --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/Utils/ExcelDetector.cs @@ -0,0 +1,131 @@ +using System; +using System.IO; +using System.Linq; +using System.Security.AccessControl; +using Microsoft.Win32; + +namespace ExcelDna.AddIn.Tasks.Utils +{ + internal class ExcelDetector : IExcelDetector + { + public bool TryFindLatestExcel(out string excelExePath) + { + excelExePath = null; + + var versions = (ExcelVersions[])Enum.GetValues(typeof(ExcelVersions)); + var versionsNumbersDescending = versions.Select(v => (int)v).OrderByDescending(vn => vn); + + foreach (var versionNumber in versionsNumbersDescending) + { + var keyPath = string.Format(@"Software\Microsoft\Office\{0}.0\Excel\InstallRoot", versionNumber); + + if (!TryGetExcelExePathFromRegistry(keyPath, out excelExePath)) continue; + + return true; + } + + return false; + } + + public bool TryFindExcelBitness(string excelExePath, out Bitness bitness) + { + bitness = Bitness.Unknown; + + if (!File.Exists(excelExePath)) + { + throw new Exception("Excel path specified in Registry not found on disk: " + excelExePath); + } + + using (var fileStream = File.OpenRead(excelExePath)) + { + using (var reader = new BinaryReader(fileStream)) + { + // See http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx + // Offset to PE header is always at 0x3C. + // The PE header starts with "PE\0\0" = 0x50 0x45 0x00 0x00, + // followed by a 2-byte machine type field (see the document above for the enum). + + fileStream.Seek(0x3c, SeekOrigin.Begin); + var peOffset = reader.ReadInt32(); + + fileStream.Seek(peOffset, SeekOrigin.Begin); + var peHead = reader.ReadUInt32(); + + if (peHead != 0x00004550) // "PE\0\0", little-endian + { + throw new Exception("Unable to find PE header in file"); + } + + var machineType = (MachineType)reader.ReadUInt16(); + + switch (machineType) + { + case MachineType.ImageFileMachineI386: + { + bitness = Bitness.Bit32; + return true; + } + case MachineType.ImageFileMachineAmd64: + case MachineType.ImageFileMachineIa64: + { + bitness = Bitness.Bit64; + return true; + } + default: + { + bitness = Bitness.Unknown; + return false; + } + } + } + } + } + + private bool TryGetExcelExePathFromRegistry(string keyPath, out string excelExePath) + { + return TryGetExcelExePathFromRegistry(keyPath, RegistryView.Registry64, out excelExePath) || + TryGetExcelExePathFromRegistry(keyPath, RegistryView.Registry32, out excelExePath); + } + + private bool TryGetExcelExePathFromRegistry(string keyPath, RegistryView registryView, out string excelExePath) + { + excelExePath = null; + + using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView)) + { + using (var excelKey = baseKey.OpenSubKey(keyPath, RegistryKeyPermissionCheck.ReadSubTree, + RegistryRights.ReadKey)) + { + if (excelKey == null) return false; + + var installRoot = excelKey.GetValue("Path"); + if (installRoot != null) + { + excelExePath = Path.Combine(installRoot.ToString(), "EXCEL.EXE"); + return true; + } + } + } + + return false; + } + + private enum ExcelVersions + { + // ReSharper disable UnusedMember.Local + Excel2003 = 11, // Office 2003 - 11.0 + Excel2007 = 12, // Office 2007 - 12.0 + Excel2010 = 14, // Office 2010 - 14.0 (sic!) + Excel2013 = 15, // Office 2013 - 15.0 + Excel2016 = 16, // Office 2016 - 16.0 + // ReSharper restore UnusedMember.Local + } + + private enum MachineType : ushort + { + ImageFileMachineAmd64 = 0x8664, + ImageFileMachineI386 = 0x14c, + ImageFileMachineIa64 = 0x200, + } + } +} diff --git a/Source/ExcelDna.AddIn.Tasks/Utils/IExcelDetector.cs b/Source/ExcelDna.AddIn.Tasks/Utils/IExcelDetector.cs new file mode 100644 index 00000000..2e21fd18 --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/Utils/IExcelDetector.cs @@ -0,0 +1,9 @@ +namespace ExcelDna.AddIn.Tasks.Utils +{ + public interface IExcelDetector + { + bool TryFindLatestExcel(out string excelExePath); + + bool TryFindExcelBitness(string excelExePath, out Bitness bitness); + } +} \ No newline at end of file From c9c31657c353f74f6005b4fb553d7f3bacf205f2 Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 16:14:41 -0300 Subject: [PATCH 05/11] Add DevToolsEnvironment utility to interact with DTE during build --- .../ExcelDna.AddIn.Tasks.csproj | 5 + .../Utils/DevToolsEnvironment.cs | 220 ++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 Source/ExcelDna.AddIn.Tasks/Utils/DevToolsEnvironment.cs diff --git a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj index c7183088..615d4e85 100644 --- a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj +++ b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj @@ -30,11 +30,15 @@ 4 + + True + + @@ -45,6 +49,7 @@ + diff --git a/Source/ExcelDna.AddIn.Tasks/Utils/DevToolsEnvironment.cs b/Source/ExcelDna.AddIn.Tasks/Utils/DevToolsEnvironment.cs new file mode 100644 index 00000000..f5ae9295 --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/Utils/DevToolsEnvironment.cs @@ -0,0 +1,220 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text.RegularExpressions; + +namespace ExcelDna.AddIn.Tasks.Utils +{ + internal class DevToolsEnvironment : IDisposable + { + private bool _isMessageFilterRegistered; + + public EnvDTE.Project GetProjectByName(string projectName) + { + var vsProcessId = GetVisualStudioProcessId(); + + var dte = GetDevToolsEnvironment(vsProcessId); + if (dte == null) return null; + + if (!_isMessageFilterRegistered) + { + MessageFilter.Register(); + _isMessageFilterRegistered = true; + } + + var project = dte + .Solution + .Projects + .OfType() + .SingleOrDefault(p => + string.Compare(p.Name, projectName, StringComparison.OrdinalIgnoreCase) == 0); + + return project; + } + + public void Dispose() + { + if (_isMessageFilterRegistered) + { + MessageFilter.Revoke(); + } + } + + private int GetVisualStudioProcessId() + { + try + { + var process = Process.GetCurrentProcess(); + + if (process.ProcessName.ToLower().Contains("devenv")) + { + // We're being compiled directly by Visual Studio + return process.Id; + } + + // We're being compiled by other tool (e.g. MSBuild) called from Visual Studio + // therefore, Visual Studio is our parent process + + using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Process WHERE ProcessId = " + process.Id)) + { + foreach (var obj in searcher.Get()) + { + var parentId = Convert.ToInt32((uint)obj["ParentProcessId"]); + var parentProcessName = Process.GetProcessById(parentId).ProcessName; + + if (parentProcessName.ToLower().Contains("devenv")) + { + return parentId; + } + } + } + } + catch (Exception) + { + // Do nothing + } + + return -1; + } + + private EnvDTE.DTE GetDevToolsEnvironment(int processId) + { + object runningObject = null; + + IBindCtx bindCtx = null; + IRunningObjectTable rot = null; + IEnumMoniker enumMonikers = null; + + try + { + Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx)); + bindCtx.GetRunningObjectTable(out rot); + rot.EnumRunning(out enumMonikers); + + var moniker = new IMoniker[1]; + var numberFetched = IntPtr.Zero; + + while (enumMonikers.Next(1, moniker, numberFetched) == 0) + { + var runningObjectMoniker = moniker[0]; + + string name = null; + + try + { + if (runningObjectMoniker != null) + { + runningObjectMoniker.GetDisplayName(bindCtx, null, out name); + } + } + catch (UnauthorizedAccessException) + { + // Do nothing, there is something in the ROT that we do not have access to + } + + var monikerRegex = new Regex(@"!VisualStudio.DTE\.\d+\.\d+\:" + processId, RegexOptions.IgnoreCase); + if (!string.IsNullOrEmpty(name) && monikerRegex.IsMatch(name)) + { + Marshal.ThrowExceptionForHR(rot.GetObject(runningObjectMoniker, out runningObject)); + break; + } + } + } + finally + { + if (enumMonikers != null) + { + Marshal.ReleaseComObject(enumMonikers); + } + + if (rot != null) + { + Marshal.ReleaseComObject(rot); + } + + if (bindCtx != null) + { + Marshal.ReleaseComObject(bindCtx); + } + } + + return runningObject as EnvDTE.DTE; + } + + [DllImport("ole32.dll")] + private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc); + + // Implement the IOleMessageFilter interface + [DllImport("Ole32.dll")] + private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); + + /// + /// Contains the IOleMessageFilter thread error-handling functions + /// See https://msdn.microsoft.com/en-us/library/ms228772 + /// + private class MessageFilter : IOleMessageFilter + { + public static void Register() + { + // Register the IOleMessageFilter to handle any threading errors + // See https://msdn.microsoft.com/en-us/library/ms228772 + + IOleMessageFilter newFilter = new MessageFilter(); + + IOleMessageFilter _; + CoRegisterMessageFilter(newFilter, out _); + } + + public static void Revoke() + { + IOleMessageFilter _; + + // Turn off the IOleMessageFilter + CoRegisterMessageFilter(null, out _); + } + + // IOleMessageFilter functions + // Handle incoming thread requests + int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo) + { + // Return the flag SERVERCALL_ISHANDLED + return 0; + } + + // Thread call was rejected, so try again + int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) + { + if (dwRejectType == 2) // SERVERCALL_RETRYLATER + { + // Retry the thread call immediately if return >= 0 & < 100. + return 99; + } + + // Too busy; cancel call + return -1; + } + + int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType) + { + // Return the flag PENDINGMSG_WAITDEFPROCESS + return 2; + } + } + + [ComImport, Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface IOleMessageFilter + { + [PreserveSig] + int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); + + [PreserveSig] + int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType); + + [PreserveSig] + int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType); + } + } +} \ No newline at end of file From af2820f48b2934e774608e6496c3642a889fb291 Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 16:49:37 -0300 Subject: [PATCH 06/11] Add ExcelDnaProject utility to set debugger options via DTE --- .../ExcelDna.AddIn.Tasks.csproj | 2 ++ .../Utils/ExcelDnaProject.cs | 35 +++++++++++++++++++ .../Utils/IExcelDnaProject.cs | 7 ++++ 3 files changed, 44 insertions(+) create mode 100644 Source/ExcelDna.AddIn.Tasks/Utils/ExcelDnaProject.cs create mode 100644 Source/ExcelDna.AddIn.Tasks/Utils/IExcelDnaProject.cs diff --git a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj index 615d4e85..1243807b 100644 --- a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj +++ b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj @@ -43,6 +43,8 @@ + + diff --git a/Source/ExcelDna.AddIn.Tasks/Utils/ExcelDnaProject.cs b/Source/ExcelDna.AddIn.Tasks/Utils/ExcelDnaProject.cs new file mode 100644 index 00000000..a0e39e5d --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/Utils/ExcelDnaProject.cs @@ -0,0 +1,35 @@ +using System.IO; + +namespace ExcelDna.AddIn.Tasks.Utils +{ + public class ExcelDnaProject : IExcelDnaProject + { + public bool TrySetDebuggerOptions(string projectName, string excelExePath, string excelAddInToDebug) + { + using (var dte = new DevToolsEnvironment()) + { + var project = dte.GetProjectByName(projectName); + if (project != null) + { + var configuration = project + .ConfigurationManager + .ActiveConfiguration; + + var startAction = configuration.Properties.Item("StartAction"); + var startProgram = configuration.Properties.Item("StartProgram"); + var startArguments = configuration.Properties.Item("StartArguments"); + + startAction.Value = 1; // Start external program + startProgram.Value = excelExePath; + startArguments.Value = string.Format(@"""{0}""", Path.GetFileName(excelAddInToDebug)); + + project.Save(string.Empty); + + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Source/ExcelDna.AddIn.Tasks/Utils/IExcelDnaProject.cs b/Source/ExcelDna.AddIn.Tasks/Utils/IExcelDnaProject.cs new file mode 100644 index 00000000..927b3dc5 --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/Utils/IExcelDnaProject.cs @@ -0,0 +1,7 @@ +namespace ExcelDna.AddIn.Tasks.Utils +{ + public interface IExcelDnaProject + { + bool TrySetDebuggerOptions(string projectName, string excelExePath, string excelAddInToDebug); + } +} \ No newline at end of file From 126fb2ab057fae30c240b12cdbfd267e2ec3de73 Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 17:51:11 -0300 Subject: [PATCH 07/11] Add SetDebuggerOptions MSBuild task implementation --- .../ExcelDna.AddIn.Tasks.csproj | 1 + .../SetDebuggerOptions.cs | 206 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 Source/ExcelDna.AddIn.Tasks/SetDebuggerOptions.cs diff --git a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj index 1243807b..69a6688a 100644 --- a/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj +++ b/Source/ExcelDna.AddIn.Tasks/ExcelDna.AddIn.Tasks.csproj @@ -43,6 +43,7 @@ + diff --git a/Source/ExcelDna.AddIn.Tasks/SetDebuggerOptions.cs b/Source/ExcelDna.AddIn.Tasks/SetDebuggerOptions.cs new file mode 100644 index 00000000..6e28a1ec --- /dev/null +++ b/Source/ExcelDna.AddIn.Tasks/SetDebuggerOptions.cs @@ -0,0 +1,206 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; +using ExcelDna.AddIn.Tasks.Utils; + +namespace ExcelDna.AddIn.Tasks +{ + public class SetDebuggerOptions : AbstractTask + { + private readonly IExcelDetector _excelDetector; + private readonly IExcelDnaProject _dte; + private BuildTaskCommon _common; + + public SetDebuggerOptions() + : this(new ExcelDetector(), new ExcelDnaProject()) + { + } + + public SetDebuggerOptions(IExcelDetector excelDetector, IExcelDnaProject dte) + : base("ExcelDnaSetDebuggerOptions") + { + if (excelDetector == null) + { + throw new ArgumentNullException("excelDetector"); + } + + if (dte == null) + { + throw new ArgumentNullException("dte"); + } + + _excelDetector = excelDetector; + _dte = dte; + } + + public override bool Execute() + { + try + { + LogDebugMessage("Running SetDebuggerOptions MSBuild Task"); + + LogDiagnostics(); + + FilesInProject = FilesInProject ?? new ITaskItem[0]; + LogDebugMessage("Number of files in project: " + FilesInProject.Length); + + var excelExePath = GetExcelPath(); + var addInForDebugging = GetAddInForDebugging(excelExePath); + + if (!_dte.TrySetDebuggerOptions(ProjectName, excelExePath, addInForDebugging)) + { + LogWarning("DNA" + "DTE".GetHashCode(), "Unable to set the debugger options within Visual Studio."); + } + } + catch (Exception ex) + { + LogWarning("DNA" + ex.GetType().Name.GetHashCode(), ex.Message); + } + + // Setting the debugger options is not essential to the build process, thus if anything + // goes wrong, we'll report errors and warnings, but will let the build continue + return true; + } + + private string GetExcelPath() + { + var excelExePath = ExcelExePath; + + try + { + if (string.IsNullOrWhiteSpace(excelExePath)) + { + if (!_excelDetector.TryFindLatestExcel(out excelExePath)) + { + LogWarning("DNA" + "EXCEL.EXE".GetHashCode(), "Unable to find path where EXCEL.EXE is located"); + return excelExePath; + } + } + + if (!File.Exists(excelExePath)) + { + LogWarning("DNA" + "EXCEL.EXE".GetHashCode(), + "EXCEL.EXE not found on disk at location " + excelExePath); + } + } + finally + { + LogMessage("EXCEL.EXE path for debugging: " + excelExePath); + } + + return excelExePath; + } + + private string GetAddInForDebugging(string excelExePath) + { + var addInForDebugging = AddInForDebugging; + + try + { + if (string.IsNullOrWhiteSpace(addInForDebugging)) + { + if (!TryGetExcelAddInForDebugging(excelExePath, out addInForDebugging)) + { + LogWarning("DNA" + "ADDIN".GetHashCode(), "Unable to find add-in to Debug"); + } + } + } + finally + { + LogMessage("Add-In for debugging: " + addInForDebugging); + } + + return addInForDebugging; + } + + private bool TryGetExcelAddInForDebugging(string excelExePath, out string addinForDebugging) + { + addinForDebugging = null; + + Bitness excelBitness; + if (!_excelDetector.TryFindExcelBitness(excelExePath, out excelBitness)) + { + return false; + } + + _common = new BuildTaskCommon(FilesInProject, OutDirectory, FileSuffix32Bit, FileSuffix64Bit); + + var outputBuildItems = _common.GetBuildItemsForDnaFiles(); + + var firstAddIn = outputBuildItems.FirstOrDefault(); + if (firstAddIn == null) return false; + + switch (excelBitness) + { + case Bitness.Bit32: + { + addinForDebugging = firstAddIn.OutputXllFileNameAs32Bit; + return true; + } + case Bitness.Bit64: + { + addinForDebugging = firstAddIn.OutputXllFileNameAs64Bit; + return true; + } + default: + { + return false; + } + } + } + + /// + /// The name of the project being compiled + /// + [Required] + public string ProjectName { get; set; } + + /// + /// The path to EXCEL.EXE that should be used for debugging + /// This overrides the automatic detection of the latest Excel installed + /// + public string ExcelExePath { get; set; } + + /// + /// The path to .XLL file name that should be used for debugging + /// This overrides the automatic detection depending on Excel's bitness + /// + public string AddInForDebugging { get; set; } + + /// + /// The list of files in the project marked as Content or None + /// + [Required] + public ITaskItem[] FilesInProject { get; set; } + + /// + /// The directory in which the built files were written to + /// + [Required] + public string OutDirectory { get; set; } + + /// + /// The name suffix for 32-bit .dna files + /// + public string FileSuffix32Bit { get; set; } + + /// + /// The name suffix for 64-bit .dna files + /// + public string FileSuffix64Bit { get; set; } + + private void LogDiagnostics() + { + LogDebugMessage("----Arguments----"); + LogDebugMessage("ProjectName: " + ProjectName); + LogDebugMessage("ExcelExePath: " + ExcelExePath); + LogDebugMessage("AddInForDebugging: " + AddInForDebugging); + LogDebugMessage("FilesInProject: " + (FilesInProject ?? new ITaskItem[0]).Length); + LogDebugMessage("OutDirectory: " + OutDirectory); + LogDebugMessage("FileSuffix32Bit: " + FileSuffix32Bit); + LogDebugMessage("FileSuffix64Bit: " + FileSuffix64Bit); + LogDebugMessage("-----------------"); + } + } +} From 5863221fcce5e272bcc9b7f5c663aed75e8c81cd Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sat, 14 Oct 2017 18:04:56 -0300 Subject: [PATCH 08/11] Update ExcelDna.AddIn.targets to support the new SetDebuggerOptions task --- .../tools/ExcelDna.AddIn.targets | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/Package/ExcelDna.AddIn/tools/ExcelDna.AddIn.targets b/Package/ExcelDna.AddIn/tools/ExcelDna.AddIn.targets index 8bf0b381..7a5a07c3 100644 --- a/Package/ExcelDna.AddIn/tools/ExcelDna.AddIn.targets +++ b/Package/ExcelDna.AddIn/tools/ExcelDna.AddIn.targets @@ -1,7 +1,8 @@ - + + true @@ -18,10 +19,11 @@ + ExcelDnaSetDebuggerOptions; $(BuildDependsOn); ExcelDnaBuild; ExcelDnaPack; @@ -41,6 +43,7 @@ Default values for configuration properties - in case they were not set in via .props file or command-line --> + true true true true @@ -59,6 +62,31 @@ true + + + + + + + + + + + + + + + @@ -83,7 +111,6 @@ PackIsEnabled="$(RunExcelDnaPack)" PackedFileSuffix="$(ExcelDnaPackXllSuffix)"> - + + + + true + + + + + + + + From 99420f6cbde99e2417c658427818c1e62cb383ef Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sun, 15 Oct 2017 09:15:18 -0300 Subject: [PATCH 10/11] Remove debug settings from install PowerShell of ExcelDna.AddIn --- Package/ExcelDna.AddIn/tools/install.ps1 | 35 ------------------------ 1 file changed, 35 deletions(-) diff --git a/Package/ExcelDna.AddIn/tools/install.ps1 b/Package/ExcelDna.AddIn/tools/install.ps1 index 3ecc0d78..c5580602 100644 --- a/Package/ExcelDna.AddIn/tools/install.ps1 +++ b/Package/ExcelDna.AddIn/tools/install.ps1 @@ -2,7 +2,6 @@ param($installPath, $toolsPath, $package, $project) Write-Host "Starting ExcelDna.AddIn install script" $dteVersion = $project.DTE.Version - $isBeforeVS2015 = ($dteVersion -lt 14.0) $isFSharpProject = ($project.Type -eq "F#") $projectName = $project.Name @@ -50,40 +49,6 @@ param($installPath, $toolsPath, $package, $project) } } - if ($isFSharpProject -and $isBeforeVS2015) - { - # I don't know how to do this for F# projects on old VS - Write-Host "`t*** Unable to configure Debug startup setting.`r`n`t Please configure manually to start Excel when debugging.`r`n`t See readme.txt for details." - } - else - { - # Find Debug configuration and set debugger settings. - $exeValue = Get-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\Excel.XLL\shell\Open\command -name "(default)" - if ($exeValue -match "`".*`"") - { - $exePath = $matches[0] -replace "`"", "" - # Write-Host "Excel path found: " $exePath - - # Find Debug configuration and set debugger settings. - $debugProject = $project.ConfigurationManager | Where-Object {$_.ConfigurationName -eq "Debug"} - if ($null -ne $debugProject) - { - # Write-Host "Start Action " $debugProject.Properties.Item("StartAction").Value - if ($debugProject.Properties.Item("StartAction").Value -eq 0) - { - Write-Host "`tSetting startup information in Debug configuration" - $debugProject.Properties.Item("StartAction").Value = 1 - $debugProject.Properties.Item("StartProgram").Value = $exePath - $debugProject.Properties.Item("StartArguments").Value = "`"${projectName}-AddIn.xll`"" - } - } - } - else - { - Write-Host "`tExcel path not found!" - } - } - Write-Host "`tAdding build targets to the project" # This is the MSBuild targets file to add From 69d7e17d56e3ea6db8732de685270b87ae1046bb Mon Sep 17 00:00:00 2001 From: Caio Proiete Date: Sun, 15 Oct 2017 09:23:21 -0300 Subject: [PATCH 11/11] Remove debug settings from uninstall PowerShell of ExcelDna.AddIn --- Package/ExcelDna.AddIn/tools/uninstall.ps1 | 31 ---------------------- 1 file changed, 31 deletions(-) diff --git a/Package/ExcelDna.AddIn/tools/uninstall.ps1 b/Package/ExcelDna.AddIn/tools/uninstall.ps1 index 96727568..96b844f1 100644 --- a/Package/ExcelDna.AddIn/tools/uninstall.ps1 +++ b/Package/ExcelDna.AddIn/tools/uninstall.ps1 @@ -2,7 +2,6 @@ param($installPath, $toolsPath, $package, $project) Write-Host "Starting ExcelDna.AddIn uninstall script" $dteVersion = $project.DTE.Version - $isBeforeVS2015 = ($dteVersion -lt 14.0) $isFSharpProject = ($project.Type -eq "F#") $projectName = $project.Name @@ -28,36 +27,6 @@ param($installPath, $toolsPath, $package, $project) } } - - if ($isFSharpProject -and $isBeforeVS2015) - { - # No Debug information was set. - } - else - { - # Clean Debug settings - $exeValue = Get-ItemProperty -Path Registry::HKEY_CLASSES_ROOT\Excel.XLL\shell\Open\command -name "(default)" - if ($exeValue -match "`".*`"") - { - $exePath = $matches[0] -replace "`"", "" - - # Find Debug configuration and set debugger settings. - $debugProject = $project.ConfigurationManager | Where-Object {$_.ConfigurationName -eq "Debug"} - if ($null -ne $debugProject) - { - if (($debugProject.Properties.Item("StartAction").Value -eq 1) -and - ($debugProject.Properties.Item("StartArguments").Value -match "\.xll")) - { - Write-Host "`tClearing Debug start settings" - $debugProject.Properties.Item("StartAction").Value = 0 - $debugProject.Properties.Item("StartProgram").Value = "" - $debugProject.Properties.Item("StartArguments").Value = "" - } - } - } - } - - Write-Host "`Removing build targets from the project" # Need to load MSBuild assembly if it's not loaded yet