SSIS Looping through servers and handling failed logins - sql-server-2008

Creating a package that will loop through all the servers in our different environments(DEV, UAT, PROD). We use service accounts and there is one service account for DEV and UAT and another for PROD. I am using a ForEach Loop Container/variables to set the connection string.
The issue: When the data flow in the loop trys to connect to the PROD servers they fail because they are using the DEV/UAT service account which obviously doesnt have access to PROD, which is fine. The problem is this kills the loop. Normally I would just put an event handler on the data flow and set the event handler's System Variable Propagate:OnError = False so that the error doesn't bubble up to the loop and kill it. Well this doesn't work because the OLE DB connection inside the data flow fails during validation(while the package is running) and apparently the Propagate = False setting only keeps an error from bubbling up if it occurs during the execution of a task and not the validation of a task.
I can set the MaximumErrorCount = 0 on everything including the package itself but that is a bit heavy handed and the package would always report that it ran successfully no matter what errors were encountered.
Running SQL Server 2008-R2
Link to an article on how to not kill your loop using the Propagate setting if someone isn't familiar with it.

One suggestion would be to put a Script Task before the Data Flow tasks that checks access to the connection string with a try-catch block and sets a variable upon failure, and then use that variable in a conditional split to determine whether to run the Data Flow or log that the connection string failed.
Alternatively, if you don't care about why it failed (since you already know it's because of permissions) you could just use a Precedence Constraint and only run Data Flows where the connection succeeded.
UPDATE:
Here's some working code for the Script Task:
public void Main() {
string connString = Dts.Variables["ConnectionStringToTest"].Value;
try {
using (OleDbConnection connection = new OleDbConnection()) {
connection.ConnectionString = connString;
connection.Open();
}
Dts.Variables["User::DatabaseCanConnect"].Value = true;
}
catch (Exception ex) {
Dts.Variables["User::DatabaseCanConnect"].Value = false;
}
Dts.TaskResult = (int)ScriptResults.Success;
}
Create a variable called DatabaseCanConnect at the package scope. Set it to Boolean, it'll default to False.
Create a Script Task. Edit the ReadWriteVariables property and add your new variable.
Add to your ReadOnlyVariables whatever variable you're using to build the connection string out of your ForEach loop. I've named mine ConnectionStringToTest.
Add the script code above as your Main() function. Note that this using an OleDbConnection. This should mimic whatever connection you're using for the connection manager you're modifying in your data flow. So if it's a SQL connection, use a SqlConnection instead.
In your code, use the DatabaseCanConnect variable to determine flow from here. You can use it in a Precedence Constraint to prevent flow to the Data Flow, or use it in a Conditional Split (my preference, more visible to other developers) to log connection errors on failure and proceed as normal otherwise.

Related

SqlDelight, MySql, and Flow: Flow's collect lambda not invoked on database changes

I'm playing around with SqlDelight using MySql with Hikary datasource in a plain JVM project using Kotlin and Flow to consume queries results.
The first execution of a query returns the data as expected, however the Flow#emit() is not triggered when the data on the database changes.
This one is the repository function I'm invoking:
fun getAllUser(): Flow<List<User>> = database.userQueries.getAll()
.asFlow()
.mapToList()
This is the main function where the repository function is invoked:
fun main() = runBlocking<Unit> { repository.getAllUser().collect {
println("Collecting users: $it")
}
}
I've tried debugging the underling code and the listener on the Query object is registered and the main is correctly blocked on the execution of the repository function.
Anyone else has experienced something similar?
So stupid, my bad! I was updating the database manually and expecting to see the Flow magically updating. Anyway, updating the database by means of SqlDelight functions causes the Flow to emit the updated recordset as expected.

SSIS error: The Password was not allowed

I am receiving the following error when executing the FTP task in SSIS.
The Password was not allowed
What does it mean?
SSIS saves sensitive data (like passwords) encrypted based on the value of the ProtectionLevel package property. This value is EncryptSensitiveWithUserKey by default (this means, that the encrypted part could be read with the same user account which created the package). Learn more about Access Control on TechNet
You can set this property to EncryptSensitiveWithPassword, then set the PackagePassword property. You can execute the package with DTExec utility with the /De {password} parameter.
(You can edit package properties by right clicking on a blank area on the package's control flow, then selecting Properties item).
Another way is to set up a simple Script Task (before the FTP Task) which sets the value at runtime. The following code sets the password property of the FTPConnectionName connection.
C# code
ConnectionManager FTPConn;
FTPConn = Dts.Connections["FTPConnectionName"];
FTPConn.Properties["ServerPassword"].SetValue(FTPConn, "YourPassword");
Dts.TaskResult = (int)ScriptResults.Success;
Add this code to the script task's entry point (Main() function).
(Optionally you can add a string variable to your package and add it to the Read Only variables of the script task and use it (Dts.Variables["FTPPassword"].Value) to set the password.

ForEachloop SSIS

Task: Loop thru these excel files and insert data into SQL table but in the process i get an error and i don't know which it errored on.
My understanding is SSIS doesn't loop thru file in an random order but i get an error about CANNOT ACQUIRE CONNECTION FROM CONNECTIONMANAGER. Excel Source failed validation and returned error code.. I did set 64bitruntime to False. This happened on VS 2008/SQL Server 2008 R2 on Windows 7 OS. Initially i was able to run the whole process successfully on Windows XP- VS2008 /SQL Server 2008 R2.
Problem: How do i know which file system is going to iterate next if i have 70 odd files in a folder. The thing i get an error and i'm not sure which file SSIS is working on. However i do see files are executed and data is in SQL.
Let me know how to find which file SSIS is currently working or the next one it will work on.
You could add a Script Task and log the variable used in the foreach loop.
Add the variable as readonly variable in the script task editor and then add something like this in the main method (C#):
public void Main()
{
bool fireAgain = true;
Dts.Events.FireInformation(0, "Logging FELC variable", "File: " + Dts.Variables["User::FilePath"].Value.ToString(), string.Empty, 0, ref fireAgain);
Add a script task inside your ForEach container, immediately before you do the Excel processing. In the script task, add the variable you configured in your ForEach loop to hold the filename to the Read Only Variables. In the script itself, call the FireInformation event, which will add an informational message to the progress log in SSIS. In the FireInformation call, pass the value of your filename variable as the message argument.
This will let you see each file being processed, and which one it was processing when it failed.
FireInformation help: http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.dts.runtime.idtscomponentevents.fireinformation.aspx

Executing child package in SSIS package store from script task, with a twist

I have a parent package that needs to execute the same child package multiple times. To make things more fun, each instance needs to have a different value defined for the parent parameter passed to the child package.
I've created a script task using the following script:
Microsoft.SqlServer.Dts.Runtime.Application App = new Microsoft.SqlServer.Dts.Runtime.Application();
Package pkg = new Package();
try
{
pkg = App.LoadPackage(#"\\server\SSIS Packages\ChildPackage.dtsx", null);
pkg.Variables["ChildVariableName"].Value = Dts.Variables["AParentVariableName"].Value;
pkg.Execute();
Dts.TaskResult = (int)ScriptResults.Success;
}
catch (Exception ex)
{
Dts.Events.FireError(0, "Run child pkg for parent task", ex.Message, string.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
}
Problem is, my packages are stored in the SSIS package store of my SQL 2008 R2 server and I can't figure out how to reference them; every code sample I've seen is for a physical location. This is on an HA cluster so having a physical location for the package will be difficult to maintain.
So I either need to (a) figure out how to change the value of ParentVariable every time an Execute Package task is kicked off for this child package or (b) figure out how to reference the proper package inside the SSIS package store, at which point I can safely pass the proper value. Anyone have any ideas?
Instead of App.LoadPackage method, you would use the LoadFromSqlServer method
app.LoadFromSqlServer("\OptionalFolderButSlashRequired\ChildPackage", "server", null, null, null);
The documentation on Application and Package usually have examples in the methods I've needed to use.

Programmatically load SSIS package configurations

I am making a framework in SSIS to load files from configurable folders and match them to a logical job in the database. In this job a package name is configured and in SSIS I execute this package in runtime.
I want to programmatically load a package configuration for this package, depending on the job loaded. SSIS SQL Server package configuration is not an option, because that loads values to this package just once in runtime for the package itself, but I want to load a specific package configuration in runtime that has been stored with the job (job has one package, but has many package configurations)....
Schematically:
folderA -> file A.1 -> job A -> load package configuration for job A -> execute package in job A.
Is that possible?
We do something simliar using parent and child packages to run a standard package for differnt clients with differnt configuration values. The parent packge uses and enviroment variable and our configuration table to pull the configuration values for that particular process. The child table is configured to accept variables for the configuration which are sent from the parent package inthe execute package task. This also allows us to do some custom steps for a particular client in the parent package if need be (which is about 100% of the time here). So of you get one file form one client that they just cannot provide in the format the standard child import uses you can do transformation steps to get teh file ready for the standard import and then run the standard. Or you can add steps after the standrd package to send an email to the client with exceptions that they need to fix in their data for instance if only one client requires that.
You create Variables in the parent package for each piece of configuration information you want to send, typically to other variables or connection strings for the conmnections in the child package. You then put in an Excute package task that uses a connection to the child package.
In the child package you then go to the SSIS menu and choose package configurations and Add. Then for the type of configuration, you choose Parent Package variable. You will create one Parent package variable for each configuration item you want to send to the Child package. Things we send are things like the client_id, the connection strings to a client specific database, variables for things that might vary by client, etc.
We also store all our configurations in a table in a meta database where we store information about imports. So we set up our parent pacakge to use an environment variable to tell it which database to connect to to get the configuration information Then the second confiuration is to the SSISConfiguration table that stores the configuration information. We populate that information by server (it will vary by server generally, connection strings are different for dev, qa and prod) through an insert script that we run before testing the package.
For further detail, look in Books Online for execute package task and it wil show you how to set up the packages to pass variables.
I found the solution now. It is only possible by using a script task that uses the SSIS object model to create a package in runtime based on the SQL Server Application class where you can load the package by filename. After loading the package from file, I can read the configuration from file by xml or by SQL Server and add it in runtime to the child package configuration list.
Two important notes:
1) Parent variables are not passed to child package automatically.
Only when an execute package task is used the parent variables are passed to the child automatically. To get this working I search the variables in runtime and write the values in it, because I know the exact variables I want to pass to each child package.
2) When using SQL Server as a package configuration for a child package, you must also create a connection manager in runtime and add it to the connection manager collection of the package. when adding the package configuration to the child package, be sure that the name of that connection manager is part of the connection string.
Here is the code to prove it works:
//load the information of the job into these variables. Package is the File system deployed package on a share. Package configuration can be the package configuration in an xml file on a share, or a connection string when using SQL Server (this one is used here).
string package = this.Dts.Variables["Package"].Value.ToString();
string packageConfiguration = this.Dts.Variables["PackageConfiguration"].Value.ToString();
//create a package from package factory, by file.
Microsoft.SqlServer.Dts.Runtime.Application app = new Microsoft.SqlServer.Dts.Runtime.Application();
Package packageToRun = app.LoadPackage(package, null);
//------------------------------------------ CHILD PACKAGE VARIABLES PASSING
packageToRun.EnableConfigurations = true;
//add one extra package configuration for child package specific configuration
Configuration config = packageToRun.Configurations.Add();
config.Name = "MyConfig";
config.ConfigurationType = DTSConfigurationType.SqlServer;
config.ConfigurationString = packageConfiguration;
//use the name 'MyConnectionManager' in your packageConfiguration
ConnectionManager cm = packageToRun.Connections.Add("OleDb");
cm.Name = "MyConnectionManager";
//TODO: retrieve this from an environvariable to allow change in data source for DEV, QA, PROD, now temporarly fixed to this value
cm.ConnectionString = "Data Source=.;Initial Catalog=YYYYYYYYYY;Provider=SQLNCLI10.1;Integrated Security=SSPI;";
//For Parent-Child var passing, I used the technique to let all the parent variables being defined in the child packages.
//Other technique could be to allow the child package not define the parent variables, but then the child packages have to reference them from code
//------------------------------------------ PARENT VARIABLES PASSING
//Now check if these parent variables exist in child package and write the actual values in them
try
{
Variables vars = null;
VariableDispenser variableDispenser = packageToRun.VariableDispenser;
if (
packageToRun.Variables.Contains("User::XXXXXXXXXXXX") &&
)
{
packageToRun.VariableDispenser.LockForWrite("User::XXXXXXXXXXXX");
variableDispenser.GetVariables(ref vars);
packageToRun.Variables["User::XXXXXXXXXXXX"].Value = this.Dts.Variables["User::XXXXXXXXXXXX"].Value;
vars.Unlock();
packageToRun.Execute();
Dts.TaskResult = (int)ScriptResults.Success;
}
else
{
this.Dts.Events.FireError(0, string.Empty, "Child package: " + package + " has no required master variables defined or unable to unlock.", string.Empty, 0);
}
}
catch (Exception ex)
{
this.Dts.Events.FireError(0, string.Empty, ex.Message, string.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
}