How can I open AutoCAD 2015 through the .NET API - c#

I've been browsing for a good hour and have yet to find something that would help with this. I'm working on opening AutoCAD from the .NET API in VS2013 using C#, but for some reason, I can never get AutoCAD to actually launch. I'm using the following code:
using System;
using System.Runtime.InteropServices;
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
namespace IOAutoCADHandler
{
public static class ACADDocumentManagement
{
[CommandMethod("ConnectToAcad")]
public static void ConnectToAcad()
{
AcadApplication acAppComObj = null;
// no version number so it will run with any version
const string strProgId = "AutoCAD.Application";
// Get a running instance of AutoCAD
try
{
acAppComObj = (AcadApplication)Marshal.GetActiveObject(strProgId);
}
catch // An error occurs if no instance is running
{
try
{
// Create a new instance of AutoCAD
acAppComObj = (AcadApplication)Activator.CreateInstance(Type.GetTypeFromProgID(strProgId), true);
}
catch //// STOPS HERE
{
// If an instance of AutoCAD is not created then message and exit
// NOTE: always shows this box and never opens AutoCAD
System.Windows.Forms.MessageBox.Show("Instance of 'AutoCAD.Application'" +
" could not be created.");
return;
}
}
// Display the application and return the name and version
acAppComObj.Visible = true;
System.Windows.Forms.MessageBox.Show("Now running " + acAppComObj.Name +
" version " + acAppComObj.Version);
// Get the active document
AcadDocument acDocComObj;
acDocComObj = acAppComObj.ActiveDocument;
// Optionally, load your assembly and start your command or if your assembly
// is demandloaded, simply start the command of your in-process assembly.
acDocComObj.SendCommand("(command " + (char)34 + "NETLOAD" + (char)34 + " " +
(char)34 + #"C:\Users\Administrator\Documents\All Code\main-libraries\IOAutoCADHandler\bin\Debug\IOAutoCADHandler.dll" + (char)34 + ") ");
acDocComObj.SendCommand("DRAWCOMPONENT");
}
}
Unfortunately, it always stops at the nested catch statement and always displays the popup box without opening AutoCAD. Any suggestions on how to at least make AutoCAD open for me?
EDIT: Error message

The issue is you're coding (correctly) to the AutoCAD interop interface. I recommend against that (due to potential version changes).
The other issue is that the documentation for AutoCAD plugins using the newer .net api is for plugins when AutoCAD is already running.
Final issue could be that the program Id of AutCAD is a mystery. I have resorted to making that a configurable setting, but default to "AutoCAD.Application", which will take the currently registered AutoCAD.Application on the production machine. If there are multiple versions installed on the machine and you want to be specific, then you could append the version number (which you'll need to research) to the ProgID like: "AutoCAD.Application.19", or "AutoCAD.Application.20" for 2015.
For the first issue, one technique is to use dynamics for the autoCad objects, particularly for creating instances. I have used the ObjectARX api for creating my application in a dummy project, and then switching to dynamics when I'm happy with the properties and method names.
In a standalone .Net application that starts AutoCAD you could use something like:
// I comment these out in production
//using Autodesk.AutoCAD.Interop;
//using Autodesk.AutoCAD.Interop.Common;
//...
//private static AcadApplication _application;
private static dynamic _application;
static string _autocadClassId = "AutoCAD.Application";
private static void GetAutoCAD()
{
_application = Marshal.GetActiveObject(_autocadClassId);
}
private static void StartAutoCad()
{
var t = Type.GetTypeFromProgID(_autocadClassId, true);
// Create a new instance Autocad.
var obj = Activator.CreateInstance(t, true);
// No need for casting with dynamics
_application = obj;
}
public static void EnsureAutoCadIsRunning(string classId)
{
if (!string.IsNullOrEmpty(classId) && classId != _autocadClassId)
_autocadClassId = classId;
Log.Activity("Loading Autocad: {0}", _autocadClassId);
if (_application == null)
{
try
{
GetAutoCAD();
}
catch (COMException ex)
{
try
{
StartAutoCad();
}
catch (Exception e2x)
{
Log.Error(e2x);
ThrowComException(ex);
}
}
catch (Exception ex)
{
ThrowComException(ex);
}
}
}

When there are several versions of AutoCAD installed on a computer, creating an instance with the ProgID "AutoCAD.Application" will run the latest version started on this computer by the current user. If the version of the Interop assemblies used does not match the version that is starting, you'll get a System.InvalidCastException with an HRESULT 0x80004002 (E_NOINTERFACE).
In your specific case, the {070AA05D-DFC1-4E64-8379-432269B48B07} IID in your error message is the GUID for the AcadApplicationinterface in R19 64-bit (AutoCAD 2013 & 2014). So there is an AutoCAD 2013 or 2014 that is starting, and you cannot cast this COM object to a 2015 type because 2015 is R20 (not binary compatible).
To avoid that, you can add a specific version to your ProgID (like "AutoCAD.Application.20" for AutoCAD 2015 (R20.0) to 2016 (R20.1)) to start the version matching your Interop assemblies or you can use late binding (eg. remove your references to Autodesk.AutoCAD.Interop* and use the dynamic keyword instead of the AutoCAD types).
In the last case, you will lost autocompletion, but your program will work with all the versions of AutoCAD.
Check also 32-bit vs 64-bit because TypeLib/Interop assemblies are not the same.

I open the application in a much straight-forward way. First, be sure to reference the correct type library. The one I am using is AutoCAD 2014 Type Library, located at:
c:\program files\common files\autodesk shared\acax19enu.tlb
To initialize the application:
using AutoCAD;
namespace test
{
class Program
{
static void Main(string[] args)
{
AutoCAD.AcadApplication app;
app = new AcadApplication();
app.Visible = true;
Console.Read();
}
}
}

Try this:
"sourcefile" is the original file
"newfile" is the new file
[CommandMethod("ModifyAndSaveas", CommandFlags.Redraw | CommandFlags.Session)]
public void ModifyAndSaveAs()
{
Document acDoc = Application.DocumentManager.Open(sourcefile);
Database acDB = acDoc.Database;
Transaction AcTran = acDoc.Database.TransactionManager.StartTransaction();
using (DocumentLock acLckDoc = acDoc.LockDocument())
{
using (AcTran)
{
BlockTable acBLT = (BlockTable)AcTran.GetObject(acDB.BlockTableId, OpenMode.ForRead);
BlockTableRecord acBLTR = (BlockTableRecord)AcTran.GetObject(acBLT[BlockTableRecord.ModelSpace], OpenMode.ForRead);
var editor = acDoc.Editor;
var SelectionSet = editor.SelectAll().Value;
foreach (ObjectId id in SelectionSet.GetObjectIds())
{
Entity ent = AcTran.GetObject(id, OpenMode.ForRead) as Entity;
//modify entities
}
AcTran.Commit();
}
}
acDB.SaveAs(newfile, DwgVersion.AC1021);
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Tekkit
{
class Program
{
static void Main(string[] args)
{
//make sure to add last 2 using statements
ProcessStartInfo start = new ProcessStartInfo("calc.exe");
Process.Start(start);//starts the process
}
}
}

Related

How can I install Selenium’s webdriver.exe in [PATH] htrough the project’s published installer?

I have a Windows Forms application that uses Selenium. I have multiple production clients that need to run this application and I’ve noticed that in every new client (and also when I need to update the webdriver) I need to copy and paste the .exe to the [PATH] location (%USERPROFILE%\AppData\Local\Microsoft\WindowsApps) and I want to automate that with the setup file that gets generated by Visual Studio every time I publish the application.
I’ve found that you can install an extension called “Microsoft Visual Studio Installer Project”, include the .exe file on it and either make a new Form that’ll check if the webdriver is in place and if it’s not to copy it, or I can change the [PATH] of my IWebDriver object in order to reflect the new path of this file. As a bonus you can also add the the desktop icon.
But first I want to know if there’s a way to publish this webdriver.exe file to it’s proper address through the “Publish wizard” parameters before I start looking for workarounds.
This worked for my use case, for context, I'm using a windows forms project targeting .NET (framework) 4.7.1. these are snippets from my events "load" and "show" formated as a different function. I only included the logic behind the file check, download and unzip with overwite. Since the System.IO.Compression.ZipFile class for this version of .NET doesn't natively support overwrite files, I used Ionic's DotNetZip package downloaded from NuGet.
using Ionic.Zip;
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
private void DriverCheck(){
string edge, edgeVersion, edgeDriverPath, edgeDriver, edgeDriverVersion;
edge = #"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe";
edgeVersion = FileVersionInfo.GetVersionInfo(edge).FileVersion;
edgeDriverPath = Environment.GetEnvironmentVariable("LocalAppData") + "\\Microsoft\\WindowsApps\\";
edgeDriver = edgeDriverPath + "msedgedriver.exe";
try
{
edgeDriverVersion = FileVersionInfo.GetVersionInfo(edgeDriver).FileVersion;
}
catch
{
edgeDriverVersion = null;
}
if (!File.Exists(edgeDriver) || edgeVersion != edgeDriverVersion)
{
try
{
using (var client = new WebClient())
{
string winver;
if (Environment.Is64BitProcess)
{
winver = "64";
}
else
{
winver = "32";
}
string zipPath = edgeDriverPath + "edgedriver_win64.zip";
client.DownloadFile("https://msedgedriver.azureedge.net/" + edgeVersion + "/edgedriver_win" + winver + ".zip", zipPath);
using (ZipFile zip = ZipFile.Read(zipPath))
{
foreach (ZipEntry temp in zip)
{
temp.Extract(edgeDriverPath, ExtractExistingFileAction.OverwriteSilently);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error downloading webdriver:\n" + ex.Message);
Application.Exit();
}
}
}

COM+ exception "Class Not Registered" after Microsoft update in February 2020 [duplicate]

This question already exists:
COM+ component - Class not registered error [closed]
Closed 2 years ago.
I have an application which is in Delphi and interacts with other .NET application through old COM+ component. The component is invoked from Delphi code and there was no issue until Microsoft introduced some security patches on Feb 11, 2020 (this year).
The issue it throws now is "Class not registered" After searching a lot on Google, I have tried the following things :
As this COM+ component is written in .NET (ComVisible attribute, had an assumption that it needs to be registered again. So tried to register it usign RegAsm using :
Regasm
But it didn't work though
Confirmed if assembly is in GAC. it is placed there indeed.
List item
Deleting registration of the DLL and then registering using regasm /u but that also provided no luck.
Some blogs over google suggested about COM+ security. Tried playing with that to.
Disabled the COM+ component to test whether COM+ component has any impact and yes indeed it turned to different error. Something like COmponent doesn't exist.
Someone suggested to run DCOMCNFG and traverse into DCOM config tree and press "Yes" if there are warnings to record the anomalies in registry. Still no luck.
Opened up Registry using RegEdit and tried to permit full control on relevant registries including Microsoft Ole. Still didn't work
Also, there were some thoughts around COM security, it was also checked and was permitted well.
The caveat is if we revert the microsoft patches, it works well again. I matched all the security settings before and after the patches which seems same.
PLEASE suggest if someone has sound knowledge about it.
Adding more details:
COM+ Component
COM component properties
Here is the .NET COM component code:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.EnterpriseServices;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Text;
using System.Threading;
using System.Xml.XPath;
using Acclipse.Config;
using Acclipse.FileSystem.Shared;
using Aspose.Words;
[assembly: ApplicationActivation(ActivationOption.Library)]
[assembly: ApplicationAccessControl(false)]
[assembly: ApplicationName("Acclipse FileSystem Client")]
[assembly: Description("Client to communicate with the Acclipse FileSystem server.")]
namespace Acclipse.FileSystem
{
class Singleton
{
private static Singleton m_Instance = new Singleton();
private Singleton()
{
Trace.WriteLine("Singleton.Create");
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
private IRemotedFS m_IRemotedFS;
public static Singleton Instance
{
get
{
return m_Instance;
}
}
internal static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Trace.WriteLine("CurrentDomain_AssemblyResolve", "Acclipse.FileSystem");
try
{
string[] parts = args.Name.Split(',');
string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), string.Format("{0}.dll", parts[0]));
//Trace.WriteLine(string.Format("Loading resolved assembly at {0}", path), "Acclipse.Licencing");
Assembly a = Assembly.LoadFile(path);
Trace.WriteLine(string.Format("Assembly is {0}, looking for {1}", a.FullName, args.Name), "Acclipse.FileSystem");
if (a.FullName != args.Name)
{
throw new ApplicationException("Unable to load correct assembly");
}
return a;
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("Failed to resolve with error {0}", ex.Message), "Acclipse.FileSystem");
}
return Assembly.GetExecutingAssembly();
}
public IRemotedFS FS
{
get
{
Trace.WriteLine("Get IRemotedFS", "Acclipse.FileSystem");
if (m_IRemotedFS == null)
{
try
{
Trace.WriteLine("Creating IRemotedFS", "Acclipse.FileSystem");
Trace.WriteLine("Configfile at " + Path.GetFullPath("Config.file"), "Acclipse.FileSystem");
ConfigFile config = ConfigFile.FromFile("Config.file");
Trace.WriteLine("Loading url", "Acclipse.FileSystem");
string url = config["FileStore.Url"];
if (string.IsNullOrEmpty(url))
{
throw new ApplicationException("Url of server is not configured in Config.file");
}
Trace.WriteLine(string.Format("Url is {0}", url));
TcpClientChannel chan = new TcpClientChannel();
ChannelServices.RegisterChannel(chan, false);
m_IRemotedFS = Activator.GetObject(typeof(IRemotedFS), url) as IRemotedFS;
if (m_IRemotedFS == null)
{
Trace.WriteLine("Oops, RemoteFS is null!!");
}
}
catch (Exception e)
{
Trace.WriteLine(string.Format("Error in getting FS *** {0} - {1}", e.Message, e.StackTrace));
throw;
}
}
Trace.WriteLine("..Done");
return m_IRemotedFS;
}
internal set
{
m_IRemotedFS = value;
}
}
}
[ComVisible(true)]
[ProgId("Acclipse.FileSystem.SqlZipClient.SqlZipFileSystemRemote")]
[Guid("5A940543-24EF-4f31-B45B-6832C8211986")]
[ClassInterface(ClassInterfaceType.None)]
[JustInTimeActivation(true)]
public class SqlZipFileSystemRemote : ServicedComponent, IFileSystem
{
private IConfigFile m_Config;
public SqlZipFileSystemRemote()
{
Trace.WriteLine("SqlZipFileSystemRemote Created", "Acclipse.FileSystem");
m_Config = DefaultConfigFile.FromFile("Config.file");
}
static SqlZipFileSystemRemote()
{
Trace.WriteLine("Set Aspose.Words license info", "Acclipse.FileSystem");
try
{
new License().SetLicense("Acclipse.FileSystem.SqlZipClient.Aspose.Words.lic");
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("Exception encountered setting license details: {0}", ex.StackTrace), "Acclipse.FileSystem");
throw;
}
Trace.WriteLine("Done", "Acclipse.FileSystem");
}
internal void AttachConfigFile(IConfigFile config)
{
this.m_Config = config;
}
}
}

.NET reference design for GUI app with built-in interactive terminal console (e.g. SublimeText, Visual Studio)

I'm trying to build a GUI app that has an interactive console, much like the one found in SublimeText.
I hope it is a valid question because it seems to be "a practical, answerable problem that is unique to software development".
In short, I see huge benefits having an interactive console inside a GUI app for
debugging, probing internal variables at runtime
logging
quick configuration changes
However, I have not come across any existing open-source applications that uses such a design.
I'm hoping someone has done it before and can share his/her design approach.
While I do have a semi-working solution using reflection and invoke in .NET, it is limited to only function calls and I'm not able to probe into nested internal variables (e.g. object.property.property).
To make the question more specific, these are the problems I'm facing:
Not easily extensible (Need to wire every new GUI command to a console command, vice-versa), any design tips? Routed commands (I could not find a useful example either)?
How to execute dynamic code that can access all existing object instances in the entire .NET app?
Thank you.
So here comes the code which worked for me:
namespace ReflectionsTest
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
//Events excluded
private void ExecuteCommand(string command)
{
string cmd = "";
cmd += #"using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Linq;
using Microsoft.CSharp;
using System.Reflection;
using ReflectionsTest;";
// i included a using statement for every namespace i want to adress direct
cmd += #"namespace ReflectionConsole
{
public class RuntimeExecution
{
public static void Main(MainForm parent, TextBox output, FieldInfo[] privateFields)
{
try {";
//the code in a trycatch because i can send every error to a specific output defined as output parameter
cmd += command;
cmd += "}catch (Exception ex) { if(output != null){" +
"output.Text += ex.Message + \"\\n\\r\";"
+"}else{MessageBox.Show(ex.Message);}}}}}";
try {
ExecuteCSharp(cmd);
}
catch (Exception ex) {
textBox2.Text += ex.Message + "\n\r";
}
}
private void ExecuteCSharp(string code)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
List<AssemblyName> assemblys = (Assembly.GetExecutingAssembly().GetReferencedAssemblies()).ToList<AssemblyName>();
foreach (var item in assemblys) {
parameters.ReferencedAssemblies.Add(item.Name + ".dll");
}
string t = Assembly.GetExecutingAssembly().GetName().Name;
parameters.ReferencedAssemblies.Add(t + ".exe");
//Here you have to reference every assembly the console wants access
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.HasErrors) {
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in results.Errors) {
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
else {
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("ReflectionConsole.RuntimeExecution");
MethodInfo main = program.GetMethod("Main");
FieldInfo[] fields = this.GetType().GetFields(
BindingFlags.NonPublic |
BindingFlags.Instance);
//if everything is correct start the method with some arguments:
// containing class, output, private fields of the containing class for easier access
main.Invoke(null, new object[]{this, textBox2, fields});
}
}
}
}
Some Explanations:
You have pass the highest class of your program which contains everything else, because it is easier to access members than parent objects.
public objects you can access like parent.obect1.Text = "textXYZ";
private objects you can access by name. These objects are listed in privateFields.
for the subclasses you have two options:
change the first and third parameter when calling main.Invoke([...])
or
recollect the private fields.
as Suggestion you could include a .dll in the command which already gives you methods to achieve this much faster.
For example GetValueFromFieldByName(object class, string name, Type resultType)
I hope that is what you've hoped for ^^

Create solution file(.sln) and project files(.csproj) dynamically

I am creating code generation tool (auto code generation based on table structure) as a Windows forms application in Visual Studio 2012 using .NET Framework 4.0. It's generating the portable object, controller, WCF services and business logic code files.
All code files bundle in the appropriate project and all project bundle in one solution. The solution and projects need to create dynamically through program.
I have tried to create the solution and project using Visual Studio Add-in project. It is working fine in Add-In project (separate solution). The OnConnection method call automatically in Add-in project. Now I want to implements this in my code generation tool. While debugging in Add-In project the application variable shown like COM object.
I am tried to pass the value for OnConnection method from code generation tool, it throws an error (I passed this object for application variable). I really don't know how to call this method from my code generation tool. Anyone help this?
Code
private DTE2 _applicationObject;
private AddIn _addInInstance;
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
createProjectsFromTemplates(_applicationObject);
}
public void createProjectsFromTemplates(DTE2 dte)
{
try
{
Solution2 soln = (Solution2)dte.Solution;
string csTemplatePath;
string csPrjPath = "SamplePath\\TestCreateProject";
csTemplatePath = soln.GetProjectTemplate("WpfApplication.zip", "CSharp");
System.Windows.Forms.MessageBox.Show("C# template path: " + csTemplatePath);
soln.AddFromTemplate(csTemplatePath, csPrjPath, "NewWCFCSharpAutoGeneratorProject", false);
Project prj;
ProjectItem prjItem;
String itemPath;
// Point to the first project (the Visual Basic project).
prj = soln.Projects.Item(1);
prjItem = prj.ProjectItems.AddFromFileCopy("SampelCSharp.cs");
}
catch (System.Exception ex)
{
System.Windows.Forms.MessageBox.Show("ERROR: " + ex.Message);
}
}
You can instantiate a VS from the host application and generate the files. Hope that will work. The below code works well for me.
Use the following namespaces to get work the below given code.
Namespaces:
using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
Code:
EnvDTE80.DTE2 dte2;
dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.11.0");
Connect objConnect = new Connect();
Array objArray = null;
objConnect.OnConnection(dte2, ext_ConnectMode.ext_cm_UISetup, null, ref objArray);
I got this reference it is really useful.
http://rcos.rpi.edu/projects/unmake/commit/programmatically-launch-devenv-generate-a-solution-and-save-it/
You can use this. This is for .cs project files and framewwork above .NET 2.0 versions. VB project sources are not compatible.
protected void Build(string project)
{
Engine engine = new Engine();
BuildPropertyGroup properties = new BuildPropertyGroup();
properties.SetProperty(#"Configuration", #"Debug");
// Point to the path that contains the .NET Framework 2.0 CLR and tools
engine.BinPath = #"c:\windows\microsoft.net\framework\v3.5";
// Instantiate a new FileLogger to generate build log
FileLogger logger = new FileLogger();
// Set the logfile parameter to indicate the log destination
string str = #"logfile=D:\temp";
str += project.Substring(project.LastIndexOf("\\"), project.LastIndexOf(".") - project.LastIndexOf("\\")) + ".log";
logger.Parameters = str;
// Register the logger with the engine
engine.RegisterLogger(logger);
// Build a project file
bool success = engine.BuildProjectFile(project, new string[] { "build" }, properties);
//Unregister all loggers to close the log file
engine.UnregisterAllLoggers();
using (BinaryWriter writer = new BinaryWriter(File.Open(#"D:\temp\Prj.log", FileMode.Append)))
{
if (success)
{
writer.Write("\nBuild Success :" + project.Substring(project.LastIndexOf("\\")));
}
else
{
writer.Write("\nBuild Fail :" + project.Substring(project.LastIndexOf("\\")));
}
}
}

Using Microsoft.Build.Evaluation to publish a database project (.sqlproj)

I need to be able to publish an SSDT project programmatically. I am looking at using Microsoft.Build to do so but can not find any documentation. It seems pretty simple to create the .dacpac, but how would I either publish to an existing database or at the very least to a .sql file. The idea is to have it do what it does when I right click on the project and select publish. It should compare with a selected database and generate an upgrade script.
This is what I have so far to create the .dacpac:
partial class DBDeploy
{
Project project;
internal void publishChanges()
{
Console.WriteLine("Building project " + ProjectPath);
Stopwatch sw = new Stopwatch();
sw.Start();
project = ProjectCollection.GlobalProjectCollection.LoadProject(ProjectPath);
project.Build();
//at this point the .dacpac is built and put in the debug folder for the project
sw.Stop();
Console.WriteLine("Project build Complete. Total time: {0}", sw.Elapsed.ToString());
}
}
Essentially I am trying to do what this MSBuild Example shows but in code.
Sorry that this is all I have. The doecumentation on the Build classes is very poor. Any help would be appreciated.
Thanks.
I had to do something similar to this because VSDBCMD which we previously used does not deploy to SQL Server 2012 and we needed to support it. What I found was the Microsoft.SqlServer.Dac assembly which seems to come as part of the SQL Server data tools (http://msdn.microsoft.com/en-us/data/tools.aspx)
When you run this on the client machine you will need the full version of the .NET 4 framework and the SQL CLR types and SQL T-SQL ScriptDOM pack found here: http://www.microsoft.com/en-us/download/details.aspx?id=29065
Code below is from a mockup I made for testing the new deployment method and deploys a given .dacpac file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Dac;
using System.IO;
namespace ConsoleApplication3
{
class Program
{
private static TextWriter output = new StreamWriter("output.txt", false);
static void Main(string[] args)
{
Console.Write("Connection String:");
//Class responsible for the deployment. (Connection string supplied by console input for now)
DacServices dbServices = new DacServices(Console.ReadLine());
//Wire up events for Deploy messages and for task progress (For less verbose output, don't subscribe to Message Event (handy for debugging perhaps?)
dbServices.Message += new EventHandler<DacMessageEventArgs>(dbServices_Message);
dbServices.ProgressChanged += new EventHandler<DacProgressEventArgs>(dbServices_ProgressChanged);
//This Snapshot should be created by our build process using MSDeploy
Console.WriteLine("Snapshot Path:");
DacPackage dbPackage = DacPackage.Load(Console.ReadLine());
DacDeployOptions dbDeployOptions = new DacDeployOptions();
//Cut out a lot of options here for configuring deployment, but are all part of DacDeployOptions
dbDeployOptions.SqlCommandVariableValues.Add("debug", "false");
dbServices.Deploy(dbPackage, "trunk", true, dbDeployOptions);
output.Close();
}
static void dbServices_Message(object sender, DacMessageEventArgs e)
{
output.WriteLine("DAC Message: {0}", e.Message);
}
static void dbServices_ProgressChanged(object sender, DacProgressEventArgs e)
{
output.WriteLine(e.Status + ": " + e.Message);
}
}
}
This seems to work on all versions of SQL Server from 2005 and up. There is a similar set of objects available in Microsoft.SqlServer.Management.Dac, however I believe this is in the previous version of DACFx and is not included in the latest version. So use the latest version if you can.
We need a way tell msbuild how and where to publish. Open your project in Visual Studio and begin to Publish it. Enter all needed info in the dialog, including your DB connection info and any custom SQLCMD variable values. Save Profile As... to a file, e.g. Northwind.publish.xml. (You may then Cancel.) Now we can use this and the project file to build and publish:
// Create a logger.
FileLogger logger = new FileLogger();
logger.Parameters = #"logfile=Northwind.msbuild.log";
// Set up properties.
var projects = ProjectCollection.GlobalProjectCollection;
projects.SetGlobalProperty("Configuration", "Debug");
projects.SetGlobalProperty("SqlPublishProfilePath", #"Northwind.publish.xml");
// Load and build project.
var dbProject = ProjectCollection.GlobalProjectCollection.LoadProject(#"Northwind.sqlproj");
dbProject.Build(new[]{"Build", "Publish"}, new[]{logger});
This can take awhile and may appear to get stuck. Be patient. :)
You should use SqlPackage.exe to publish your dacpac.
SqlPackage.exe
/Action:Publish
/SourceFile:C:/file.dacpac
/TargetConnectionString:[Connection string]
Also instead of passing too many parameters you could save your settings into DAC Publish Profile (this can be done from visual studio)
I wanted to build and publish a database based on a sqlproj file and log helpful information to console. Here's what I arrived at:
using Microsoft.Build.Framework;
using Microsoft.Build.Execution;
public void UpdateSchema() {
var props = new Dictionary<string, string> {
{ "UpdateDatabase", "True" },
{ "PublishScriptFileName", "schema-update.sql" },
{ "SqlPublishProfilePath", "path/to/publish.xml") }
};
var projPath = "path/to/database.sqlproj";
var result = BuildManager.DefaultBuildManager.Build(
new BuildParameters { Loggers = new[] { new ConsoleLogger() } },
new BuildRequestData(new ProjectInstance(projPath, props, null), new[] { "Publish" }));
if (result.OverallResult == BuildResultCode.Success) {
Console.WriteLine("Schema update succeeded!");
}
else {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Schema update failed!");
Console.ResetColor();
}
}
private class ConsoleLogger : ILogger
{
public void Initialize(IEventSource eventSource) {
eventSource.ErrorRaised += (sender, e) => {
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e.Message);
Console.ResetColor();
};
eventSource.MessageRaised += (sender, e) => {
if (e.Importance != MessageImportance.Low)
Console.WriteLine(e.Message);
};
}
public void Shutdown() { }
public LoggerVerbosity Verbosity { get; set; }
public string Parameters { get; set; }
}
This is for .NET 4 and above. Be sure and include assembly references to Microsoft.Build and Microsoft.Build.Framework.

Categories