Set Script Task code dynamically in SSIS 2012 - c#

In my application, a script task is created dynamically.
In SQL Server 2008's implementation of SSIS, the following method worked fine.
private void SetSourceCode(ScriptTask scriptTask, string code, string codeName)
{
string fileName = "ScriptMain.vb";
string language = "VisualBasic";
string proj = ".vbproj";
scriptTask.ScriptLanguage = VSTAScriptLanguages.GetDisplayName(language);
scriptTask.ScriptingEngine.InitNewScript(language,
scriptTask.ScriptProjectName, proj);
scriptTask.ScriptingEngine.ShowDesigner(false);
scriptTask.ScriptingEngine.AddCodeFile(fileName, code);
if (!scriptTask.ScriptingEngine.Build())
throw new Exception("Failed to build vb script code: " + codeName);
scriptTask.ScriptingEngine.SaveScriptToStorage();
if (!scriptTask.ScriptingEngine.CloseIDE(false))
{
throw new Exception("Unable to close Scripting engine.");
}
}
How do I migrate this code to SQL Server 2012, because following methods are removed from SQL Server 2012 dll-s (assemblies):
InitNewScript
AddProjectReference
AddCodeFile
SaveScriptToStorage
CloseIDE
Build
ShowDesigner
Generally, how do I dynamically set source code for script task in SQL Server 2012?

As you've noticed, the VSTA helper methods you could use in 2008 were moved/removed in 2012. It is still possible to do, but the code has changed.
The easiest thing to do is load an existing project using VstaHelper.LoadProjectFromFolder().
If you want to dynamically add script files, see the snippet below. There are two main things you need to keep in mind:
The ScriptingEngine and VstaHelper classes represent VSTA itself. This is where you’d create the project, and add new files. You cannot remove or replace an existing file directly here. When you call SaveProjecToStorage(), it's like closing the VSTA window … it saves the project and compiled binary to the ScriptTask.
ScriptTask.ScriptStorage allows you to directly manipulate the source file contents. From here, you can modify the content of a file.
The following code snippet should help you get started.
static void Main(string[] args)
{
// 1. Create new package, and add a script task
var pkg = new Package();
var exec = pkg.Executables.Add("STOCK:ScriptTask");
var th = (TaskHost)exec;
th.Name = "Script Task";
th.Description = "This is a Script Task";
var task = (ScriptTask)th.InnerObject;
// 2. Set the script language - "CSharp" or "VisualBasic"
task.ScriptLanguage = VSTAScriptLanguages.GetDisplayName("CSharp");
// 3. Set any variables used by the script
//task.ReadWriteVariables = "User::Var1, User::Var2";
// 4. Create a new project from the template located in the default path
task.ScriptingEngine.VstaHelper.LoadNewProject(task.ProjectTemplatePath, null, "MyScriptProject");
// 5. Initialize the designer project, add a new code file, and build
//task.ScriptingEngine.VstaHelper.Initalize("", true);
//task.ScriptingEngine.VstaHelper.AddFileToProject("XX.cs", "FileContents");
//task.ScriptingEngine.VstaHelper.Build("");
// 6. Persist the VSTA project + binary to the task
if (!task.ScriptingEngine.SaveProjectToStorage())
{
throw new Exception("Save failed");
}
// 7. Use the following code to replace the ScriptMain contents
var contents = File.ReadAllText("path to file");
var scriptFile =
task.ScriptStorage.ScriptFiles["ScriptMain.cs"] =
new VSTAScriptProjectStorage.VSTAScriptFile(VSTAScriptProjectStorage.Encoding.UTF8, contents);
// 8. Reload the script project, build and save
task.ScriptingEngine.LoadProjectFromStorage();
task.ScriptingEngine.VstaHelper.Build("");
// 9. Persist the VSTA project + binary to the task
if (!task.ScriptingEngine.SaveProjectToStorage())
{
throw new Exception("Save failed");
}
// 10. Cleanup
task.ScriptingEngine.DisposeVstaHelper();
// 11. Save
string xml;
pkg.SaveToXML(out xml, null);
File.WriteAllText(#"c:\temp\package.dtsx", xml);
}

Related

Can I store an Ini file in a Resources file?

I have a Windows Forms application, .Net Framework 4.6.1, and I want to store some DB connection data in an Ini file.
I then wanted to store it in the Resources file of the project (so I don't have to copy/paste the file in the Debug and Release folder manually, etc.) as a normal file, but when I tried to compile the program and read the Ini data with ini-parser, the following exception showed up: System.ArgumentException: 'Invalid characters in path access'.
I'm using Properties.Resources where I read the Ini file, so I guessed there would be no problem with the path. Could it be a problem with the Ini file itself?
The content of the Ini file is the following:
[Db]
host = (anIP)
port = (aPort)
db = (aDbName)
user = (aDbUser)
password = (aDbUserPwd)
And my method for reading the data:
public static void ParseIniData()
{
var parser = new FileIniDataParser();
IniData data = parser.ReadFile(Properties.Resources.dbc);
mysqlHost = data["Db"]["host"];
mysqlPort = data["Db"]["port"];
mysqlDb = data["Db"]["db"];
mysqlUser = data["Db"]["user"];
mysqlPwd = data["Db"]["password"];
}
I finally could do it using what #KlausGütter told me in the comments (thanks!).
Instead of using the FileIniDataParser you have to use the StreamIniDataParser, and get the Stream with Assembly.GetManifestResourceStream.
I found this a bit tricky, because using this method you need to set the Build Action in the file you want to read to Embedded Resource.
This file is then added as an embedded resource in compile time and you can retrieve its stream.
So my method ended up the following way:
public static void ParseIniData()
{
var parser = new StreamIniDataParser();
dbcReader = new StreamReader(_Assembly.GetManifestResourceStream("NewsEditor.Resources.dbc.ini"));
IniData data = parser.ReadData(dbcReader);
mysqlHost = data["Db"]["host"];
mysqlPort = data["Db"]["port"];
mysqlDb = data["Db"]["db"];
mysqlUser = data["Db"]["user"];
mysqlPwd = data["Db"]["password"];
}
where _Assembly is a private static attribute: private static Assembly _Assembly = Assembly.GetExecutingAssembly();. This gets you the assembly that's being executed when running the code (you could also use this code directly in the method, but I used the Assembly on another method in my class, so I decided to set an attribute... DRY I guess).

Find references to loaded dlls

I have an application that can be customized by writing custom extensions. All of them are in proj\Extensions folder. At runtime my Core project loads every extension from the folder and executes code. The problem is when one of the extensions uses additional libraries, because the Core project cannot find reference to these additional libraries.
So for example in my Core project I have:
public void Preview(IFileDescription fileDescription)
{
var extension = Path.GetExtension(fileDescription.FilePath);
var reader = _readerFactory.Get(extension);
Data = reader.GetPreview(fileDescription);
}
In one of my extensions I have
public DataTable GetPreview(IFileDescription options)
{
var data = new DataTable();
using (var stream = new StreamReader(options.FilePath))
{
var reader = new CsvReader(stream); // <- This is from external library and because of this Core throws IO exception
}
/*
...
*/
return data;
}
Core only knows about the interface, so when one of readers uses for example CsvHelper.dll I get exception of FileNotFound, because Core cannot find CsvHelper.dll. Is there any way to tell the compiler to look for additional libraries in specific folder? I used Reference Paths, but it didn't solve the problem. It still threw the same exception.
Yes, it's possible. You can attach to the AppDomain.AssemblyResolve event and manually load the required DLLs from your add-in directory. Execute the following code before executing any add-in code:
var addinFolder = ...;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var missing = new AssemblyName(e.Name);
var missingPath = Path.Combine(addinFolder, missing.Name + ".dll");
// If we find the DLL in the add-in folder, load and return it.
if (File.Exists(missingPath))
return Assembly.LoadFrom(missingPath);
// nothing found, let .NET search the common folders
return null;
};

SharpShell server .dll NOT signed

I need to develop a Shell Context Menu extension that references some other custom assemblies... I don't want to assign a Strong Name Key to those custom assemblies!
The guide I followed to do this uses the SharpShell project and illustrates how to sign (but does not expalins why) the assembly... and this is my problem: if I sign my final .dll then I have many errors during my project's building phase, because some assemblies my project references are not strongly named ("Referenced assembly does not have a strong name").
In general, googling about the C# Shell Extension implementation, all best tutorials I found sign the final assembly... is it mandatory?
Without signing the assembly ServerManager.exe returns this error: "The file 'XYZ.dll' is not a SharpShell Server".
Finally I've solved my troubles... the SharpShell.dll file obtained through NuGet was a different version of the ServerManager.exe ones.
Uninstalling the SharpShell NuGet package and directly referencing the SharpShell.dll you find inside the ServerManager folder was my solution!
Moreover, I was looking between the article comments... please read this question.
You don't need to use old DLL.
Please use this code directly, without using ServerManager.exe.
private static ServerEntry serverEntry = null;
public static ServerEntry SelectedServerEntry
{
get
{
if (serverEntry == null)
serverEntry = ServerManagerApi.LoadServer("xxx.dll");
return serverEntry;
}
}
public static ServerEntry LoadServer(string path)
{
try
{
// Create a server entry for the server.
var serverEntry = new ServerEntry();
// Set the data.
serverEntry.ServerName = Path.GetFileNameWithoutExtension(path);
serverEntry.ServerPath = path;
// Create an assembly catalog for the assembly and a container from it.
var catalog = new AssemblyCatalog(Path.GetFullPath(path));
var container = new CompositionContainer(catalog);
// Get the exported server.
var server = container.GetExport<ISharpShellServer>().Value;
serverEntry.ServerType = server.ServerType;
serverEntry.ClassId = server.GetType().GUID;
serverEntry.Server = server;
return serverEntry;
}
catch (Exception)
{
// It's almost certainly not a COM server.
MessageBox.Show("The file '" + Path.GetFileName(path) + "' is not a SharpShell Server.", "Warning");
return null;
}
}
Install code:
ServerRegistrationManager.InstallServer(SelectedServerEntry.Server, RegistrationType.OS64Bit, true);
Register code:
ServerRegistrationManager.RegisterServer(SelectedServerEntry.Server, RegistrationType.OS64Bit);

Wix Bootstrapper - Best way to update/install Visual FoxPro database

As mentioned here wix-bootstrapper-update-ui-xaml-from-customaction I use a Bootstrapper to install two MSI-packages.
During the installation I want to install/update a Visual FoxPro database (consisting of free tables).
At the moment I achieve this by calling a Visual FoxPro-exe during the ApplyComplete-Event of the BootstrapperApplication. To establish a communication between the BootstrapperApplication and the Visual FoxPro-exe I use MSMQ :
private void OnApplyComplete(object sender, ApplyCompleteEventArgs e)
{
string updateFile = this.installationFolder + "\\updfile.exe";
if (!MessageQueue.Exists(NAMEDPVDATENBANKNACHRICHTENSCHLANGE))
{
this._msmq = MessageQueue.Create(UPDATEMSMQ);
}
else
{
this._msmq = new MessageQueue(UPDATEMSMQ);
}
this._msmq.SetPermissions("Everyone", MessageQueueAccessRights.FullControl);
this._msmq.Purge();
if (System.IO.File.Exists(updateFile))
{
this._dbUpdate = true;
ProcessStartInfo updateProcessInfo = new ProcessStartInfo();
updateProcessInfo.FileName = updateFile;
updateProcessInfo.Arguments = UPDATEMSMQ;
updateProcessInfo.UseShellExecute = true;
updateProcessInfo.WorkingDirectory = this.installationFolder;
updateProcessInfo.CreateNoWindow = true;
Process updateProcess = new Process();
updateProcess.StartInfo = updateProcessInfo;
updateProcess.EnableRaisingEvents = true;
updateProcess.Exited += new EventHandler(this.updateFinished);
updateProcess .Start();
while (this._dbUpdate)
{
Message msg = null;
try
{
nachricht = this._msmq.Receive(new TimeSpan(0, 0, 45));
}
catch (MessageQueueException msgEx)
{
if (nachrichtAusnahme.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
this.Engine.Log(LogLevel.Verbose, msgEx);
}
}
if (msg != null)
{
msg.Formatter = new ActiveXMessageFormatter();
this.Engine.Log(LogLevel.Verbose, "VfpUpdate - " + msg.Body.ToString());
}
}
}
this._msmq.Close();
MessageQueue.Delete(UPDATEMSMQ);
}
private void updateFinished(object sender, EventArgs e)
{
this._dbUpdate = false;
this.Engine.Log(LogLevel.Verbose, "Update finished");
}
This way it works like a charm unless there are errors during the the update of the Visual FoxPro database. It should be possible to roll-back the changes made during the installation. For me it would be no problem to create a backup of the Visual FoxPro-files and to restore the files if an error occurs. But how should I do this with the files changed by the actual Bootstrapper?
With a CustomAction I can use ActionResult.Failure or ActionResult.Success. But with a CustomAction I face the following issues:
no access to Application.Current (for reading values from a customized ResourceDictionary with localized strings that I use within the Bootstrapper)
MSMQ-queue is broken (closed?!) after the first message is delivered
display the currently performed task in the MainWindow.
Any advice on how to perform the update of the Visual FoxPro database inside a BootstrapperApplication is really welcome.
I don't know that in the context of an installation bootstrapper. OTOH, either a VFP database and tables or VFP tables (free) are just plain files that are copied/moved from a folder. You don't need to have an exe for that to create them programmatically.
Also having those files as part of the setup might not be a good idea as well. If some files are removed because of version changes then your installer might start to bark about that.
Looking at the bootstrapper code, I have a suspicion that you are creating those free tables in the installation folder or its subfolders which would be a real PITA. Because installation folder is generally under "Program Files (x86)" which is a read only location.
Your bootstrapper code is C#. You might use C# to create those tables as well. One way to do that is to use VFPOLEDB and ExecScript() calls.

IManExt ImportCmd trouble

I have been writing a small application using C# to copy a document into an individuals 'My Documents' folder on our DMS server.
I've beased the code around the listing provided in the 'WorkSite SDK 8: Utilize the IMANEXT2Lib.IManRefileCmd to File New Document Folders' blog.
Using this code in a WinForm application I have no problems copying the file from the source folder into the users DMS 'My Documents' folder.
However if I use the code in a command line application/.dll or any other type of application (other than WinForm) during the copy process I receive the error messages;
1.
Error occurred when try to log the event!
IManExt: Error occurred when try to log the event!
Access is denied.
2.
The document was imported to the database, but could not be added to
the folder.
IManExt: The document was imported to the database, but could not be
added to the folder.
IManExt.LogRuleEventsCmd.1: Error occurred when try to log the event!
IManExt.LogRuleEventsCmd.1: Access is denied.
Error occurred when try to log the event!
-%-
Does anyone know why I'd receiving the 'Access Denied' error messages when using a non-WinForms application to copy documents?
What would I need to do to get around this issue?
Any help would be amazing!
Code in place:
public void moveToDMS(String servName, String dBName, String foldName)
{
const string SERVERNAME = servName; //Server name
const string DATABASENAME = dBName; //Database name
const string FOLDERNAME = foldName; //Matter alias of workspace
IManDMS dms = new ManDMSClass();
IManSession sess = dms.Sessions.Add(SERVERNAME);
sess.TrustedLogin();
//Get destination database.
IManDatabase db = sess.Databases.ItemByName(DATABASENAME);
//Get destination folder by folder and owner name.
IManFolderSearchParameters fparms = dms.CreateFolderSearchParameters();
fparms.Add(imFolderAttributeID.imFolderOwner, sess.UserID);
fparms.Add(imFolderAttributeID.imFolderName, FOLDERNAME);
//Build a database list in which to search.
ManStrings dblist = new ManStringsClass();
dblist.Add(db.Name);
IManFolders results = sess.WorkArea.SearchFolders(dblist, fparms);
if (results.Empty == true)
{
//No results returned based on the search criteria.
Console.WriteLine("NO RESULTS FOUND!");
}
IManDocumentFolder fldr = null;
if (results.Empty == false)
{
//Assuming there is only one workspace returned from the results.
fldr = (IManDocumentFolder)results.ItemByIndex(1);
}
if (fldr != null)
{
// Import file path
string docPath = #"C:\Temp\";
string docName = "MyWord.doc";
// Create an instance of the ContextItems Collection Object.
ContextItems context = new ContextItemsClass();
// Invoke ImportCmd to import a new document to WorkSite database.
ImportCmd impCmd = new ImportCmdClass();
// The WorkSite object you pass in can be a database, session, or folder.
// Depends on in where you want the imported doc to be stored.
context.Add("IManDestinationObject", fldr); //The destination folder.
// Filename set here is used for easy example, a string variable is normally used here
context.Add("IManExt.Import.FileName", docPath + docName);
// Document Author
context.Add("IManExt.Import.DocAuthor", sess.UserID); //Example of a application type.
// Document Class
context.Add("IManExt.Import.DocClass", "BLANK"); //Example of a document class.
//context.Add("IManExt.Import.DocClass", "DOC"); //Example of a document class.
// Document Description (optional)
context.Add("IManExt.Import.DocDescription", docName); //Using file path as example of a description.
// Skip UI
context.Add("IManExt.NewProfile.ProfileNoUI", true);
impCmd.Initialize(context);
impCmd.Update();
if (impCmd.Status == (int)CommandStatus.nrActiveCommand)
{
impCmd.Execute();
bool brefresh = (bool)context.Item("IManExt.Refresh");
if (brefresh == true)
{
//Succeeded in importing a document to WorkSite
IManDocument doc = (IManDocument)context.Item("ImportedDocument");
//Succeeded in filing the new folder under the folder.
Console.WriteLine("New document number, " + doc.Number + ", is successfully filed to " + fldr.Name + " folder.");
}
}
}
}
Just in case this helps someone else.
It seems my issue was the result of a threading issue.
I noticed the C# winform apps I had created were automatically set to run on a single 'ApartmentState' thread ([STAThread]).
Whereas the console applications & class library thread state and management hadn't been defined within the project and was being handled with the default .NET config.
To get this to work: In the console application, I just added the [STAThread] tag on the line above my Main method call.
In the class library, I defined a thread for the function referencing the IMANxxx.dll and set ApartmentState e.g.
Thread t = new Thread(new ThreadStart(PerformSearchAndMove));
t.SetApartmentState(ApartmentState.STA);
t.Start();
In both cases ensuring single 'ApartmentState' thread was implemented set would resolve the issue.

Categories